summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accounts/AccountMonitor.java66
-rw-r--r--core/java/android/app/Activity.java99
-rw-r--r--core/java/android/app/ActivityGroup.java16
-rw-r--r--core/java/android/app/ActivityManager.java72
-rw-r--r--core/java/android/app/ActivityManagerNative.java21
-rw-r--r--core/java/android/app/ActivityThread.java32
-rw-r--r--core/java/android/app/AlarmManager.java78
-rw-r--r--core/java/android/app/AlertDialog.java203
-rw-r--r--core/java/android/app/ApplicationContext.java165
-rw-r--r--core/java/android/app/DatePickerDialog.java4
-rw-r--r--core/java/android/app/IActivityManager.java4
-rwxr-xr-xcore/java/android/app/IAlarmManager.aidl1
-rw-r--r--core/java/android/app/Instrumentation.java10
-rw-r--r--core/java/android/app/LocalActivityManager.java40
-rw-r--r--core/java/android/app/Notification.java4
-rw-r--r--core/java/android/app/PendingIntent.java24
-rw-r--r--core/java/android/app/ProgressDialog.java2
-rw-r--r--core/java/android/app/SearchDialog.java59
-rw-r--r--core/java/android/app/SearchManager.java43
-rw-r--r--core/java/android/app/Service.java4
-rw-r--r--core/java/android/app/TimePickerDialog.java2
-rw-r--r--core/java/android/bluetooth/BluetoothA2dp.java181
-rw-r--r--core/java/android/bluetooth/BluetoothClass.java191
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java2
-rw-r--r--core/java/android/bluetooth/BluetoothError.java42
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java162
-rw-r--r--core/java/android/bluetooth/DeviceClass.java131
-rw-r--r--core/java/android/bluetooth/IBluetoothA2dp.aidl29
-rw-r--r--core/java/android/bluetooth/IBluetoothHeadset.aidl6
-rw-r--r--core/java/android/bluetooth/package.html1
-rw-r--r--core/java/android/content/AbstractTableMerger.java3
-rw-r--r--core/java/android/content/AsyncQueryHandler.java26
-rw-r--r--core/java/android/content/BroadcastReceiver.java2
-rw-r--r--core/java/android/content/Context.java24
-rw-r--r--core/java/android/content/DefaultDataHandler.java75
-rw-r--r--core/java/android/content/DialogInterface.java41
-rw-r--r--core/java/android/content/Intent.java177
-rw-r--r--core/java/android/content/SyncManager.java7
-rw-r--r--core/java/android/content/pm/ActivityInfo.java29
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java9
-rwxr-xr-xcore/java/android/content/pm/ConfigurationInfo.java120
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl2
-rw-r--r--core/java/android/content/pm/InstrumentationInfo.java2
-rw-r--r--core/java/android/content/pm/PackageInfo.java11
-rw-r--r--core/java/android/content/pm/PackageItemInfo.java7
-rw-r--r--core/java/android/content/pm/PackageManager.java186
-rw-r--r--core/java/android/content/pm/PackageParser.java80
-rw-r--r--core/java/android/content/res/ColorStateList.java6
-rw-r--r--core/java/android/content/res/Configuration.java64
-rw-r--r--core/java/android/content/res/PluralRules.java14
-rw-r--r--core/java/android/content/res/Resources.java169
-rw-r--r--core/java/android/content/res/StringBlock.java15
-rw-r--r--core/java/android/database/AbstractCursor.java21
-rw-r--r--core/java/android/database/AbstractWindowedCursor.java2
-rw-r--r--core/java/android/database/CursorWindow.java9
-rw-r--r--core/java/android/database/DatabaseUtils.java15
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java174
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java168
-rw-r--r--core/java/android/database/sqlite/SQLiteOpenHelper.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteProgram.java4
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java56
-rw-r--r--core/java/android/hardware/Camera.java33
-rw-r--r--core/java/android/hardware/ISensorService.aidl1
-rw-r--r--core/java/android/hardware/Sensor.java146
-rw-r--r--core/java/android/hardware/SensorEvent.java146
-rw-r--r--core/java/android/hardware/SensorEventListener.java49
-rw-r--r--core/java/android/hardware/SensorListener.java64
-rw-r--r--core/java/android/hardware/SensorManager.java1366
-rw-r--r--core/java/android/inputmethodservice/AbstractInputMethodService.java170
-rw-r--r--core/java/android/inputmethodservice/ExtractEditText.java23
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java151
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java172
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java864
-rwxr-xr-xcore/java/android/inputmethodservice/Keyboard.java756
-rwxr-xr-xcore/java/android/inputmethodservice/KeyboardView.java1049
-rw-r--r--core/java/android/inputmethodservice/SoftInputWindow.java151
-rw-r--r--core/java/android/inputmethodservice/package.html8
-rw-r--r--core/java/android/net/MobileDataStateTracker.java26
-rw-r--r--core/java/android/net/NetworkInfo.java121
-rw-r--r--core/java/android/net/NetworkStateTracker.java50
-rw-r--r--core/java/android/net/NetworkUtils.java8
-rw-r--r--core/java/android/net/Proxy.java8
-rw-r--r--core/java/android/net/http/Connection.java5
-rw-r--r--core/java/android/net/http/Headers.java219
-rw-r--r--core/java/android/net/http/Request.java6
-rw-r--r--core/java/android/net/http/RequestHandle.java20
-rw-r--r--core/java/android/net/http/RequestQueue.java2
-rw-r--r--core/java/android/os/AsyncTask.java454
-rw-r--r--core/java/android/os/BatteryManager.java6
-rw-r--r--core/java/android/os/BatteryStats.java518
-rw-r--r--core/java/android/os/Debug.java4
-rw-r--r--core/java/android/os/Handler.java64
-rw-r--r--core/java/android/os/ICheckinService.aidl3
-rw-r--r--core/java/android/os/IPowerManager.aidl2
-rw-r--r--core/java/android/os/Looper.java16
-rw-r--r--core/java/android/os/Process.java26
-rw-r--r--core/java/android/pim/EventRecurrence.java1
-rw-r--r--core/java/android/pim/RecurrenceSet.java7
-rw-r--r--core/java/android/preference/CheckBoxPreference.java2
-rw-r--r--core/java/android/preference/DialogPreference.java13
-rw-r--r--core/java/android/preference/EditTextPreference.java2
-rw-r--r--core/java/android/preference/ListPreference.java16
-rw-r--r--core/java/android/preference/Preference.java408
-rw-r--r--core/java/android/preference/PreferenceActivity.java4
-rw-r--r--core/java/android/preference/PreferenceCategory.java2
-rw-r--r--core/java/android/preference/PreferenceGroup.java4
-rw-r--r--core/java/android/preference/PreferenceManager.java27
-rw-r--r--core/java/android/preference/PreferenceScreen.java41
-rw-r--r--core/java/android/preference/RingtonePreference.java4
-rw-r--r--core/java/android/preference/VolumePreference.java200
-rw-r--r--core/java/android/provider/Calendar.java21
-rw-r--r--core/java/android/provider/Checkin.java2
-rw-r--r--core/java/android/provider/Contacts.java58
-rw-r--r--core/java/android/provider/Downloads.java221
-rw-r--r--core/java/android/provider/Gmail.java51
-rw-r--r--core/java/android/provider/Im.java146
-rw-r--r--core/java/android/provider/LiveFolders.java298
-rw-r--r--core/java/android/provider/MediaStore.java287
-rw-r--r--core/java/android/provider/SearchRecentSuggestions.java5
-rw-r--r--core/java/android/provider/Settings.java1448
-rw-r--r--core/java/android/provider/Sync.java51
-rw-r--r--core/java/android/provider/Telephony.java126
-rw-r--r--core/java/android/security/Md5MessageDigest.java3
-rw-r--r--core/java/android/security/MessageDigest.java21
-rw-r--r--core/java/android/security/Sha1MessageDigest.java3
-rw-r--r--core/java/android/security/package.html3
-rw-r--r--core/java/android/server/BluetoothA2dpService.java315
-rw-r--r--core/java/android/server/BluetoothDeviceService.java171
-rw-r--r--core/java/android/server/BluetoothEventLoop.java93
-rw-r--r--core/java/android/server/checkin/FallbackCheckinService.java4
-rw-r--r--core/java/android/server/search/SearchableInfo.java3
-rw-r--r--core/java/android/speech/recognition/AbstractEmbeddedGrammarListener.java51
-rw-r--r--core/java/android/speech/recognition/AbstractGrammarListener.java39
-rw-r--r--core/java/android/speech/recognition/AbstractRecognizerListener.java83
-rw-r--r--core/java/android/speech/recognition/AbstractSrecGrammarListener.java59
-rw-r--r--core/java/android/speech/recognition/AudioAlreadyInUseException.java34
-rw-r--r--core/java/android/speech/recognition/AudioDriverErrorException.java33
-rw-r--r--core/java/android/speech/recognition/AudioSource.java45
-rw-r--r--core/java/android/speech/recognition/AudioSourceListener.java44
-rw-r--r--core/java/android/speech/recognition/AudioStream.java35
-rw-r--r--core/java/android/speech/recognition/Codec.java126
-rw-r--r--core/java/android/speech/recognition/DeviceSpeaker.java77
-rw-r--r--core/java/android/speech/recognition/DeviceSpeakerListener.java44
-rw-r--r--core/java/android/speech/recognition/EmbeddedGrammar.java43
-rw-r--r--core/java/android/speech/recognition/EmbeddedGrammarListener.java58
-rw-r--r--core/java/android/speech/recognition/EmbeddedRecognizer.java66
-rw-r--r--core/java/android/speech/recognition/Grammar.java43
-rw-r--r--core/java/android/speech/recognition/GrammarErrorException.java33
-rw-r--r--core/java/android/speech/recognition/GrammarListener.java45
-rw-r--r--core/java/android/speech/recognition/GrammarOverflowException.java33
-rw-r--r--core/java/android/speech/recognition/InvalidURLException.java34
-rw-r--r--core/java/android/speech/recognition/Logger.java127
-rw-r--r--core/java/android/speech/recognition/MediaFileReader.java90
-rw-r--r--core/java/android/speech/recognition/MediaFileReaderListener.java29
-rw-r--r--core/java/android/speech/recognition/MediaFileWriter.java49
-rw-r--r--core/java/android/speech/recognition/MediaFileWriterListener.java40
-rw-r--r--core/java/android/speech/recognition/Microphone.java76
-rw-r--r--core/java/android/speech/recognition/MicrophoneListener.java29
-rw-r--r--core/java/android/speech/recognition/NBestRecognitionResult.java113
-rw-r--r--core/java/android/speech/recognition/ParameterErrorException.java33
-rw-r--r--core/java/android/speech/recognition/ParametersListener.java63
-rw-r--r--core/java/android/speech/recognition/ParseErrorException.java33
-rw-r--r--core/java/android/speech/recognition/RecognitionResult.java27
-rw-r--r--core/java/android/speech/recognition/Recognizer.java102
-rw-r--r--core/java/android/speech/recognition/RecognizerListener.java142
-rw-r--r--core/java/android/speech/recognition/SlotItem.java27
-rw-r--r--core/java/android/speech/recognition/SrecGrammar.java81
-rw-r--r--core/java/android/speech/recognition/SrecGrammarListener.java58
-rw-r--r--core/java/android/speech/recognition/VoicetagItem.java82
-rw-r--r--core/java/android/speech/recognition/VoicetagItemListener.java49
-rw-r--r--core/java/android/speech/recognition/WordItem.java58
-rw-r--r--core/java/android/speech/recognition/impl/AudioStreamImpl.java84
-rw-r--r--core/java/android/speech/recognition/impl/DeviceSpeakerImpl.java164
-rw-r--r--core/java/android/speech/recognition/impl/EmbeddedGrammarImpl.java73
-rw-r--r--core/java/android/speech/recognition/impl/EmbeddedRecognizerImpl.java246
-rw-r--r--core/java/android/speech/recognition/impl/EntryImpl.java147
-rw-r--r--core/java/android/speech/recognition/impl/GrammarImpl.java114
-rw-r--r--core/java/android/speech/recognition/impl/LoggerImpl.java166
-rw-r--r--core/java/android/speech/recognition/impl/MediaFileReaderImpl.java156
-rw-r--r--core/java/android/speech/recognition/impl/MediaFileWriterImpl.java102
-rw-r--r--core/java/android/speech/recognition/impl/MicrophoneImpl.java165
-rw-r--r--core/java/android/speech/recognition/impl/NBestRecognitionResultImpl.java106
-rw-r--r--core/java/android/speech/recognition/impl/SrecGrammarImpl.java120
-rw-r--r--core/java/android/speech/recognition/impl/System.java179
-rw-r--r--core/java/android/speech/recognition/impl/VoicetagItemImpl.java206
-rw-r--r--core/java/android/speech/recognition/impl/WordItemImpl.java157
-rwxr-xr-xcore/java/android/speech/recognition/impl/package.html5
-rw-r--r--core/java/android/speech/recognition/package.html6
-rw-r--r--core/java/android/speech/srec/MicrophoneInputStream.java106
-rw-r--r--core/java/android/speech/srec/Recognizer.java679
-rw-r--r--core/java/android/speech/srec/Srec.java162
-rw-r--r--core/java/android/speech/srec/UlawEncoderInputStream.java186
-rw-r--r--core/java/android/speech/srec/package.html5
-rw-r--r--core/java/android/test/InstrumentationTestCase.java56
-rw-r--r--core/java/android/test/suitebuilder/annotation/LargeTest.java30
-rw-r--r--core/java/android/test/suitebuilder/annotation/MediumTest.java31
-rw-r--r--core/java/android/test/suitebuilder/annotation/SmallTest.java30
-rw-r--r--core/java/android/text/BoringLayout.java4
-rw-r--r--core/java/android/text/InputFilter.java5
-rw-r--r--core/java/android/text/InputType.java227
-rw-r--r--core/java/android/text/LoginFilter.java15
-rw-r--r--core/java/android/text/Selection.java7
-rw-r--r--core/java/android/text/Spanned.java14
-rw-r--r--core/java/android/text/StaticLayout.java5
-rw-r--r--core/java/android/text/TextUtils.java159
-rw-r--r--core/java/android/text/format/DateFormat.java (renamed from core/java/android/pim/DateFormat.java)87
-rw-r--r--core/java/android/text/format/DateUtils.java (renamed from core/java/android/pim/DateUtils.java)410
-rw-r--r--core/java/android/text/format/Formatter.java (renamed from core/java/android/content/Formatter.java)27
-rw-r--r--core/java/android/text/format/Time.java (renamed from core/java/android/pim/Time.java)236
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java31
-rw-r--r--core/java/android/text/method/BaseKeyListener.java23
-rw-r--r--core/java/android/text/method/CharacterPickerDialog.java2
-rw-r--r--core/java/android/text/method/DateKeyListener.java6
-rw-r--r--core/java/android/text/method/DateTimeKeyListener.java6
-rw-r--r--core/java/android/text/method/DialerKeyListener.java6
-rw-r--r--core/java/android/text/method/DigitsKeyListener.java12
-rw-r--r--core/java/android/text/method/KeyListener.java40
-rw-r--r--core/java/android/text/method/MetaKeyKeyListener.java6
-rw-r--r--core/java/android/text/method/MultiTapKeyListener.java7
-rw-r--r--core/java/android/text/method/QwertyKeyListener.java17
-rw-r--r--core/java/android/text/method/ScrollingMovementMethod.java28
-rw-r--r--core/java/android/text/method/TextKeyListener.java83
-rw-r--r--core/java/android/text/method/TimeKeyListener.java6
-rw-r--r--core/java/android/text/method/Touch.java21
-rw-r--r--core/java/android/text/style/DynamicDrawableSpan.java36
-rw-r--r--core/java/android/text/util/Linkify.java38
-rw-r--r--core/java/android/text/util/Regex.java20
-rw-r--r--core/java/android/util/TimeUtils.java46
-rw-r--r--core/java/android/view/ContextMenu.java2
-rw-r--r--core/java/android/view/Gravity.java191
-rw-r--r--core/java/android/view/IWindow.aidl4
-rw-r--r--core/java/android/view/IWindowManager.aidl11
-rw-r--r--core/java/android/view/IWindowSession.aidl53
-rw-r--r--core/java/android/view/KeyEvent.java54
-rw-r--r--core/java/android/view/Menu.java21
-rw-r--r--core/java/android/view/MenuItem.java16
-rw-r--r--core/java/android/view/MotionEvent.java26
-rw-r--r--core/java/android/view/OrientationListener.java19
-rw-r--r--core/java/android/view/SurfaceView.java15
-rw-r--r--core/java/android/view/View.java1360
-rw-r--r--core/java/android/view/ViewConfiguration.java15
-rw-r--r--core/java/android/view/ViewDebug.java249
-rw-r--r--core/java/android/view/ViewGroup.java108
-rw-r--r--core/java/android/view/ViewParent.java26
-rw-r--r--core/java/android/view/ViewRoot.java798
-rw-r--r--core/java/android/view/ViewTreeObserver.java171
-rw-r--r--core/java/android/view/VolumePanel.java147
-rw-r--r--core/java/android/view/Window.java46
-rw-r--r--core/java/android/view/WindowManager.java222
-rw-r--r--core/java/android/view/WindowManagerImpl.java31
-rw-r--r--core/java/android/view/WindowManagerPolicy.java128
-rw-r--r--core/java/android/view/animation/Animation.java10
-rw-r--r--core/java/android/view/animation/AnimationSet.java17
-rw-r--r--core/java/android/view/animation/Transformation.java10
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java98
-rw-r--r--core/java/android/view/inputmethod/CompletionInfo.aidl19
-rw-r--r--core/java/android/view/inputmethod/CompletionInfo.java131
-rw-r--r--core/java/android/view/inputmethod/DefaultInputMethod.java239
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.aidl19
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java126
-rw-r--r--core/java/android/view/inputmethod/ExtractedText.aidl19
-rw-r--r--core/java/android/view/inputmethod/ExtractedText.java82
-rw-r--r--core/java/android/view/inputmethod/ExtractedTextRequest.aidl19
-rw-r--r--core/java/android/view/inputmethod/ExtractedTextRequest.java61
-rw-r--r--core/java/android/view/inputmethod/InputBinding.aidl19
-rw-r--r--core/java/android/view/inputmethod/InputBinding.java152
-rw-r--r--core/java/android/view/inputmethod/InputConnection.java249
-rw-r--r--core/java/android/view/inputmethod/InputConnectionWrapper.java95
-rw-r--r--core/java/android/view/inputmethod/InputMethod.java170
-rw-r--r--core/java/android/view/inputmethod/InputMethodInfo.aidl19
-rw-r--r--core/java/android/view/inputmethod/InputMethodInfo.java300
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java900
-rw-r--r--core/java/android/view/inputmethod/InputMethodSession.java137
-rw-r--r--core/java/android/view/inputmethod/MutableInputConnectionWrapper.java38
-rw-r--r--core/java/android/view/inputmethod/package.html11
-rw-r--r--core/java/android/webkit/BrowserFrame.java183
-rw-r--r--core/java/android/webkit/CacheManager.java70
-rw-r--r--core/java/android/webkit/CallbackProxy.java10
-rw-r--r--core/java/android/webkit/CookieManager.java118
-rw-r--r--core/java/android/webkit/DateSorter.java23
-rw-r--r--core/java/android/webkit/FileLoader.java33
-rw-r--r--core/java/android/webkit/FrameLoader.java103
-rw-r--r--core/java/android/webkit/HttpDateTime.java2
-rw-r--r--core/java/android/webkit/JWebCoreJavaBridge.java1
-rw-r--r--core/java/android/webkit/LoadListener.java85
-rw-r--r--core/java/android/webkit/MimeTypeMap.java2
-rw-r--r--core/java/android/webkit/Network.java7
-rw-r--r--core/java/android/webkit/TextDialog.java6
-rw-r--r--core/java/android/webkit/URLUtil.java1
-rw-r--r--core/java/android/webkit/WebBackForwardList.java2
-rw-r--r--core/java/android/webkit/WebHistoryItem.java18
-rw-r--r--core/java/android/webkit/WebSettings.java308
-rw-r--r--core/java/android/webkit/WebView.java1074
-rw-r--r--core/java/android/webkit/WebViewCore.java400
-rw-r--r--core/java/android/webkit/WebViewDatabase.java76
-rw-r--r--core/java/android/webkit/gears/AndroidWifiDataProvider.java136
-rw-r--r--core/java/android/webkit/gears/DesktopAndroid.java6
-rw-r--r--core/java/android/webkit/gears/HttpRequestAndroid.java29
-rw-r--r--core/java/android/webkit/gears/NativeDialog.java142
-rw-r--r--core/java/android/webkit/gears/PluginSettings.java79
-rw-r--r--core/java/android/webkit/gears/UrlInterceptHandlerGears.java10
-rw-r--r--core/java/android/widget/AbsListView.java256
-rw-r--r--core/java/android/widget/AbsSeekBar.java11
-rw-r--r--core/java/android/widget/AbsoluteLayout.java4
-rw-r--r--core/java/android/widget/AdapterView.java7
-rw-r--r--core/java/android/widget/AlphabetIndexer.java283
-rw-r--r--core/java/android/widget/AnalogClock.java2
-rwxr-xr-xcore/java/android/widget/AppSecurityPermissions.java347
-rw-r--r--core/java/android/widget/ArrayAdapter.java6
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java441
-rw-r--r--core/java/android/widget/BaseExpandableListAdapter.java4
-rw-r--r--core/java/android/widget/Chronometer.java2
-rw-r--r--core/java/android/widget/CursorAdapter.java5
-rw-r--r--core/java/android/widget/CursorTreeAdapter.java7
-rw-r--r--core/java/android/widget/DatePicker.java2
-rw-r--r--core/java/android/widget/DigitalClock.java10
-rw-r--r--core/java/android/widget/EditText.java10
-rw-r--r--core/java/android/widget/ExpandableListConnector.java330
-rw-r--r--core/java/android/widget/ExpandableListPosition.java77
-rw-r--r--core/java/android/widget/ExpandableListView.java107
-rw-r--r--core/java/android/widget/FastScroller.java471
-rw-r--r--core/java/android/widget/Gallery.java49
-rw-r--r--core/java/android/widget/GridView.java28
-rw-r--r--core/java/android/widget/LinearLayout.java14
-rw-r--r--core/java/android/widget/ListView.java151
-rw-r--r--core/java/android/widget/MediaController.java2
-rw-r--r--core/java/android/widget/PopupWindow.java488
-rw-r--r--core/java/android/widget/ProgressBar.java98
-rw-r--r--core/java/android/widget/ScrollBarDrawable.java5
-rw-r--r--core/java/android/widget/ScrollView.java43
-rw-r--r--core/java/android/widget/SectionIndexer.java52
-rw-r--r--core/java/android/widget/SimpleAdapter.java24
-rw-r--r--core/java/android/widget/SimpleCursorAdapter.java44
-rw-r--r--core/java/android/widget/TableLayout.java14
-rw-r--r--core/java/android/widget/TextView.java1497
-rw-r--r--core/java/android/widget/VideoView.java35
-rw-r--r--core/java/com/android/internal/app/AlertController.java333
-rw-r--r--[-rwxr-xr-x]core/java/com/android/internal/app/IBatteryStats.aidl5
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java197
-rw-r--r--core/java/com/android/internal/app/RingtonePickerActivity.java3
-rw-r--r--core/java/com/android/internal/app/UsbStorageActivity.java4
-rw-r--r--core/java/com/android/internal/database/ArrayListCursor.java122
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.aidl19
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java1661
-rw-r--r--core/java/com/android/internal/os/HandlerCaller.java166
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java14
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java22
-rw-r--r--core/java/com/android/internal/provider/Settings.java199
-rw-r--r--core/java/com/android/internal/util/FastXmlSerializer.java4
-rw-r--r--core/java/com/android/internal/view/IInputConnectionCallback.aidl31
-rw-r--r--core/java/com/android/internal/view/IInputConnectionWrapper.java252
-rw-r--r--core/java/com/android/internal/view/IInputContext.aidl58
-rw-r--r--core/java/com/android/internal/view/IInputContextCallback.aidl29
-rw-r--r--core/java/com/android/internal/view/IInputMethod.aidl54
-rw-r--r--core/java/com/android/internal/view/IInputMethodCallback.aidl34
-rw-r--r--core/java/com/android/internal/view/IInputMethodClient.aidl30
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl51
-rw-r--r--core/java/com/android/internal/view/IInputMethodSession.aidl48
-rw-r--r--core/java/com/android/internal/view/InputBindResult.aidl19
-rw-r--r--core/java/com/android/internal/view/InputBindResult.java91
-rw-r--r--core/java/com/android/internal/view/InputConnectionWrapper.java306
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuItemView.java10
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuView.java235
-rw-r--r--core/java/com/android/internal/view/menu/MenuBuilder.java21
-rw-r--r--core/java/com/android/internal/view/menu/MenuDialogHelper.java3
-rw-r--r--core/java/com/android/internal/view/menu/MenuItemImpl.java3
-rw-r--r--core/java/com/android/internal/view/package.html3
-rw-r--r--core/java/com/android/internal/widget/DialogTitle.java71
-rw-r--r--core/java/com/android/internal/widget/EditableInputConnection.java338
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java14
-rw-r--r--core/java/com/android/internal/widget/LockPatternView.java41
-rw-r--r--core/java/com/android/internal/widget/NumberPicker.java15
-rw-r--r--core/java/com/android/internal/widget/SlidingDrawer.java28
-rw-r--r--core/java/com/google/android/mms/pdu/PduComposer.java5
-rw-r--r--core/java/com/google/android/mms/pdu/PduHeaders.java7
-rw-r--r--core/java/com/google/android/mms/pdu/PduPersister.java25
-rw-r--r--core/java/com/google/android/mms/pdu/SendReq.java2
-rw-r--r--core/java/com/google/android/net/ParentalControl.java23
-rw-r--r--core/java/com/google/android/util/GoogleWebContentHelper.java52
-rw-r--r--core/java/jarjar-rules.txt2
380 files changed, 30096 insertions, 10483 deletions
diff --git a/core/java/android/accounts/AccountMonitor.java b/core/java/android/accounts/AccountMonitor.java
index 9bcc1e7..f21385e 100644
--- a/core/java/android/accounts/AccountMonitor.java
+++ b/core/java/android/accounts/AccountMonitor.java
@@ -42,31 +42,42 @@ public class AccountMonitor extends BroadcastReceiver implements ServiceConnecti
private final Context mContext;
private final AccountMonitorListener mListener;
private boolean mClosed = false;
+ private int pending = 0;
// This thread runs in the background and runs the code to update accounts
// in the listener.
private class AccountUpdater extends Thread {
private IBinder mService;
-
+
public AccountUpdater(IBinder service) {
mService = service;
}
-
+
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
IAccountsService accountsService = IAccountsService.Stub.asInterface(mService);
- String[] accounts;
- try {
- accounts = accountsService.getAccounts();
- } catch (RemoteException e) {
- // if the service was killed then the system will restart it and when it does we
- // will get another onServiceConnected, at which point we will do a notify.
- Log.w("AccountMonitor", "Remote exception when getting accounts", e);
- return;
- }
+ String[] accounts = null;
+ do {
+ try {
+ accounts = accountsService.getAccounts();
+ } catch (RemoteException e) {
+ // if the service was killed then the system will restart it and when it does we
+ // will get another onServiceConnected, at which point we will do a notify.
+ Log.w("AccountMonitor", "Remote exception when getting accounts", e);
+ return;
+ }
+
+ synchronized (AccountMonitor.this) {
+ --pending;
+ if (pending == 0) {
+ break;
+ }
+ }
+ } while (true);
+
mContext.unbindService(AccountMonitor.this);
-
+
try {
mListener.onAccountsUpdated(accounts);
} catch (SQLException e) {
@@ -76,7 +87,7 @@ public class AccountMonitor extends BroadcastReceiver implements ServiceConnecti
}
}
}
-
+
/**
* Initializes the AccountMonitor and initiates a bind to the
* AccountsService to get the initial account list. For 1.0,
@@ -93,7 +104,7 @@ public class AccountMonitor extends BroadcastReceiver implements ServiceConnecti
mContext = context;
mListener = listener;
- // Register an intent receiver to monitor account changes
+ // Register a broadcast receiver to monitor account changes
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(AccountsServiceConstants.LOGIN_ACCOUNTS_CHANGED_ACTION);
intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); // To recover from disk-full.
@@ -116,14 +127,27 @@ public class AccountMonitor extends BroadcastReceiver implements ServiceConnecti
public void onServiceDisconnected(ComponentName className) {
}
- private void notifyListener() {
- // initiate the bind
- if (!mContext.bindService(AccountsServiceConstants.SERVICE_INTENT, this,
- Context.BIND_AUTO_CREATE)) {
- // This is normal if GLS isn't part of this build.
- Log.w("AccountMonitor",
- "Couldn't connect to the accounts service (Missing service?)");
+ private synchronized void notifyListener() {
+ if (pending == 0) {
+ // initiate the bind
+ if (!mContext.bindService(AccountsServiceConstants.SERVICE_INTENT,
+ this, Context.BIND_AUTO_CREATE)) {
+ // This is normal if GLS isn't part of this build.
+ Log.w("AccountMonitor",
+ "Couldn't connect to " +
+ AccountsServiceConstants.SERVICE_INTENT +
+ " (Missing service?)");
+ }
+ } else {
+ // already bound. bindService will not trigger another
+ // call to onServiceConnected, so instead we make sure
+ // that the existing background thread will call
+ // getAccounts() after this function returns, by
+ // incrementing pending.
+ //
+ // Yes, this else clause contains only a comment.
}
+ ++pending;
}
/**
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index fa310a5..eafb048 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -62,6 +62,7 @@ import android.widget.AdapterView;
import com.android.internal.policy.PolicyManager;
import java.util.ArrayList;
+import java.util.HashMap;
/**
* An activity is a single, focused thing that the user can do. Almost all
@@ -613,7 +614,8 @@ public class Activity extends ContextThemeWrapper
private ComponentName mComponent;
/*package*/ ActivityInfo mActivityInfo;
/*package*/ ActivityThread mMainThread;
- private Object mLastNonConfigurationInstance;
+ /*package*/ Object mLastNonConfigurationInstance;
+ /*package*/ HashMap<String,Object> mLastNonConfigurationChildInstances;
Activity mParent;
boolean mCalled;
private boolean mResumed;
@@ -1379,6 +1381,38 @@ public class Activity extends ContextThemeWrapper
return null;
}
+ /**
+ * Retrieve the non-configuration instance data that was previously
+ * returned by {@link #onRetainNonConfigurationChildInstances()}. This will
+ * be available from the initial {@link #onCreate} and
+ * {@link #onStart} calls to the new instance, allowing you to extract
+ * any useful dynamic state from the previous instance.
+ *
+ * <p>Note that the data you retrieve here should <em>only</em> be used
+ * as an optimization for handling configuration changes. You should always
+ * be able to handle getting a null pointer back, and an activity must
+ * still be able to restore itself to its previous state (through the
+ * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
+ * function returns null.
+ *
+ * @return Returns the object previously returned by
+ * {@link #onRetainNonConfigurationChildInstances()}
+ */
+ HashMap<String,Object> getLastNonConfigurationChildInstances() {
+ return mLastNonConfigurationChildInstances;
+ }
+
+ /**
+ * This method is similar to {@link #onRetainNonConfigurationInstance()} except that
+ * it should return either a mapping from child activity id strings to arbitrary objects,
+ * or null. This method is intended to be used by Activity framework subclasses that control a
+ * set of child activities, such as ActivityGroup. The same guarantees and restrictions apply
+ * as for {@link #onRetainNonConfigurationInstance()}. The default implementation returns null.
+ */
+ HashMap<String,Object> onRetainNonConfigurationChildInstances() {
+ return null;
+ }
+
public void onLowMemory() {
mCalled = true;
}
@@ -1837,13 +1871,50 @@ public class Activity extends ContextThemeWrapper
* Called when the current {@link Window} of the activity gains or loses
* focus. This is the best indicator of whether this activity is visible
* to the user.
+ *
+ * <p>Note that this provides information what global focus state, which
+ * is managed independently of activity lifecycles. As such, while focus
+ * changes will generally have some relation to lifecycle changes (an
+ * activity that is stopped will not generally get window focus), you
+ * should not rely on any particular order between the callbacks here and
+ * those in the other lifecycle methods such as {@link #onResume}.
+ *
+ * <p>As a general rule, however, a resumed activity will have window
+ * focus... unless it has displayed other dialogs or popups that take
+ * input focus, in which case the activity itself will not have focus
+ * when the other windows have it. Likewise, the system may display
+ * system-level windows (such as the status bar notification panel or
+ * a system alert) which will temporarily take window input focus without
+ * pausing the foreground activity.
*
* @param hasFocus Whether the window of this activity has focus.
+ *
+ * @see #hasWindowFocus()
+ * @see #onResume
*/
public void onWindowFocusChanged(boolean hasFocus) {
}
/**
+ * Returns true if this activity's <em>main</em> window currently has window focus.
+ * Note that this is not the same as the view itself having focus.
+ *
+ * @return True if this activity's main window currently has window focus.
+ *
+ * @see #onWindowAttributesChanged(android.view.WindowManager.LayoutParams)
+ */
+ public boolean hasWindowFocus() {
+ Window w = getWindow();
+ if (w != null) {
+ View d = w.getDecorView();
+ if (d != null) {
+ return d.hasWindowFocus();
+ }
+ }
+ return false;
+ }
+
+ /**
* Called to process key events. You can override this to intercept all
* key events before they are dispatched to the window. Be sure to call
* this implementation for key events that should be handled normally.
@@ -2160,6 +2231,15 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Programmatically closes the most recently opened context menu, if showing.
+ *
+ * @hide pending API council
+ */
+ public void closeContextMenu() {
+ mWindow.closePanel(Window.FEATURE_CONTEXT_MENU);
+ }
+
+ /**
* This hook is called whenever an item in a context menu is selected. The
* default implementation simply returns false to have the normal processing
* happen (calling the item's Runnable or sending a message to its Handler
@@ -2910,6 +2990,7 @@ public class Activity extends ContextThemeWrapper
* @param flags May be {@link PendingIntent#FLAG_ONE_SHOT PendingIntent.FLAG_ONE_SHOT},
* {@link PendingIntent#FLAG_NO_CREATE PendingIntent.FLAG_NO_CREATE},
* {@link PendingIntent#FLAG_CANCEL_CURRENT PendingIntent.FLAG_CANCEL_CURRENT},
+ * {@link PendingIntent#FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT},
* or any of the flags as supported by
* {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
* of the intent that can be supplied when the actual send happens.
@@ -3285,10 +3366,21 @@ public class Activity extends ContextThemeWrapper
Application application, Intent intent, ActivityInfo info, CharSequence title,
Activity parent, String id, Object lastNonConfigurationInstance,
Configuration config) {
+ attach(context, aThread, instr, token, application, intent, info, title, parent, id,
+ lastNonConfigurationInstance, null, config);
+ }
+
+ final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token,
+ Application application, Intent intent, ActivityInfo info, CharSequence title,
+ Activity parent, String id, Object lastNonConfigurationInstance,
+ HashMap<String,Object> lastNonConfigurationChildInstances, Configuration config) {
attachBaseContext(context);
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
+ if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
+ mWindow.setSoftInputMode(info.softInputMode);
+ }
mUiThread = Thread.currentThread();
mMainThread = aThread;
@@ -3302,6 +3394,7 @@ public class Activity extends ContextThemeWrapper
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstance = lastNonConfigurationInstance;
+ mLastNonConfigurationChildInstances = lastNonConfigurationChildInstances;
mWindow.setWindowManager(null, mToken, mComponent.flattenToString());
if (mParent != null) {
@@ -3375,6 +3468,10 @@ public class Activity extends ContextThemeWrapper
}
}
+ final void performPause() {
+ onPause();
+ }
+
final void performStop() {
if (!mStopped) {
if (mWindow != null) {
diff --git a/core/java/android/app/ActivityGroup.java b/core/java/android/app/ActivityGroup.java
index 96bb475..f1216f9 100644
--- a/core/java/android/app/ActivityGroup.java
+++ b/core/java/android/app/ActivityGroup.java
@@ -16,14 +16,19 @@
package android.app;
+import java.util.HashMap;
+
import android.content.Intent;
import android.os.Bundle;
+import android.util.Log;
/**
* A screen that contains and runs multiple embedded activities.
*/
public class ActivityGroup extends Activity {
+ private static final String TAG = "ActivityGroup";
private static final String STATES_KEY = "android:states";
+ static final String PARENT_NON_CONFIG_INSTANCE_KEY = "android:parent_non_config_instance";
/**
* This field should be made private, so it is hidden from the SDK.
@@ -80,6 +85,17 @@ public class ActivityGroup extends Activity {
mLocalActivityManager.dispatchDestroy(isFinishing());
}
+ /**
+ * Returns a HashMap mapping from child activity ids to the return values
+ * from calls to their onRetainNonConfigurationInstance methods.
+ *
+ * {@hide}
+ */
+ @Override
+ public HashMap<String,Object> onRetainNonConfigurationChildInstances() {
+ return mLocalActivityManager.dispatchRetainNonConfigurationInstance();
+ }
+
public Activity getCurrentActivity() {
return mLocalActivityManager.getCurrentActivity();
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 6eb1102..f9b9221 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -29,7 +29,6 @@ import android.os.Parcelable;
import android.os.Parcelable.Creator;
import android.text.TextUtils;
import android.util.Log;
-
import java.util.List;
/**
@@ -601,4 +600,75 @@ public class ActivityManager {
return null;
}
}
+
+ /**
+ * Information you can retrieve about a running process.
+ */
+ public static class RunningAppProcessInfo implements Parcelable {
+ /**
+ * The name of the process that this object is associated with
+ */
+ public String processName;
+
+ /**
+ * The pid of this process; 0 if none
+ */
+ public int pid;
+
+ public String pkgList[];
+
+ public RunningAppProcessInfo() {
+ }
+
+ public RunningAppProcessInfo(String pProcessName, int pPid, String pArr[]) {
+ processName = pProcessName;
+ pid = pPid;
+ pkgList = pArr;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(processName);
+ dest.writeInt(pid);
+ dest.writeStringArray(pkgList);
+ }
+
+ public void readFromParcel(Parcel source) {
+ processName = source.readString();
+ pid = source.readInt();
+ pkgList = source.readStringArray();
+ }
+
+ public static final Creator<RunningAppProcessInfo> CREATOR =
+ new Creator<RunningAppProcessInfo>() {
+ public RunningAppProcessInfo createFromParcel(Parcel source) {
+ return new RunningAppProcessInfo(source);
+ }
+ public RunningAppProcessInfo[] newArray(int size) {
+ return new RunningAppProcessInfo[size];
+ }
+ };
+
+ private RunningAppProcessInfo(Parcel source) {
+ readFromParcel(source);
+ }
+ }
+
+ /**
+ * Returns a list of application processes that are running on the device.
+ *
+ * @return Returns a list of RunningAppProcessInfo records, or null if there are no
+ * running processes (it will not return an empty list). This list ordering is not
+ * specified.
+ */
+ public List<RunningAppProcessInfo> getRunningAppProcesses() {
+ try {
+ return ActivityManagerNative.getDefault().getRunningAppProcesses();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index e6f1b05..ae9f3bf 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -387,6 +387,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeTypedList(list);
return true;
}
+
+ case GET_RUNNING_APP_PROCESSES_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ List<ActivityManager.RunningAppProcessInfo> list = getRunningAppProcesses();
+ reply.writeNoException();
+ reply.writeTypedList(list);
+ return true;
+ }
case MOVE_TASK_TO_FRONT_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
@@ -1314,6 +1322,19 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return list;
}
+ public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses()
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(GET_RUNNING_APP_PROCESSES_TRANSACTION, data, reply, 0);
+ reply.readException();
+ ArrayList<ActivityManager.RunningAppProcessInfo> list
+ = reply.createTypedArrayList(ActivityManager.RunningAppProcessInfo.CREATOR);
+ data.recycle();
+ reply.recycle();
+ return list;
+ }
public void moveTaskToFront(int task) throws RemoteException
{
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d03a76f..3d448a6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -220,7 +220,8 @@ public final class ActivityThread {
mApplicationInfo = aInfo;
mPackageName = aInfo.packageName;
mAppDir = aInfo.sourceDir;
- mResDir = aInfo.publicSourceDir;
+ mResDir = aInfo.uid == Process.myUid() ? aInfo.sourceDir
+ : aInfo.publicSourceDir;
mSharedLibraries = aInfo.sharedLibraryFiles;
mDataDir = aInfo.dataDir;
mDataDirFile = mDataDir != null ? new File(mDataDir) : null;
@@ -1057,6 +1058,7 @@ public final class ActivityThread {
Activity parent;
String embeddedID;
Object lastNonConfigurationInstance;
+ HashMap<String,Object> lastNonConfigurationChildInstances;
boolean paused;
boolean stopped;
boolean hideForNow;
@@ -1966,6 +1968,12 @@ public final class ActivityThread {
public final Activity startActivityNow(Activity parent, String id,
Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state) {
+ return startActivityNow(parent, id, intent, activityInfo, token, state, null);
+ }
+
+ public final Activity startActivityNow(Activity parent, String id,
+ Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
+ Object lastNonConfigurationInstance) {
ActivityRecord r = new ActivityRecord();
r.token = token;
r.intent = intent;
@@ -1973,6 +1981,7 @@ public final class ActivityThread {
r.parent = parent;
r.embeddedID = id;
r.activityInfo = activityInfo;
+ r.lastNonConfigurationInstance = lastNonConfigurationInstance;
if (localLOGV) {
ComponentName compname = intent.getComponent();
String name;
@@ -2090,9 +2099,11 @@ public final class ActivityThread {
Configuration config = new Configuration(mConfiguration);
activity.attach(appContext, this, getInstrumentation(), r.token, app,
r.intent, r.activityInfo, title, r.parent, r.embeddedID,
- r.lastNonConfigurationInstance, config);
+ r.lastNonConfigurationInstance, r.lastNonConfigurationChildInstances,
+ config);
r.lastNonConfigurationInstance = null;
+ r.lastNonConfigurationChildInstances = null;
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
@@ -2948,6 +2959,17 @@ public final class ActivityThread {
+ ": " + e.toString(), e);
}
}
+ try {
+ r.lastNonConfigurationChildInstances
+ = r.activity.onRetainNonConfigurationChildInstances();
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(r.activity, e)) {
+ throw new RuntimeException(
+ "Unable to retain child activities "
+ + r.intent.getComponent().toShortString()
+ + ": " + e.toString(), e);
+ }
+ }
}
try {
@@ -3225,11 +3247,7 @@ public final class ActivityThread {
Locale.setDefault(config.locale);
}
- if (mSystemContext != null) {
- mSystemContext.getResources().updateConfiguration(config, null);
- //Log.i(TAG, "Updated system resources " + mSystemContext.getResources()
- // + ": " + mSystemContext.getResources().getConfiguration());
- }
+ Resources.updateSystemConfiguration(config, null);
ApplicationContext.ApplicationPackageManager.configurationChanged();
//Log.i(TAG, "Configuration changed in " + currentPackageName());
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index 35c6ac1..b4c0e31 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -71,17 +71,13 @@ public class AlarmManager
*/
public static final int ELAPSED_REALTIME = 3;
- private static IAlarmManager mService;
+ private final IAlarmManager mService;
- static {
- mService = IAlarmManager.Stub.asInterface(
- ServiceManager.getService(Context.ALARM_SERVICE));
- }
-
/**
* package private on purpose
*/
- AlarmManager() {
+ AlarmManager(IAlarmManager service) {
+ mService = service;
}
/**
@@ -97,7 +93,7 @@ public class AlarmManager
* this one.
*
* <p>
- * The alarm is an intent broadcast that goes to an intent receiver that
+ * The alarm is an intent broadcast that goes to a broadcast receiver that
* you registered with {@link android.content.Context#registerReceiver}
* or through the &lt;receiver&gt; tag in an AndroidManifest.xml file.
*
@@ -189,6 +185,72 @@ public class AlarmManager
}
/**
+ * Available inexact recurrence intervals recognized by
+ * {@link #setInexactRepeating(int, long, long, PendingIntent)}
+ */
+ public static final long INTERVAL_FIFTEEN_MINUTES = 15 * 60 * 1000;
+ public static final long INTERVAL_HALF_HOUR = 2*INTERVAL_FIFTEEN_MINUTES;
+ public static final long INTERVAL_HOUR = 2*INTERVAL_HALF_HOUR;
+ public static final long INTERVAL_HALF_DAY = 12*INTERVAL_HOUR;
+ public static final long INTERVAL_DAY = 2*INTERVAL_HALF_DAY;
+
+ /**
+ * Schedule a repeating alarm that has inexact trigger time requirements;
+ * for example, an alarm that repeats every hour, but not necessarily at
+ * the top of every hour. These alarms are more power-efficient than
+ * the strict recurrences supplied by {@link #setRepeating}, since the
+ * system can adjust alarms' phase to cause them to fire simultaneously,
+ * avoiding waking the device from sleep more than necessary.
+ *
+ * <p>Your alarm's first trigger will not be before the requested time,
+ * but it might not occur for almost a full interval after that time. In
+ * addition, while the overall period of the repeating alarm will be as
+ * requested, the time between any two successive firings of the alarm
+ * may vary. If your application demands very low jitter, use
+ * {@link #setRepeating} instead.
+ *
+ * @param type One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP}, RTC or
+ * RTC_WAKEUP.
+ * @param triggerAtTime Time the alarm should first go off, using the
+ * appropriate clock (depending on the alarm type). This
+ * is inexact: the alarm will not fire before this time,
+ * but there may be a delay of almost an entire alarm
+ * interval before the first invocation of the alarm.
+ * @param interval Interval between subsequent repeats of the alarm. If
+ * this is one of INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR,
+ * INTERVAL_HOUR, INTERVAL_HALF_DAY, or INTERVAL_DAY then the
+ * alarm will be phase-aligned with other alarms to reduce
+ * the number of wakeups. Otherwise, the alarm will be set
+ * as though the application had called {@link #setRepeating}.
+ * @param operation Action to perform when the alarm goes off;
+ * typically comes from {@link PendingIntent#getBroadcast
+ * IntentSender.getBroadcast()}.
+ *
+ * @see android.os.Handler
+ * @see #set
+ * @see #cancel
+ * @see android.content.Context#sendBroadcast
+ * @see android.content.Context#registerReceiver
+ * @see android.content.Intent#filterEquals
+ * @see #ELAPSED_REALTIME
+ * @see #ELAPSED_REALTIME_WAKEUP
+ * @see #RTC
+ * @see #RTC_WAKEUP
+ * @see #INTERVAL_FIFTEEN_MINUTES
+ * @see #INTERVAL_HALF_HOUR
+ * @see #INTERVAL_HOUR
+ * @see #INTERVAL_HALF_DAY
+ * @see #INTERVAL_DAY
+ */
+ public void setInexactRepeating(int type, long triggerAtTime, long interval,
+ PendingIntent operation) {
+ try {
+ mService.setInexactRepeating(type, triggerAtTime, interval, operation);
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
* Remove any alarms with a matching {@link Intent}.
* Any alarm, of any type, whose Intent matches this one (as defined by
* {@link Intent#filterEquals}), will be canceled.
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index cc80ba4..a6981a5 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -25,6 +25,7 @@ import android.os.Message;
import android.view.KeyEvent;
import android.view.View;
import android.widget.AdapterView;
+import android.widget.Button;
import android.widget.ListAdapter;
import android.widget.ListView;
@@ -59,6 +60,29 @@ public class AlertDialog extends Dialog implements DialogInterface {
setOnCancelListener(cancelListener);
mAlert = new AlertController(context, this, getWindow());
}
+
+ /**
+ * Gets one of the buttons used in the dialog.
+ * <p>
+ * If a button does not exist in the dialog, null will be returned.
+ *
+ * @param whichButton The identifier of the button that should be returned.
+ * For example, this can be
+ * {@link DialogInterface#BUTTON_POSITIVE}.
+ * @return The button from the dialog, or null if a button does not exist.
+ */
+ public Button getButton(int whichButton) {
+ return mAlert.getButton(whichButton);
+ }
+
+ /**
+ * Gets the list view used in the dialog.
+ *
+ * @return The {@link ListView} from the dialog.
+ */
+ public ListView getListView() {
+ return mAlert.getListView();
+ }
@Override
public void setTitle(CharSequence title) {
@@ -83,44 +107,115 @@ public class AlertDialog extends Dialog implements DialogInterface {
public void setView(View view) {
mAlert.setView(view);
}
+
+ /**
+ * Set the view to display in that dialog, specifying the spacing to appear around that
+ * view.
+ *
+ * @param view The view to show in the content area of the dialog
+ * @param viewSpacingLeft Extra space to appear to the left of {@code view}
+ * @param viewSpacingTop Extra space to appear above {@code view}
+ * @param viewSpacingRight Extra space to appear to the right of {@code view}
+ * @param viewSpacingBottom Extra space to appear below {@code view}
+ */
+ public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
+ int viewSpacingBottom) {
+ mAlert.setView(view, viewSpacingLeft, viewSpacingTop, viewSpacingRight, viewSpacingBottom);
+ }
- public void setButton(CharSequence text, Message msg) {
- mAlert.setButton(text, msg);
+ /**
+ * Set a message to be sent when a button is pressed.
+ *
+ * @param whichButton Which button to set the message for, can be one of
+ * {@link DialogInterface#BUTTON_POSITIVE},
+ * {@link DialogInterface#BUTTON_NEGATIVE}, or
+ * {@link DialogInterface#BUTTON_NEUTRAL}
+ * @param text The text to display in positive button.
+ * @param msg The {@link Message} to be sent when clicked.
+ */
+ public void setButton(int whichButton, CharSequence text, Message msg) {
+ mAlert.setButton(whichButton, text, null, msg);
+ }
+
+ /**
+ * Set a listener to be invoked when the positive button of the dialog is pressed.
+ *
+ * @param whichButton Which button to set the listener on, can be one of
+ * {@link DialogInterface#BUTTON_POSITIVE},
+ * {@link DialogInterface#BUTTON_NEGATIVE}, or
+ * {@link DialogInterface#BUTTON_NEUTRAL}
+ * @param text The text to display in positive button.
+ * @param listener The {@link DialogInterface.OnClickListener} to use.
+ */
+ public void setButton(int whichButton, CharSequence text, OnClickListener listener) {
+ mAlert.setButton(whichButton, text, listener, null);
}
+ /**
+ * @deprecated Use {@link #setButton(int, CharSequence, Message)} with
+ * {@link DialogInterface#BUTTON_POSITIVE}.
+ */
+ @Deprecated
+ public void setButton(CharSequence text, Message msg) {
+ setButton(BUTTON_POSITIVE, text, msg);
+ }
+
+ /**
+ * @deprecated Use {@link #setButton(int, CharSequence, Message)} with
+ * {@link DialogInterface#BUTTON_NEGATIVE}.
+ */
+ @Deprecated
public void setButton2(CharSequence text, Message msg) {
- mAlert.setButton2(text, msg);
+ setButton(BUTTON_NEGATIVE, text, msg);
}
+ /**
+ * @deprecated Use {@link #setButton(int, CharSequence, Message)} with
+ * {@link DialogInterface#BUTTON_NEUTRAL}.
+ */
+ @Deprecated
public void setButton3(CharSequence text, Message msg) {
- mAlert.setButton3(text, msg);
+ setButton(BUTTON_NEUTRAL, text, msg);
}
/**
* Set a listener to be invoked when button 1 of the dialog is pressed.
+ *
* @param text The text to display in button 1.
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ * @deprecated Use
+ * {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)}
+ * with {@link DialogInterface#BUTTON_POSITIVE}
*/
+ @Deprecated
public void setButton(CharSequence text, final OnClickListener listener) {
- mAlert.setButton(text, listener);
+ setButton(BUTTON_POSITIVE, text, listener);
}
/**
* Set a listener to be invoked when button 2 of the dialog is pressed.
* @param text The text to display in button 2.
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ * @deprecated Use
+ * {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)}
+ * with {@link DialogInterface#BUTTON_NEGATIVE}
*/
+ @Deprecated
public void setButton2(CharSequence text, final OnClickListener listener) {
- mAlert.setButton2(text, listener);
+ setButton(BUTTON_NEGATIVE, text, listener);
}
/**
* Set a listener to be invoked when button 3 of the dialog is pressed.
* @param text The text to display in button 3.
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ * @deprecated Use
+ * {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)}
+ * with {@link DialogInterface#BUTTON_POSITIVE}
*/
+ @Deprecated
public void setButton3(CharSequence text, final OnClickListener listener) {
- mAlert.setButton3(text, listener);
+ setButton(BUTTON_NEUTRAL, text, listener);
}
/**
@@ -170,6 +265,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Set the title using the given resource id.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setTitle(int titleId) {
P.mTitle = P.mContext.getText(titleId);
@@ -178,6 +275,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Set the title displayed in the {@link Dialog}.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setTitle(CharSequence title) {
P.mTitle = title;
@@ -192,6 +291,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* via the other methods.
*
* @param customTitleView The custom view to use as the title.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setCustomTitle(View customTitleView) {
P.mCustomTitleView = customTitleView;
@@ -200,6 +301,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Set the message to display using the given resource id.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setMessage(int messageId) {
P.mMessage = P.mContext.getText(messageId);
@@ -208,6 +311,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Set the message to display.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setMessage(CharSequence message) {
P.mMessage = message;
@@ -216,6 +321,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Set the resource id of the {@link Drawable} to be used in the title.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setIcon(int iconId) {
P.mIconId = iconId;
@@ -224,6 +331,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Set the {@link Drawable} to be used in the title.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setIcon(Drawable icon) {
P.mIcon = icon;
@@ -234,6 +343,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* Set a listener to be invoked when the positive button of the dialog is pressed.
* @param textId The resource id of the text to display in the positive button
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setPositiveButton(int textId, final OnClickListener listener) {
P.mPositiveButtonText = P.mContext.getText(textId);
@@ -245,6 +356,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* Set a listener to be invoked when the positive button of the dialog is pressed.
* @param text The text to display in the positive button
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
P.mPositiveButtonText = text;
@@ -256,6 +369,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* Set a listener to be invoked when the negative button of the dialog is pressed.
* @param textId The resource id of the text to display in the negative button
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setNegativeButton(int textId, final OnClickListener listener) {
P.mNegativeButtonText = P.mContext.getText(textId);
@@ -267,6 +382,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* Set a listener to be invoked when the negative button of the dialog is pressed.
* @param text The text to display in the negative button
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setNegativeButton(CharSequence text, final OnClickListener listener) {
P.mNegativeButtonText = text;
@@ -278,6 +395,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* Set a listener to be invoked when the neutral button of the dialog is pressed.
* @param textId The resource id of the text to display in the neutral button
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setNeutralButton(int textId, final OnClickListener listener) {
P.mNeutralButtonText = P.mContext.getText(textId);
@@ -289,6 +408,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* Set a listener to be invoked when the neutral button of the dialog is pressed.
* @param text The text to display in the neutral button
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setNeutralButton(CharSequence text, final OnClickListener listener) {
P.mNeutralButtonText = text;
@@ -298,6 +419,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Sets whether the dialog is cancelable or not default is true.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setCancelable(boolean cancelable) {
P.mCancelable = cancelable;
@@ -307,6 +430,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Sets the callback that will be called if the dialog is canceled.
* @see #setCancelable(boolean)
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setOnCancelListener(OnCancelListener onCancelListener) {
P.mOnCancelListener = onCancelListener;
@@ -315,6 +440,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Sets the callback that will be called if a key is dispatched to the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setOnKeyListener(OnKeyListener onKeyListener) {
P.mOnKeyListener = onKeyListener;
@@ -324,6 +451,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Set a list of items to be displayed in the dialog as the content, you will be notified of the
* selected item via the supplied listener. This should be an array type i.e. R.array.foo
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setItems(int itemsId, final OnClickListener listener) {
P.mItems = P.mContext.getResources().getTextArray(itemsId);
@@ -334,6 +463,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Set a list of items to be displayed in the dialog as the content, you will be notified of the
* selected item via the supplied listener.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setItems(CharSequence[] items, final OnClickListener listener) {
P.mItems = items;
@@ -348,6 +479,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
*
* @param adapter The {@link ListAdapter} to supply the list of items
* @param listener The listener that will be called when an item is clicked.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setAdapter(final ListAdapter adapter, final OnClickListener listener) {
P.mAdapter = adapter;
@@ -364,6 +497,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* @param listener The listener that will be called when an item is clicked.
* @param labelColumn The column name on the cursor containing the string to display
* in the label.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setCursor(final Cursor cursor, final OnClickListener listener,
String labelColumn) {
@@ -388,6 +523,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* @param listener notified when an item on the list is clicked. The dialog will not be
* dismissed when an item is clicked. It will only be dismissed if clicked on a
* button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setMultiChoiceItems(int itemsId, boolean[] checkedItems,
final OnMultiChoiceClickListener listener) {
@@ -412,6 +549,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* @param listener notified when an item on the list is clicked. The dialog will not be
* dismissed when an item is clicked. It will only be dismissed if clicked on a
* button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems,
final OnMultiChoiceClickListener listener) {
@@ -438,6 +577,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* @param listener notified when an item on the list is clicked. The dialog will not be
* dismissed when an item is clicked. It will only be dismissed if clicked on a
* button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn,
final OnMultiChoiceClickListener listener) {
@@ -461,6 +602,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* @param listener notified when an item on the list is clicked. The dialog will not be
* dismissed when an item is clicked. It will only be dismissed if clicked on a
* button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setSingleChoiceItems(int itemsId, int checkedItem,
final OnClickListener listener) {
@@ -484,6 +627,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* @param listener notified when an item on the list is clicked. The dialog will not be
* dismissed when an item is clicked. It will only be dismissed if clicked on a
* button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn,
final OnClickListener listener) {
@@ -506,6 +651,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* @param listener notified when an item on the list is clicked. The dialog will not be
* dismissed when an item is clicked. It will only be dismissed if clicked on a
* button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final OnClickListener listener) {
P.mItems = items;
@@ -526,6 +673,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* @param listener notified when an item on the list is clicked. The dialog will not be
* dismissed when an item is clicked. It will only be dismissed if clicked on a
* button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem, final OnClickListener listener) {
P.mAdapter = adapter;
@@ -540,6 +689,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
*
* @param listener The listener to be invoked.
* @see AdapterView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setOnItemSelectedListener(final AdapterView.OnItemSelectedListener listener) {
P.mOnItemSelectedListener = listener;
@@ -549,9 +700,44 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Set a custom view to be the contents of the Dialog. If the supplied view is an instance
* of a {@link ListView} the light background will be used.
+ *
+ * @param view The view to use as the contents of the Dialog.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setView(View view) {
P.mView = view;
+ P.mViewSpacingSpecified = false;
+ return this;
+ }
+
+ /**
+ * Set a custom view to be the contents of the Dialog, specifying the
+ * spacing to appear around that view. If the supplied view is an
+ * instance of a {@link ListView} the light background will be used.
+ *
+ * @param view The view to use as the contents of the Dialog.
+ * @param viewSpacingLeft Spacing between the left edge of the view and
+ * the dialog frame
+ * @param viewSpacingTop Spacing between the top edge of the view and
+ * the dialog frame
+ * @param viewSpacingRight Spacing between the right edge of the view
+ * and the dialog frame
+ * @param viewSpacingBottom Spacing between the bottom edge of the view
+ * and the dialog frame
+ * @return This Builder object to allow for chaining of calls to set
+ * methods
+ *
+ * @hide pending API review
+ */
+ public Builder setView(View view, int viewSpacingLeft, int viewSpacingTop,
+ int viewSpacingRight, int viewSpacingBottom) {
+ P.mView = view;
+ P.mViewSpacingSpecified = true;
+ P.mViewSpacingLeft = viewSpacingLeft;
+ P.mViewSpacingTop = viewSpacingTop;
+ P.mViewSpacingRight = viewSpacingRight;
+ P.mViewSpacingBottom = viewSpacingBottom;
return this;
}
@@ -560,7 +746,8 @@ public class AlertDialog extends Dialog implements DialogInterface {
* contents is.
*
* @param useInverseBackground Whether to use the inverse background
- * @return This Builder object to allow for chaining of sets.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setInverseBackgroundForced(boolean useInverseBackground) {
P.mForceInverseBackground = useInverseBackground;
diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java
index 342ffcf..0e41ae6 100644
--- a/core/java/android/app/ApplicationContext.java
+++ b/core/java/android/app/ApplicationContext.java
@@ -87,6 +87,7 @@ import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.WindowManagerImpl;
+import android.view.inputmethod.InputMethodManager;
import com.android.internal.policy.PolicyManager;
@@ -104,6 +105,8 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import org.xmlpull.v1.XmlPullParserException;
+
class ReceiverRestrictedContext extends ContextWrapper {
ReceiverRestrictedContext(Context base) {
super(base);
@@ -139,12 +142,12 @@ class ReceiverRestrictedContext extends ContextWrapper {
* Common implementation of Context API, which Activity and other application
* classes inherit.
*/
-@SuppressWarnings({"EmptyCatchBlock"})
class ApplicationContext extends Context {
private final static String TAG = "ApplicationContext";
private final static boolean DEBUG_ICONS = false;
private static final Object sSync = new Object();
+ private static AlarmManager sAlarmManager;
private static PowerManager sPowerManager;
private static ConnectivityManager sConnectivityManager;
private static WifiManager sWifiManager;
@@ -288,12 +291,15 @@ class ApplicationContext extends Context {
}
throw new RuntimeException("Not supported in system context");
}
+
+ private static File makeBackupFile(File prefsFile) {
+ return new File(prefsFile.getPath() + ".bak");
+ }
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
- File f;
- f = makeFilename(getPreferencesDir(), name + ".xml");
SharedPreferencesImpl sp;
+ File f = makeFilename(getPreferencesDir(), name + ".xml");
synchronized (sSharedPrefs) {
sp = sSharedPrefs.get(f);
if (sp != null && !sp.hasFileChanged()) {
@@ -301,15 +307,27 @@ class ApplicationContext extends Context {
return sp;
}
}
+
+ FileInputStream str = null;
+ File backup = makeBackupFile(f);
+ if (backup.exists()) {
+ f.delete();
+ backup.renameTo(f);
+ }
Map map = null;
- try {
- FileInputStream str = new FileInputStream(f);
- map = XmlUtils.readMapXml(str);
- str.close();
- } catch (org.xmlpull.v1.XmlPullParserException e) {
- } catch (java.io.FileNotFoundException e) {
- } catch (java.io.IOException e) {
+ if (f.exists()) {
+ try {
+ str = new FileInputStream(f);
+ map = XmlUtils.readMapXml(str);
+ str.close();
+ } catch (org.xmlpull.v1.XmlPullParserException e) {
+ Log.w(TAG, "getSharedPreferences", e);
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "getSharedPreferences", e);
+ } catch (IOException e) {
+ Log.w(TAG, "getSharedPreferences", e);
+ }
}
synchronized (sSharedPrefs) {
@@ -350,7 +368,7 @@ class ApplicationContext extends Context {
File f = makeFilename(getFilesDir(), name);
try {
FileOutputStream fos = new FileOutputStream(f, append);
- setFilePermissionsFromMode(f.toString(), mode, 0);
+ setFilePermissionsFromMode(f.getPath(), mode, 0);
return fos;
} catch (FileNotFoundException e) {
}
@@ -358,11 +376,11 @@ class ApplicationContext extends Context {
File parent = f.getParentFile();
parent.mkdir();
FileUtils.setPermissions(
- parent.toString(),
+ parent.getPath(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-1, -1);
FileOutputStream fos = new FileOutputStream(f, append);
- setFilePermissionsFromMode(f.toString(), mode, 0);
+ setFilePermissionsFromMode(f.getPath(), mode, 0);
return fos;
}
@@ -378,6 +396,16 @@ class ApplicationContext extends Context {
if (mFilesDir == null) {
mFilesDir = new File(getDataDirFile(), "files");
}
+ if (!mFilesDir.exists()) {
+ if(!mFilesDir.mkdirs()) {
+ Log.w(TAG, "Unable to create files directory");
+ return null;
+ }
+ FileUtils.setPermissions(
+ mFilesDir.getPath(),
+ FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+ -1, -1);
+ }
return mFilesDir;
}
}
@@ -394,7 +422,7 @@ class ApplicationContext extends Context {
return null;
}
FileUtils.setPermissions(
- mCacheDir.toString(),
+ mCacheDir.getPath(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-1, -1);
}
@@ -418,14 +446,14 @@ class ApplicationContext extends Context {
public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) {
File dir = getDatabasesDir();
if (!dir.isDirectory() && dir.mkdir()) {
- FileUtils.setPermissions(dir.toString(),
+ FileUtils.setPermissions(dir.getPath(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-1, -1);
}
File f = makeFilename(dir, name);
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f, factory);
- setFilePermissionsFromMode(f.toString(), mode, 0);
+ setFilePermissionsFromMode(f.getPath(), mode, 0);
return db;
}
@@ -844,7 +872,7 @@ class ApplicationContext extends Context {
} else if (ACTIVITY_SERVICE.equals(name)) {
return getActivityManager();
} else if (ALARM_SERVICE.equals(name)) {
- return new AlarmManager();
+ return getAlarmManager();
} else if (POWER_SERVICE.equals(name)) {
return getPowerManager();
} else if (CONNECTIVITY_SERVICE.equals(name)) {
@@ -878,6 +906,8 @@ class ApplicationContext extends Context {
return getTelephonyManager();
} else if (CLIPBOARD_SERVICE.equals(name)) {
return getClipboardManager();
+ } else if (INPUT_METHOD_SERVICE.equals(name)) {
+ return InputMethodManager.getInstance(this);
}
return null;
@@ -893,6 +923,17 @@ class ApplicationContext extends Context {
return mActivityManager;
}
+ private AlarmManager getAlarmManager() {
+ synchronized (sSync) {
+ if (sAlarmManager == null) {
+ IBinder b = ServiceManager.getService(ALARM_SERVICE);
+ IAlarmManager service = IAlarmManager.Stub.asInterface(b);
+ sAlarmManager = new AlarmManager(service);
+ }
+ }
+ return sAlarmManager;
+ }
+
private PowerManager getPowerManager() {
synchronized (sSync) {
if (sPowerManager == null) {
@@ -1299,7 +1340,7 @@ class ApplicationContext extends Context {
File file = makeFilename(getDataDirFile(), name);
if (!file.exists()) {
file.mkdir();
- setFilePermissionsFromMode(file.toString(), mode,
+ setFilePermissionsFromMode(file.getPath(), mode,
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH);
}
return file;
@@ -1636,6 +1677,20 @@ class ApplicationContext extends Context {
throw new RuntimeException("Package manager has died", e);
}
}
+
+ @Override
+ public int getUidForSharedUser(String sharedUserName)
+ throws NameNotFoundException {
+ try {
+ int uid = mPM.getUidForSharedUser(sharedUserName);
+ if(uid != -1) {
+ return uid;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+ throw new NameNotFoundException("No shared userid for user:"+sharedUserName);
+ }
@Override
public List<PackageInfo> getInstalledPackages(int flags) {
@@ -1899,7 +1954,9 @@ class ApplicationContext extends Context {
if (app.packageName.equals("system")) {
return mContext.mMainThread.getSystemContext().getResources();
}
- Resources r = mContext.mMainThread.getTopLevelResources(app.publicSourceDir);
+ Resources r = mContext.mMainThread.getTopLevelResources(
+ app.uid == Process.myUid() ? app.sourceDir
+ : app.publicSourceDir);
if (r != null) {
return r;
}
@@ -2341,6 +2398,7 @@ class ApplicationContext extends Context {
private static final class SharedPreferencesImpl implements SharedPreferences {
private final File mFile;
+ private final File mBackupFile;
private final int mMode;
private Map mMap;
private final FileStatus mFileStatus = new FileStatus();
@@ -2351,6 +2409,7 @@ class ApplicationContext extends Context {
SharedPreferencesImpl(
File file, int mode, Map initialContents) {
mFile = file;
+ mBackupFile = makeBackupFile(file);
mMode = mode;
mMap = initialContents != null ? initialContents : new HashMap();
if (FileUtils.getFileStatus(file.getPath(), mFileStatus)) {
@@ -2544,30 +2603,68 @@ class ApplicationContext extends Context {
public Editor edit() {
return new EditorImpl();
}
+
+ private FileOutputStream createFileOutputStream(File file) {
+ FileOutputStream str = null;
+ try {
+ str = new FileOutputStream(file);
+ } catch (FileNotFoundException e) {
+ File parent = file.getParentFile();
+ if (!parent.mkdir()) {
+ Log.e(TAG, "Couldn't create directory for SharedPreferences file " + file);
+ return null;
+ }
+ FileUtils.setPermissions(
+ parent.getPath(),
+ FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+ -1, -1);
+ try {
+ str = new FileOutputStream(file);
+ } catch (FileNotFoundException e2) {
+ Log.e(TAG, "Couldn't create SharedPreferences file " + file, e2);
+ }
+ }
+ return str;
+ }
private boolean writeFileLocked() {
+ // Rename the current file so it may be used as a backup during the next read
+ if (mFile.exists()) {
+ if (!mFile.renameTo(mBackupFile)) {
+ Log.e(TAG, "Couldn't rename file " + mFile + " to backup file " + mBackupFile);
+ }
+ }
+
+ // Attempt to write the file, delete the backup and return true as atomically as
+ // possible. If any exception occurs, delete the new file; next time we will restore
+ // from the backup.
try {
- FileOutputStream str;
- try {
- str = new FileOutputStream(mFile);
- } catch (Exception e) {
- File parent = mFile.getParentFile();
- parent.mkdir();
- FileUtils.setPermissions(
- parent.toString(),
- FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
- -1, -1);
- str = new FileOutputStream(mFile);
+ FileOutputStream str = createFileOutputStream(mFile);
+ if (str == null) {
+ return false;
}
XmlUtils.writeMapXml(mMap, str);
str.close();
- setFilePermissionsFromMode(mFile.toString(), mMode, 0);
+ setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
if (FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) {
mTimestamp = mFileStatus.mtime;
}
- } catch (org.xmlpull.v1.XmlPullParserException e) {
- } catch (java.io.FileNotFoundException e) {
- } catch (java.io.IOException e) {
+
+ // Writing was successful, delete the backup file
+ if (!mBackupFile.delete()) {
+ Log.e(TAG, "Couldn't delete new backup file " + mBackupFile);
+ }
+ return true;
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "writeFileLocked: Got exception:", e);
+ } catch (IOException e) {
+ Log.w(TAG, "writeFileLocked: Got exception:", e);
+ }
+ // Clean up an unsuccessfully written file
+ if (mFile.exists()) {
+ if (!mFile.delete()) {
+ Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
+ }
}
return false;
}
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index 7450559..ee5e0d5 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -20,8 +20,8 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
-import android.pim.DateFormat;
import android.text.TextUtils.TruncateAt;
+import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.DatePicker;
@@ -107,7 +107,7 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener,
DateFormatSymbols symbols = new DateFormatSymbols();
mWeekDays = symbols.getShortWeekdays();
- mDateFormat = DateFormat.getLongDateFormat(context);
+ mDateFormat = DateFormat.getMediumDateFormat(context);
mCalendar = Calendar.getInstance();
updateTitle(mInitialYear, mInitialMonth, mInitialDay);
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 2de21ed..353500e 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -213,6 +213,9 @@ public interface IActivityManager extends IInterface {
* SIGUSR1 is delivered. All others are ignored.
*/
public void signalPersistentProcesses(int signal) throws RemoteException;
+ // Retrieve running application processes in the system
+ public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses()
+ throws RemoteException;
/** Information you can retrieve about a particular application. */
public static class ContentProviderHolder implements Parcelable {
@@ -350,4 +353,5 @@ public interface IActivityManager extends IInterface {
int KILL_PIDS_FOR_MEMORY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+79;
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;
}
diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl
index c7f20b9..cb42236 100755
--- a/core/java/android/app/IAlarmManager.aidl
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -26,6 +26,7 @@ import android.app.PendingIntent;
interface IAlarmManager {
void set(int type, long triggerAtTime, in PendingIntent operation);
void setRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);
+ void setInexactRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);
void setTimeZone(String zone);
void remove(in PendingIntent operation);
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index f80c947..17618ff 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -40,6 +40,7 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.Window;
+import android.view.inputmethod.InputMethodManager;
import java.io.File;
import java.util.ArrayList;
@@ -1262,7 +1263,7 @@ public class Instrumentation {
* @param activity The activity being paused.
*/
public void callActivityOnPause(Activity activity) {
- activity.onPause();
+ activity.performPause();
}
/*
@@ -1392,8 +1393,8 @@ public class Instrumentation {
* if there was no Activity found to run the given Intent.
*
* @param who The Context from which the activity is being started.
- * @param whoThread The main thread of the Context from which the activity
- * is being started.
+ * @param contextThread The main thread of the Context from which the activity
+ * is being started.
* @param token Internal token identifying to the system who is starting
* the activity; may be null.
* @param target Which activity is perform the start (and thus receiving
@@ -1416,8 +1417,9 @@ public class Instrumentation {
* {@hide}
*/
public ActivityResult execStartActivity(
- Context who, IApplicationThread whoThread, IBinder token, Activity target,
+ Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode) {
+ IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 12e70e3..a24fcae 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -22,9 +22,6 @@ import android.os.Binder;
import android.os.Bundle;
import android.util.Config;
import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
import android.view.Window;
import java.util.ArrayList;
@@ -114,13 +111,21 @@ public class LocalActivityManager {
}
if (r.curState == INITIALIZING) {
+ // Get the lastNonConfigurationInstance for the activity
+ HashMap<String,Object> lastNonConfigurationInstances =
+ mParent.getLastNonConfigurationChildInstances();
+ Object instance = null;
+ if (lastNonConfigurationInstances != null) {
+ instance = lastNonConfigurationInstances.get(r.id);
+ }
+
// We need to have always created the activity.
if (localLOGV) Log.v(TAG, r.id + ": starting " + r.intent);
if (r.activityInfo == null) {
r.activityInfo = mActivityThread.resolveActivityInfo(r.intent);
}
r.activity = mActivityThread.startActivityNow(
- mParent, r.id, r.intent, r.activityInfo, r, r.instanceState);
+ mParent, r.id, r.intent, r.activityInfo, r, r.instanceState, instance);
if (r.activity == null) {
return;
}
@@ -288,7 +293,6 @@ public class LocalActivityManager {
// It's a brand new world.
mActivities.put(id, r);
mActivityArray.add(r);
-
} else if (r.activityInfo != null) {
// If the new activity is the same as the current one, then
// we may be able to reuse it.
@@ -568,6 +572,32 @@ public class LocalActivityManager {
moveToState(r, CREATED);
}
}
+
+ /**
+ * Call onRetainNonConfigurationInstance on each child activity and store the
+ * results in a HashMap by id. Only construct the HashMap if there is a non-null
+ * object to store. Note that this does not support nested ActivityGroups.
+ *
+ * {@hide}
+ */
+ public HashMap<String,Object> dispatchRetainNonConfigurationInstance() {
+ HashMap<String,Object> instanceMap = null;
+
+ final int N = mActivityArray.size();
+ for (int i=0; i<N; i++) {
+ LocalActivityRecord r = mActivityArray.get(i);
+ if ((r != null) && (r.activity != null)) {
+ Object instance = r.activity.onRetainNonConfigurationInstance();
+ if (instance != null) {
+ if (instanceMap == null) {
+ instanceMap = new HashMap<String,Object>();
+ }
+ instanceMap.put(r.id, instance);
+ }
+ }
+ }
+ return instanceMap;
+ }
/**
* Remove all activities from this LocalActivityManager, performing an
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index cc56385..ea67cdb 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -25,9 +25,9 @@ import android.media.AudioManager;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
-import android.pim.DateFormat;
-import android.pim.DateUtils;
import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
import android.widget.RemoteViews;
/**
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index ba84903..b59e9dc 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -73,9 +73,22 @@ public final class PendingIntent implements Parcelable {
* {@link #getService}: if the described PendingIntent already exists,
* the current one is canceled before generating a new one. You can use
* this to retrieve a new PendingIntent when you are only changing the
- * extra data in the Intent.
+ * extra data in the Intent; by canceling the previous pending intent,
+ * this ensures that only entities given the new data will be able to
+ * launch it. If this assurance is not an issue, consider
+ * {@link #FLAG_UPDATE_CURRENT}.
*/
public static final int FLAG_CANCEL_CURRENT = 1<<28;
+ /**
+ * Flag for use with {@link #getActivity}, {@link #getBroadcast}, and
+ * {@link #getService}: if the described PendingIntent already exists,
+ * then keep it but its replace its extra data with what is in this new
+ * Intent. This can be used if you are creating intents where only the
+ * extras change, and don't care that any entities that received your
+ * previous PendingIntent will be able to launch it with your new
+ * extras even if they are not explicitly given to it.
+ */
+ public static final int FLAG_UPDATE_CURRENT = 1<<27;
/**
* Exception thrown when trying to send through a PendingIntent that
@@ -161,7 +174,8 @@ public final class PendingIntent implements Parcelable {
* not used).
* @param intent Intent of the activity to be launched.
* @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE},
- * {@link #FLAG_CANCEL_CURRENT}, or any of the flags as supported by
+ * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT},
+ * or any of the flags as supported by
* {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
* of the intent that can be supplied when the actual send happens.
*
@@ -195,7 +209,8 @@ public final class PendingIntent implements Parcelable {
* not used).
* @param intent The Intent to be broadcast.
* @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE},
- * {@link #FLAG_CANCEL_CURRENT}, or any of the flags as supported by
+ * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT},
+ * or any of the flags as supported by
* {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
* of the intent that can be supplied when the actual send happens.
*
@@ -230,7 +245,8 @@ public final class PendingIntent implements Parcelable {
* not used).
* @param intent An Intent describing the service to be started.
* @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE},
- * {@link #FLAG_CANCEL_CURRENT}, or any of the flags as supported by
+ * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT},
+ * or any of the flags as supported by
* {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
* of the intent that can be supplied when the actual send happens.
*
diff --git a/core/java/android/app/ProgressDialog.java b/core/java/android/app/ProgressDialog.java
index 8b60cfa..c87e398 100644
--- a/core/java/android/app/ProgressDialog.java
+++ b/core/java/android/app/ProgressDialog.java
@@ -262,7 +262,7 @@ public class ProgressDialog extends AlertDialog {
}
public void setIndeterminate(boolean indeterminate) {
- if (mHasStarted && (isIndeterminate() != indeterminate)) {
+ if (mProgress != null) {
mProgress.setIndeterminate(indeterminate);
} else {
mIndeterminate = indeterminate;
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 5f3f9ef..2ce2db9 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;
@@ -48,10 +51,12 @@ import android.view.WindowManager;
import android.view.View.OnFocusChangeListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView;
-import android.widget.Button;
import android.widget.CursorAdapter;
import android.widget.EditText;
+import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
@@ -62,6 +67,7 @@ import android.widget.WrapperListAdapter;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
+import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
@@ -100,7 +106,7 @@ public class SearchDialog extends Dialog {
private TextView mBadgeLabel;
private LinearLayout mSearchEditLayout;
private EditText mSearchTextField;
- private Button mGoButton;
+ private ImageButton mGoButton;
private ListView mSuggestionsList;
private ViewTreeObserver mViewTreeObserver = null;
@@ -130,14 +136,14 @@ public class SearchDialog extends Dialog {
private String mSuggestionAction = null;
private Uri mSuggestionData = null;
private String mSuggestionQuery = null;
-
+
/**
* Constructor - fires it up and makes it look like the search UI.
*
* @param context Application Context we can use for system acess
*/
public SearchDialog(Context context) {
- super(context, com.android.internal.R.style.Theme_Translucent);
+ super(context, com.android.internal.R.style.Theme_SearchBar);
}
/**
@@ -149,21 +155,15 @@ public class SearchDialog extends Dialog {
super.onCreate(savedInstanceState);
Window theWindow = getWindow();
- theWindow.requestFeature(Window.FEATURE_NO_TITLE);
- theWindow.setFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND,
- WindowManager.LayoutParams.FLAG_DIM_BEHIND);
theWindow.setGravity(Gravity.TOP|Gravity.FILL_HORIZONTAL);
setContentView(com.android.internal.R.layout.search_bar);
- // Note: theWindow.setBackgroundDrawable(null) does not work here - you get blackness
- theWindow.setBackgroundDrawableResource(android.R.color.transparent);
-
theWindow.setLayout(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
WindowManager.LayoutParams lp = theWindow.getAttributes();
- lp.dimAmount = 0.5f;
lp.setTitle("Search Dialog");
+ lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;
theWindow.setAttributes(lp);
// get the view elements for local access
@@ -171,14 +171,14 @@ public class SearchDialog extends Dialog {
mBadgeLabel = (TextView) findViewById(com.android.internal.R.id.search_badge);
mSearchEditLayout = (LinearLayout)findViewById(com.android.internal.R.id.search_edit_frame);
mSearchTextField = (EditText) findViewById(com.android.internal.R.id.search_src_text);
- mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn);
+ mGoButton = (ImageButton) findViewById(com.android.internal.R.id.search_go_btn);
mSuggestionsList = (ListView) findViewById(com.android.internal.R.id.search_suggest_list);
// attach listeners
mSearchTextField.addTextChangedListener(mTextWatcher);
mSearchTextField.setOnKeyListener(mTextKeyListener);
mGoButton.setOnClickListener(mGoButtonClickListener);
- mGoButton.setOnKeyListener(mGoButtonKeyListener);
+ mGoButton.setOnKeyListener(mButtonsKeyListener);
mSuggestionsList.setOnItemClickListener(mSuggestionsListItemClickListener);
mSuggestionsList.setOnKeyListener(mSuggestionsKeyListener);
mSuggestionsList.setOnFocusChangeListener(mSuggestFocusListener);
@@ -241,6 +241,7 @@ public class SearchDialog extends Dialog {
if (mSuggestionsList != null) {
mSuggestionsList.setVisibility(View.GONE); // prevent any flicker if was visible
}
+
super.show();
setupSearchableInfo();
@@ -266,6 +267,17 @@ public class SearchDialog extends Dialog {
initialQuery = ""; // This forces the preload to happen, triggering suggestions
}
mSearchTextField.setText(initialQuery);
+
+ // If it is not for global search, that means the search dialog is
+ // launched to input a web address.
+ if (!globalSearch) {
+ mSearchTextField.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_URI);
+ } else {
+ mSearchTextField.setRawInputType(EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_NORMAL);
+ }
+
if (selectInitialQuery) {
mSearchTextField.selectAll();
} else {
@@ -424,7 +436,6 @@ public class SearchDialog extends Dialog {
public void onConfigurationChanged(Configuration newConfig) {
if (isShowing()) {
// Redraw (resources may have changed)
- updateSearchButton();
updateSearchBadge();
updateQueryHint();
}
@@ -439,7 +450,6 @@ public class SearchDialog extends Dialog {
mActivityContext = mSearchable.getActivityContext(getContext());
mProviderContext = mSearchable.getProviderContext(getContext(), mActivityContext);
- updateSearchButton();
updateSearchBadge();
updateQueryHint();
}
@@ -459,18 +469,6 @@ public class SearchDialog extends Dialog {
}
/**
- * Update the text in the search button
- */
- private void updateSearchButton() {
- int textId = mSearchable.getSearchButtonText();
- if (textId == 0) {
- textId = com.android.internal.R.string.search_go;
- }
- String goText = mActivityContext.getResources().getString(textId);
- mGoButton.setText(goText);
- }
-
- /**
* Setup the search "Badge" if request by mode flags.
*/
private void updateSearchBadge() {
@@ -1031,9 +1029,10 @@ public class SearchDialog extends Dialog {
}
/**
- * React to typing in the GO button by refocusing to EditText. Continue typing the query.
+ * React to typing in the GO search button by refocusing to EditText.
+ * Continue typing the query.
*/
- View.OnKeyListener mGoButtonKeyListener = new View.OnKeyListener() {
+ View.OnKeyListener mButtonsKeyListener = new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
// also guard against possible race conditions (late arrival after dismiss)
if (mSearchable != null) {
@@ -1054,7 +1053,7 @@ public class SearchDialog extends Dialog {
}
}
};
-
+
/**
* 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.
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 01babc4..5f25b90 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -48,6 +48,7 @@ import android.view.KeyEvent;
* <li><a href="#ActionKeys">Action Keys</a>
* <li><a href="#SearchabilityMetadata">Searchability Metadata</a>
* <li><a href="#PassingSearchContext">Passing Search Context</a>
+ * <li><a href="#ProtectingUserPrivacy">Protecting User Privacy</a>
* </ol>
*
* <a name="DeveloperGuide"></a>
@@ -578,7 +579,7 @@ import android.view.KeyEvent;
* file. Each element defines one of the keycodes you are interested in,
* defines the conditions under which they are sent, and provides details
* on how to communicate the action key event back to your searchable activity.</li>
- * <li>In your intent receiver, if you wish, you can check for action keys by checking the
+ * <li>In your broadcast receiver, if you wish, you can check for action keys by checking the
* extras field of the {@link android.content.Intent Intent}.</li>
* </ul>
*
@@ -974,6 +975,36 @@ import android.view.KeyEvent;
* appData.get...();
* appData.get...();
* }</pre>
+ *
+ * <a name="ProtectingUserPrivacy"></a>
+ * <h3>Protecting User Privacy</h3>
+ *
+ * <p>Many users consider their activities on the phone, including searches, to be private
+ * information. Applications that implement search should take steps to protect users' privacy
+ * wherever possible. This section covers two areas of concern, but you should consider your search
+ * design carefully and take any additional steps necessary.
+ *
+ * <p><b>Don't send personal information to servers, and if you do, don't log it.</b>
+ * "Personal information" is information that can personally identify your users, such as name,
+ * email address or billing information, or other data which can be reasonably linked to such
+ * information. If your application implements search with the assistance of a server, try to
+ * avoid sending personal information with your searches. For example, if you are searching for
+ * businesses near a zip code, you don't need to send the user ID as well - just send the zip code
+ * to the server. If you do need to send personal information, you should take steps to avoid
+ * logging it. If you must log it, you should protect that data very carefully, and erase it as
+ * soon as possible.
+ *
+ * <p><b>Provide the user with a way to clear their search history.</b> The Search Manager helps
+ * your application provide context-specific suggestions. Sometimes these suggestions are based
+ * on previous searches, or other actions taken by the user in an earlier session. A user may not
+ * wish for previous searches to be revealed to other users, for instance if they share their phone
+ * with a friend. If your application provides suggestions that can reveal previous activities,
+ * you should implement a "Clear History" menu, preference, or button. If you are using
+ * {@link android.provider.SearchRecentSuggestions}, you can simply call its
+ * {@link android.provider.SearchRecentSuggestions#clearHistory() clearHistory()} method from
+ * your "Clear History" UI. If you are implementing your own form of recent suggestions, you'll
+ * need to provide a similar a "clear history" API in your provider, and call it from your
+ * "Clear History" UI.
*/
public class SearchManager
implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener
@@ -1008,6 +1039,16 @@ public class SearchManager
* activity that launched the search.
*/
public final static String APP_DATA = "app_data";
+
+ /**
+ * Intent app_data bundle key: Use this key with the bundle from
+ * {@link android.content.Intent#getBundleExtra
+ * content.Intent.getBundleExtra(APP_DATA)} to obtain the source identifier
+ * set by the activity that launched the search.
+ *
+ * @hide
+ */
+ public final static String SOURCE = "source";
/**
* Intent extra data key: Use this key with Intent.ACTION_SEARCH and
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 28b0615..6c08e75 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -163,7 +163,6 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
/**
* Called by the system when the service is first created. Do not call this method directly.
- * If you override this method, be sure to call super.onCreate().
*/
public void onCreate() {
}
@@ -172,7 +171,6 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
* Called by the system every time a client explicitly starts the service by calling
* {@link android.content.Context#startService}, providing the arguments it supplied and a
* unique integer token representing the start request. Do not call this method directly.
- * If you override this method, be sure to call super.onStart().
*
* @param intent The Intent supplied to {@link android.content.Context#startService},
* as given.
@@ -189,7 +187,6 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
* service should clean up an resources it holds (threads, registered
* receivers, etc) at this point. Upon return, there will be no more calls
* in to this Service object and it is effectively dead. Do not call this method directly.
- * If you override this method, be sure to call super.onDestroy().
*/
public void onDestroy() {
}
@@ -375,4 +372,3 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
private Application mApplication = null;
private IActivityManager mActivityManager = null;
}
-
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index 107532e..002b01f 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -20,7 +20,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
-import android.pim.DateFormat;
+import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TimePicker;
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
new file mode 100644
index 0000000..d6ea889
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -0,0 +1,181 @@
+/*
+ * 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.bluetooth;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.server.BluetoothA2dpService;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Public API for controlling the Bluetooth A2DP Profile Service.
+ *
+ * BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
+ * Service via IPC.
+ *
+ * Creating a BluetoothA2dp object will initiate a binding with the
+ * BluetoothHeadset service. Users of this object should call close() when they
+ * are finished, so that this proxy object can unbind from the service.
+ *
+ * Currently the BluetoothA2dp service runs in the system server and this
+ * proxy object will be immediately bound to the service on construction.
+ * However this may change in future releases, and error codes such as
+ * BluetoothError.ERROR_IPC_NOT_READY will be returned from this API when the
+ * proxy object is not yet attached.
+ *
+ * Currently this class provides methods to connect to A2DP audio sinks.
+ *
+ * @hide
+ */
+public class BluetoothA2dp {
+ private static final String TAG = "BluetoothA2dp";
+
+ /** int extra for SINK_STATE_CHANGED_ACTION */
+ public static final String SINK_STATE =
+ "android.bluetooth.a2dp.intent.SINK_STATE";
+ /** int extra for SINK_STATE_CHANGED_ACTION */
+ public static final String SINK_PREVIOUS_STATE =
+ "android.bluetooth.a2dp.intent.SINK_PREVIOUS_STATE";
+
+ /** Indicates the state of an A2DP audio sink has changed.
+ * This intent will always contain SINK_STATE, SINK_PREVIOUS_STATE and
+ * BluetoothIntent.ADDRESS extras.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SINK_STATE_CHANGED_ACTION =
+ "android.bluetooth.a2dp.intent.action.SINK_STATE_CHANGED";
+
+ public static final int STATE_DISCONNECTED = 0;
+ public static final int STATE_CONNECTING = 1;
+ public static final int STATE_CONNECTED = 2;
+ public static final int STATE_DISCONNECTING = 3;
+ /** Playing implies connected */
+ public static final int STATE_PLAYING = 4;
+
+ private final IBluetoothA2dp mService;
+ private final Context mContext;
+
+ /**
+ * Create a BluetoothA2dp proxy object for interacting with the local
+ * Bluetooth A2DP service.
+ * @param c Context
+ */
+ public BluetoothA2dp(Context c) {
+ mContext = c;
+ IBinder b = ServiceManager.getService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE);
+ if (b == null) {
+ throw new RuntimeException("Bluetooth A2DP service not available!");
+ }
+ mService = IBluetoothA2dp.Stub.asInterface(b);
+ }
+
+ /** Initiate a connection to an A2DP sink.
+ * Listen for A2DP_SINK_STATE_CHANGED_ACTION to find out when the
+ * connection is completed.
+ * @param address Remote BT address.
+ * @return Result code, negative indicates an immediate error.
+ * @hide
+ */
+ public int connectSink(String address) {
+ try {
+ return mService.connectSink(address);
+ } catch (RemoteException e) {
+ Log.w(TAG, "", e);
+ return BluetoothError.ERROR_IPC;
+ }
+ }
+
+ /** Initiate disconnect from an A2DP sink.
+ * Listen for A2DP_SINK_STATE_CHANGED_ACTION to find out when
+ * disconnect is completed.
+ * @param address Remote BT address.
+ * @return Result code, negative indicates an immediate error.
+ * @hide
+ */
+ public int disconnectSink(String address) {
+ try {
+ return mService.disconnectSink(address);
+ } catch (RemoteException e) {
+ Log.w(TAG, "", e);
+ return BluetoothError.ERROR_IPC;
+ }
+ }
+
+ /** Check if a specified A2DP sink is connected.
+ * @param address Remote BT address.
+ * @return True if connected (or playing), false otherwise and on error.
+ * @hide
+ */
+ public boolean isSinkConnected(String address) {
+ int state = getSinkState(address);
+ return state == STATE_CONNECTED || state == STATE_PLAYING;
+ }
+
+ /** Check if any A2DP sink is connected.
+ * @return a List of connected A2DP sinks, or null on error.
+ * @hide
+ */
+ public List<String> listConnectedSinks() {
+ try {
+ return mService.listConnectedSinks();
+ } catch (RemoteException e) {
+ Log.w(TAG, "", e);
+ return null;
+ }
+ }
+
+ /** Get the state of an A2DP sink
+ * @param address Remote BT address.
+ * @return State code, or negative on error
+ * @hide
+ */
+ public int getSinkState(String address) {
+ try {
+ return mService.getSinkState(address);
+ } catch (RemoteException e) {
+ Log.w(TAG, "", e);
+ return BluetoothError.ERROR_IPC;
+ }
+ }
+
+ /** Helper for converting a state to a string.
+ * For debug use only - strings are not internationalized.
+ * @hide
+ */
+ public static String stateToString(int state) {
+ switch (state) {
+ case STATE_DISCONNECTED:
+ return "disconnected";
+ case STATE_CONNECTING:
+ return "connecting";
+ case STATE_CONNECTED:
+ return "connected";
+ case STATE_DISCONNECTING:
+ return "disconnecting";
+ case STATE_PLAYING:
+ return "playing";
+ default:
+ return "<unknown state " + state + ">";
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java
new file mode 100644
index 0000000..88ce18b
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothClass.java
@@ -0,0 +1,191 @@
+/*
+ * 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.bluetooth;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ *
+ * Static helper methods and constants to decode the device class bit vector
+ * returned by the Bluetooth API.
+ *
+ * The Android Bluetooth API returns a 32-bit integer to represent the class.
+ * The format of these bits is defined at
+ * http://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
+ * (login required). This class provides static helper methods and constants to
+ * determine what Service Class(es) and Device Class are encoded in the 32-bit
+ * class.
+ *
+ * Devices typically have zero or more service classes, and exactly one device
+ * class. The device class is encoded as a major and minor device class, the
+ * minor being a subset of the major.
+ *
+ * Class is useful to describe a device (for example to show an icon),
+ * but does not reliably describe what profiles a device supports. To determine
+ * profile support you usually need to perform SDP queries.
+ *
+ * Each of these helper methods takes the 32-bit integer class as an argument.
+ *
+ * @hide
+ */
+public class BluetoothClass {
+ /** Indicates the Bluetooth API could not retrieve the class */
+ public static final int ERROR = 0xFF000000;
+
+ /** Every Bluetooth device has zero or more service classes */
+ public static class Service {
+ public static final int BITMASK = 0xFFE000;
+
+ public static final int LIMITED_DISCOVERABILITY = 0x002000;
+ public static final int POSITIONING = 0x010000;
+ public static final int NETWORKING = 0x020000;
+ public static final int RENDER = 0x040000;
+ public static final int CAPTURE = 0x080000;
+ public static final int OBJECT_TRANSFER = 0x100000;
+ public static final int AUDIO = 0x200000;
+ public static final int TELEPHONY = 0x400000;
+ public static final int INFORMATION = 0x800000;
+
+ /** Returns true if the given class supports the given Service Class.
+ * A bluetooth device can claim to support zero or more service classes.
+ * @param btClass The bluetooth class.
+ * @param serviceClass The service class constant to test for. For
+ * example, Service.AUDIO. Must be one of the
+ * Service.FOO constants.
+ * @return True if the service class is supported.
+ */
+ public static boolean hasService(int btClass, int serviceClass) {
+ if (btClass == ERROR) {
+ return false;
+ }
+ return ((btClass & Service.BITMASK & serviceClass) != 0);
+ }
+ }
+
+ /** Every Bluetooth device has exactly one device class, comprimised of
+ * major and minor components. We have not included the minor classes for
+ * major classes: NETWORKING, PERIPHERAL and IMAGING yet because they work
+ * a little differently. */
+ public static class Device {
+ public static final int BITMASK = 0x1FFC;
+
+ public static class Major {
+ public static final int BITMASK = 0x1F00;
+
+ public static final int MISC = 0x0000;
+ public static final int COMPUTER = 0x0100;
+ public static final int PHONE = 0x0200;
+ public static final int NETWORKING = 0x0300;
+ public static final int AUDIO_VIDEO = 0x0400;
+ public static final int PERIPHERAL = 0x0500;
+ public static final int IMAGING = 0x0600;
+ public static final int WEARABLE = 0x0700;
+ public static final int TOY = 0x0800;
+ public static final int HEALTH = 0x0900;
+ public static final int UNCATEGORIZED = 0x1F00;
+
+ /** Returns the Major Device Class component of a bluetooth class.
+ * Values returned from this function can be compared with the constants
+ * Device.Major.FOO. A bluetooth device can only be associated
+ * with one major class.
+ */
+ public static int getDeviceMajor(int btClass) {
+ if (btClass == ERROR) {
+ return ERROR;
+ }
+ return (btClass & Device.Major.BITMASK);
+ }
+ }
+
+ // Devices in the COMPUTER major class
+ public static final int COMPUTER_UNCATEGORIZED = 0x0100;
+ public static final int COMPUTER_DESKTOP = 0x0104;
+ public static final int COMPUTER_SERVER = 0x0108;
+ public static final int COMPUTER_LAPTOP = 0x010C;
+ public static final int COMPUTER_HANDHELD_PC_PDA = 0x0110;
+ public static final int COMPUTER_PALM_SIZE_PC_PDA = 0x0114;
+ public static final int COMPUTER_WEARABLE = 0x0118;
+
+ // Devices in the PHONE major class
+ public static final int PHONE_UNCATEGORIZED = 0x0200;
+ public static final int PHONE_CELLULAR = 0x0204;
+ public static final int PHONE_CORDLESS = 0x0208;
+ public static final int PHONE_SMART = 0x020C;
+ public static final int PHONE_MODEM_OR_GATEWAY = 0x0210;
+ public static final int PHONE_ISDN = 0x0214;
+
+ // Minor classes for the AUDIO_VIDEO major class
+ public static final int AUDIO_VIDEO_UNCATEGORIZED = 0x0400;
+ public static final int AUDIO_VIDEO_WEARABLE_HEADSET = 0x0404;
+ public static final int AUDIO_VIDEO_HANDSFREE = 0x0408;
+ //public static final int AUDIO_VIDEO_RESERVED = 0x040C;
+ public static final int AUDIO_VIDEO_MICROPHONE = 0x0410;
+ public static final int AUDIO_VIDEO_LOUDSPEAKER = 0x0414;
+ public static final int AUDIO_VIDEO_HEADPHONES = 0x0418;
+ public static final int AUDIO_VIDEO_PORTABLE_AUDIO = 0x041C;
+ public static final int AUDIO_VIDEO_CAR_AUDIO = 0x0420;
+ public static final int AUDIO_VIDEO_SET_TOP_BOX = 0x0424;
+ public static final int AUDIO_VIDEO_HIFI_AUDIO = 0x0428;
+ public static final int AUDIO_VIDEO_VCR = 0x042C;
+ public static final int AUDIO_VIDEO_VIDEO_CAMERA = 0x0430;
+ public static final int AUDIO_VIDEO_CAMCORDER = 0x0434;
+ public static final int AUDIO_VIDEO_VIDEO_MONITOR = 0x0438;
+ public static final int AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER = 0x043C;
+ public static final int AUDIO_VIDEO_VIDEO_CONFERENCING = 0x0440;
+ //public static final int AUDIO_VIDEO_RESERVED = 0x0444;
+ public static final int AUDIO_VIDEO_VIDEO_GAMING_TOY = 0x0448;
+
+ // Devices in the WEARABLE major class
+ public static final int WEARABLE_UNCATEGORIZED = 0x0700;
+ public static final int WEARABLE_WRIST_WATCH = 0x0704;
+ public static final int WEARABLE_PAGER = 0x0708;
+ public static final int WEARABLE_JACKET = 0x070C;
+ public static final int WEARABLE_HELMET = 0x0710;
+ public static final int WEARABLE_GLASSES = 0x0714;
+
+ // Devices in the TOY major class
+ public static final int TOY_UNCATEGORIZED = 0x0800;
+ public static final int TOY_ROBOT = 0x0804;
+ public static final int TOY_VEHICLE = 0x0808;
+ public static final int TOY_DOLL_ACTION_FIGURE = 0x080C;
+ public static final int TOY_CONTROLLER = 0x0810;
+ public static final int TOY_GAME = 0x0814;
+
+ // Devices in the HEALTH major class
+ public static final int HEALTH_UNCATEGORIZED = 0x0900;
+ public static final int HEALTH_BLOOD_PRESSURE = 0x0904;
+ public static final int HEALTH_THERMOMETER = 0x0908;
+ public static final int HEALTH_WEIGHING = 0x090C;
+ public static final int HEALTH_GLUCOSE = 0x0910;
+ public static final int HEALTH_PULSE_OXIMETER = 0x0914;
+ public static final int HEALTH_PULSE_RATE = 0x0918;
+ public static final int HEALTH_DATA_DISPLAY = 0x091C;
+
+ /** Returns the Device Class component of a bluetooth class. This includes
+ * both the major and minor device components. Values returned from this
+ * function can be compared with the constants Device.FOO. A bluetooth
+ * device can only be associated with one device class.
+ */
+ public static int getDevice(int btClass) {
+ if (btClass == ERROR) {
+ return ERROR;
+ }
+ return (btClass & Device.BITMASK);
+ }
+ }
+}
+
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 0b24db6..d1f71c5 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -514,7 +514,7 @@ public class BluetoothDevice {
try {
return mService.getRemoteClass(address);
} catch (RemoteException e) {Log.e(TAG, "", e);}
- return DeviceClass.CLASS_UNKNOWN;
+ return BluetoothClass.ERROR;
}
public byte[] getRemoteFeatures(String address) {
try {
diff --git a/core/java/android/bluetooth/BluetoothError.java b/core/java/android/bluetooth/BluetoothError.java
new file mode 100644
index 0000000..2554bea
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothError.java
@@ -0,0 +1,42 @@
+/*
+ * 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.bluetooth;
+
+/**
+ * Bluetooth API error codes.
+ *
+ * Errors are always negative.
+ *
+ * @hide
+ */
+public class BluetoothError {
+ /** No error */
+ public static final int SUCCESS = 0;
+
+ /** Generic error */
+ public static final int ERROR = -1000;
+
+ /** Bluetooth currently disabled */
+ public static final int ERROR_DISABLED = -1001;
+
+ /** IPC is not ready, for example service is not yet bound */
+ public static final int ERROR_IPC_NOT_READY = -1011;
+
+ /** Some other IPC error, for example a RemoteException */
+ public static final int ERROR_IPC = -1012;
+
+}
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 90db39b..905173e 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -28,35 +28,36 @@ import android.util.Log;
* The Android Bluetooth API is not finalized, and *will* change. Use at your
* own risk.
*
- * Public API for controlling the Bluetooth Headset Service.
+ * Public API for controlling the Bluetooth Headset Service. This includes both
+ * Bluetooth Headset and Handsfree (v1.5) profiles. The Headset service will
+ * attempt a handsfree connection first, and fall back to headset.
*
* BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
- * Service.
+ * Service via IPC.
*
* Creating a BluetoothHeadset object will create a binding with the
* BluetoothHeadset service. Users of this object should call close() when they
* are finished with the BluetoothHeadset, so that this proxy object can unbind
* from the service.
*
- * BlueoothHeadset objects are not guarenteed to be connected to the
- * BluetoothHeadsetService at all times. Calls on this object while not
- * connected to the service will result in default error return values. Even
- * after object construction, there is a short delay (~10ms) before this proxy
- * object is actually connected to the Service.
+ * This BluetoothHeadset object is not immediately bound to the
+ * BluetoothHeadset service. Use the ServiceListener interface to obtain a
+ * notification when it is bound, this is especially important if you wish to
+ * immediately call methods on BluetootHeadset after construction.
*
* Android only supports one connected Bluetooth Headset at a time.
*
- * Note that in this context, Headset includes both Bluetooth Headset's and
- * Handsfree devices.
- *
* @hide
*/
public class BluetoothHeadset {
- private final static String TAG = "BluetoothHeadset";
+ private static final String TAG = "BluetoothHeadset";
+ private static final boolean DBG = false;
- private final Context mContext;
private IBluetoothHeadset mService;
+ private final Context mContext;
+ private final ServiceListener mServiceListener;
+ private ConnectHeadsetCallback mConnectHeadsetCallback;
/** There was an error trying to obtain the state */
public static final int STATE_ERROR = -1;
@@ -72,25 +73,44 @@ public class BluetoothHeadset {
/** Connection cancelled before completetion. */
public static final int RESULT_CANCELLED = 2;
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- mService = IBluetoothHeadset.Stub.asInterface(service);
- Log.i(TAG, "Proxy object is now connected to Bluetooth Headset Service");
- }
- public void onServiceDisconnected(ComponentName className) {
- mService = null;
- }
- };
+ /**
+ * An interface for notifying BluetoothHeadset IPC clients when they have
+ * been connected to the BluetoothHeadset service.
+ */
+ public interface ServiceListener {
+ /**
+ * Called to notify the client when this proxy object has been
+ * connected to the BluetoothHeadset service. Clients must wait for
+ * this callback before making IPC calls on the BluetoothHeadset
+ * service.
+ */
+ public void onServiceConnected();
+
+ /**
+ * Called to notify the client that this proxy object has been
+ * disconnected from the BluetoothHeadset service. Clients must not
+ * make IPC calls on the BluetoothHeadset service after this callback.
+ * This callback will currently only occur if the application hosting
+ * the BluetoothHeadset service, but may be called more often in future.
+ */
+ public void onServiceDisconnected();
+ }
+
+ /**
+ * Interface for connectHeadset() callback.
+ * This callback can occur in the Binder thread.
+ */
+ public interface ConnectHeadsetCallback {
+ public void onConnectHeadsetResult(String address, int resultCode);
+ }
/**
* Create a BluetoothHeadset proxy object.
- * Remeber to call close() when you are done with this object, so that it
- * can unbind from the BluetoothHeadsetService.
*/
- public BluetoothHeadset(Context context) {
+ public BluetoothHeadset(Context context, ServiceListener l) {
mContext = context;
- if (!context.bindService(
- new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
+ mServiceListener = l;
+ if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
Log.e(TAG, "Could not bind to Bluetooth Headset Service");
}
}
@@ -126,6 +146,9 @@ public class BluetoothHeadset {
try {
return mService.getState();
} catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
}
return BluetoothHeadset.STATE_ERROR;
}
@@ -141,6 +164,9 @@ public class BluetoothHeadset {
try {
return mService.getHeadsetAddress();
} catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
}
return null;
}
@@ -150,20 +176,29 @@ public class BluetoothHeadset {
* This call does not block. Fails if a headset is already connecting
* or connected.
* Will connect to the last connected headset if address is null.
+ * onConnectHeadsetResult() of your ConnectHeadsetCallback will be called
+ * on completition.
* @param address The Bluetooth Address to connect to, or null to connect
* to the last connected headset.
- * @param callback A callback with onCreateBondingResult() defined, or
- * null.
+ * @param callback Callback on result. Not called if false is returned. Can
+ * be null.
+ * to the last connected headset.
* @return False if there was a problem initiating the connection
* procedure, and your callback will not be used. True if
* the connection procedure was initiated, in which case
* your callback is guarenteed to be called.
*/
- public boolean connectHeadset(String address, IBluetoothHeadsetCallback callback) {
+ public boolean connectHeadset(String address, ConnectHeadsetCallback callback) {
if (mService != null) {
try {
- return mService.connectHeadset(address, callback);
+ if (mService.connectHeadset(address, mHeadsetCallback)) {
+ mConnectHeadsetCallback = callback;
+ return true;
+ }
} catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
}
return false;
}
@@ -178,6 +213,9 @@ public class BluetoothHeadset {
try {
return mService.isConnected(address);
} catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
}
return false;
}
@@ -191,8 +229,72 @@ public class BluetoothHeadset {
if (mService != null) {
try {
mService.disconnectHeadset();
+ return true;
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Start BT Voice Recognition mode, and set up Bluetooth audio path.
+ * Returns false if there is no headset connected, or if the
+ * connected headset does not support voice recognition, or on
+ * error.
+ */
+ public boolean startVoiceRecognition() {
+ if (mService != null) {
+ try {
+ return mService.startVoiceRecognition();
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Stop BT Voice Recognition mode, and shut down Bluetooth audio path.
+ * Returns false if there is no headset connected, or the connected
+ * headset is not in voice recognition mode, or on error.
+ */
+ public boolean stopVoiceRecognition() {
+ if (mService != null) {
+ try {
+ return mService.stopVoiceRecognition();
} catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
}
return false;
}
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ if (DBG) Log.d(TAG, "Proxy object connected");
+ mService = IBluetoothHeadset.Stub.asInterface(service);
+ if (mServiceListener != null) {
+ mServiceListener.onServiceConnected();
+ }
+ }
+ public void onServiceDisconnected(ComponentName className) {
+ if (DBG) Log.d(TAG, "Proxy object disconnected");
+ mService = null;
+ if (mServiceListener != null) {
+ mServiceListener.onServiceDisconnected();
+ }
+ }
+ };
+
+ private IBluetoothHeadsetCallback mHeadsetCallback = new IBluetoothHeadsetCallback.Stub() {
+ public void onConnectHeadsetResult(String address, int resultCode) {
+ if (mConnectHeadsetCallback != null) {
+ mConnectHeadsetCallback.onConnectHeadsetResult(address, resultCode);
+ }
+ }
+ };
}
diff --git a/core/java/android/bluetooth/DeviceClass.java b/core/java/android/bluetooth/DeviceClass.java
deleted file mode 100644
index 36035ca..0000000
--- a/core/java/android/bluetooth/DeviceClass.java
+++ /dev/null
@@ -1,131 +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.bluetooth;
-
-/**
- * The Android Bluetooth API is not finalized, and *will* change. Use at your
- * own risk.
- *
- * Static helper methods and constants to decode the device class bit vector
- * returned by the Bluetooth API.
- *
- * The Android Bluetooth API returns a 32-bit integer to represent the device
- * class. This is actually a bit vector, the format defined at
- * http://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
- * (login required). This class provides static helper methods and constants to
- * determine what Service Class(es), Major Class, and Minor Class are encoded
- * in a 32-bit device class.
- *
- * Each of the helper methods takes the 32-bit integer device class as an
- * argument.
- *
- * @hide
- */
-public class DeviceClass {
-
- // Baseband class information
- // See http://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
-
- public static final int SERVICE_CLASS_BITMASK = 0xFFE000;
- public static final int SERVICE_CLASS_LIMITED_DISCOVERABILITY = 0x002000;
- public static final int SERVICE_CLASS_POSITIONING = 0x010000;
- public static final int SERVICE_CLASS_NETWORKING = 0x020000;
- public static final int SERVICE_CLASS_RENDER = 0x040000;
- public static final int SERVICE_CLASS_CAPTURE = 0x080000;
- public static final int SERVICE_CLASS_OBJECT_TRANSFER = 0x100000;
- public static final int SERVICE_CLASS_AUDIO = 0x200000;
- public static final int SERVICE_CLASS_TELEPHONY = 0x400000;
- public static final int SERVICE_CLASS_INFORMATION = 0x800000;
-
- public static final int MAJOR_CLASS_BITMASK = 0x001F00;
- public static final int MAJOR_CLASS_MISC = 0x000000;
- public static final int MAJOR_CLASS_COMPUTER = 0x000100;
- public static final int MAJOR_CLASS_PHONE = 0x000200;
- public static final int MAJOR_CLASS_NETWORKING = 0x000300;
- public static final int MAJOR_CLASS_AUDIO_VIDEO = 0x000400;
- public static final int MAJOR_CLASS_PERIPHERAL = 0x000500;
- public static final int MAJOR_CLASS_IMAGING = 0x000600;
- public static final int MAJOR_CLASS_WEARABLE = 0x000700;
- public static final int MAJOR_CLASS_TOY = 0x000800;
- public static final int MAJOR_CLASS_MEDICAL = 0x000900;
- public static final int MAJOR_CLASS_UNCATEGORIZED = 0x001F00;
-
- // Minor classes for the AUDIO_VIDEO major class
- public static final int MINOR_CLASS_AUDIO_VIDEO_BITMASK = 0x0000FC;
- public static final int MINOR_CLASS_AUDIO_VIDEO_UNCATEGORIZED = 0x000000;
- public static final int MINOR_CLASS_AUDIO_VIDEO_HEADSET = 0x000004;
- public static final int MINOR_CLASS_AUDIO_VIDEO_HANDSFREE = 0x000008;
- public static final int MINOR_CLASS_AUDIO_VIDEO_MICROPHONE = 0x000010;
- public static final int MINOR_CLASS_AUDIO_VIDEO_LOUDSPEAKER = 0x000014;
- public static final int MINOR_CLASS_AUDIO_VIDEO_HEADPHONES = 0x000018;
- public static final int MINOR_CLASS_AUDIO_VIDEO_PORTABLE_AUDIO = 0x00001C;
- public static final int MINOR_CLASS_AUDIO_VIDEO_CAR_AUDIO = 0x000020;
- public static final int MINOR_CLASS_AUDIO_VIDEO_SET_TOP_BOX = 0x000024;
- public static final int MINOR_CLASS_AUDIO_VIDEO_HIFI_AUDIO = 0x000028;
- public static final int MINOR_CLASS_AUDIO_VIDEO_VCR = 0x00002C;
- public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_CAMERA = 0x000030;
- public static final int MINOR_CLASS_AUDIO_VIDEO_CAMCORDER = 0x000034;
- public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_MONITOR = 0x000038;
- public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER = 0x00003C;
- public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_CONFERENCING = 0x000040;
- public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_GAMING_TOY = 0x000048;
-
- // Indicates the Bluetooth API could not retrieve the class
- public static final int CLASS_UNKNOWN = 0xFF000000;
-
- /** Returns true if the given device class supports the given Service Class.
- * A bluetooth device can claim to support zero or more service classes.
- * @param deviceClass The bluetooth device class.
- * @param serviceClassType The service class constant to test for. For
- * example, DeviceClass.SERVICE_CLASS_AUDIO. This
- * must be one of the SERVICE_CLASS_xxx constants,
- * results of this function are undefined
- * otherwise.
- * @return If the deviceClass claims to support the serviceClassType.
- */
- public static boolean hasServiceClass(int deviceClass, int serviceClassType) {
- if (deviceClass == CLASS_UNKNOWN) {
- return false;
- }
- return ((deviceClass & SERVICE_CLASS_BITMASK & serviceClassType) != 0);
- }
-
- /** Returns the Major Class of a bluetooth device class.
- * Values returned from this function can be compared with the constants
- * MAJOR_CLASS_xxx. A bluetooth device can only be associated
- * with one major class.
- */
- public static int getMajorClass(int deviceClass) {
- if (deviceClass == CLASS_UNKNOWN) {
- return CLASS_UNKNOWN;
- }
- return (deviceClass & MAJOR_CLASS_BITMASK);
- }
-
- /** Returns the Minor Class of a bluetooth device class.
- * Values returned from this function can be compared with the constants
- * MINOR_CLASS_xxx_yyy, where xxx is the Major Class. A bluetooth
- * device can only be associated with one minor class within its major
- * class.
- */
- public static int getMinorClass(int deviceClass) {
- if (deviceClass == CLASS_UNKNOWN) {
- return CLASS_UNKNOWN;
- }
- return (deviceClass & MINOR_CLASS_AUDIO_VIDEO_BITMASK);
- }
-}
diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl
new file mode 100644
index 0000000..7e0226d
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.bluetooth;
+
+/**
+ * System private API for Bluetooth A2DP service
+ *
+ * {@hide}
+ */
+interface IBluetoothA2dp {
+ int connectSink(in String address);
+ int disconnectSink(in String address);
+ List<String> listConnectedSinks();
+ int getSinkState(in String address);
+}
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index 7b6030b..564861f 100644
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -36,7 +36,11 @@ interface IBluetoothHeadset {
// returns true
boolean connectHeadset(in String address, in IBluetoothHeadsetCallback callback);
+ void disconnectHeadset();
+
boolean isConnected(in String address);
- void disconnectHeadset();
+ boolean startVoiceRecognition();
+
+ boolean stopVoiceRecognition();
}
diff --git a/core/java/android/bluetooth/package.html b/core/java/android/bluetooth/package.html
index ccd8fec..79abf0c 100644
--- a/core/java/android/bluetooth/package.html
+++ b/core/java/android/bluetooth/package.html
@@ -9,6 +9,5 @@ query the SDP database of other Bluetooth devices, establish RFCOMM
channels/sockets on Android, and connect to specified sockets on other devices.
</p>
<p>Remember, not all Android devices are guaranteed to have Bluetooth functionality.</p>
-{@hide}
</BODY>
</HTML>
diff --git a/core/java/android/content/AbstractTableMerger.java b/core/java/android/content/AbstractTableMerger.java
index 56e5d4a..e1a484e 100644
--- a/core/java/android/content/AbstractTableMerger.java
+++ b/core/java/android/content/AbstractTableMerger.java
@@ -548,7 +548,8 @@ public abstract class AbstractTableMerger
long numDeletedEntries = 0;
if (mDeletedTable != null) {
Cursor deletedCursor = mDb.query(mDeletedTable,
- syncIdAndVersionProjection, _SYNC_ACCOUNT + "=?", accountSelectionArgs,
+ syncIdAndVersionProjection,
+ _SYNC_ACCOUNT + "=? AND " + _SYNC_ID + " IS NOT NULL", accountSelectionArgs,
null, null, mDeletedTable + "." + _SYNC_ID);
numDeletedEntries = deletedCursor.getCount();
diff --git a/core/java/android/content/AsyncQueryHandler.java b/core/java/android/content/AsyncQueryHandler.java
index 48f1bc7..2d651a7 100644
--- a/core/java/android/content/AsyncQueryHandler.java
+++ b/core/java/android/content/AsyncQueryHandler.java
@@ -24,6 +24,8 @@ import android.os.Looper;
import android.os.Message;
import android.util.Log;
+import java.lang.ref.WeakReference;
+
/**
* A helper class to help make handling asynchronous {@link ContentResolver}
* queries easier.
@@ -37,7 +39,7 @@ public abstract class AsyncQueryHandler extends Handler {
private static final int EVENT_ARG_UPDATE = 3;
private static final int EVENT_ARG_DELETE = 4;
- /* package */ ContentResolver mResolver;
+ /* package */ final WeakReference<ContentResolver> mResolver;
private static Looper sLooper = null;
@@ -62,18 +64,26 @@ public abstract class AsyncQueryHandler extends Handler {
@Override
public void handleMessage(Message msg) {
+ final ContentResolver resolver = mResolver.get();
+ if (resolver == null) return;
+
WorkerArgs args = (WorkerArgs) msg.obj;
int token = msg.what;
int event = msg.arg1;
-
+
switch (event) {
case EVENT_ARG_QUERY:
Cursor cursor;
try {
- cursor = mResolver.query(args.uri, args.projection,
+ cursor = resolver.query(args.uri, args.projection,
args.selection, args.selectionArgs,
args.orderBy);
+ // Calling getCount() causes the cursor window to be filled,
+ // which will make the first access on the main thread a lot faster.
+ if (cursor != null) {
+ cursor.getCount();
+ }
} catch (Exception e) {
cursor = null;
}
@@ -82,18 +92,16 @@ public abstract class AsyncQueryHandler extends Handler {
break;
case EVENT_ARG_INSERT:
- args.result = mResolver.insert(args.uri, args.values);
+ args.result = resolver.insert(args.uri, args.values);
break;
case EVENT_ARG_UPDATE:
- int r = mResolver.update(args.uri, args.values, args.selection,
+ args.result = resolver.update(args.uri, args.values, args.selection,
args.selectionArgs);
- args.result = new Integer(r);
break;
case EVENT_ARG_DELETE:
- int r2 = mResolver.delete(args.uri, args.selection, args.selectionArgs);
- args.result = new Integer(r2);
+ args.result = resolver.delete(args.uri, args.selection, args.selectionArgs);
break;
}
@@ -115,7 +123,7 @@ public abstract class AsyncQueryHandler extends Handler {
public AsyncQueryHandler(ContentResolver cr) {
super();
- mResolver = cr;
+ mResolver = new WeakReference<ContentResolver>(cr);
synchronized (AsyncQueryHandler.class) {
if (sLooper == null) {
HandlerThread thread = new HandlerThread("AsyncQueryWorker");
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 6a6f4f9..cd92002 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -327,7 +327,7 @@ public abstract class BroadcastReceiver {
* current broadcast; only works with broadcasts sent through
* {@link Context#sendOrderedBroadcast(Intent, String)
* Context.sendOrderedBroadcast}. This will prevent
- * any other intent receivers from receiving the broadcast. It will still
+ * any other broadcast receivers from receiving the broadcast. It will still
* call {@link #onReceive} of the BroadcastReceiver that the caller of
* {@link Context#sendOrderedBroadcast(Intent, String)
* Context.sendOrderedBroadcast} passed in.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 00a6d31..6da00df 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -982,10 +982,10 @@ public abstract class Context {
String profileFile, Bundle arguments);
/**
- * Return the handle to a system-level service by name. The class of the
- * returned object varies by the requested name. Currently available names
+ * Return the handle to a system-level service by name. The class of the
+ * returned object varies by the requested name. Currently available names
* are:
- *
+ *
* <dl>
* <dt> {@link #WINDOW_SERVICE} ("window")
* <dd> The top-level window manager in which you can place custom
@@ -1021,6 +1021,9 @@ public abstract class Context {
* <dt> {@link #WIFI_SERVICE} ("wifi")
* <dd> A {@link android.net.wifi.WifiManager WifiManager} for management of
* Wi-Fi connectivity.
+ * <dt> {@link #INPUT_METHOD_SERVICE} ("input_method")
+ * <dd> An {@link android.view.inputmethod.InputMethodManager InputMethodManager}
+ * for management of input methods.
* </dl>
*
* <p>Note: System services obtained via this API may be closely associated with
@@ -1029,9 +1032,9 @@ public abstract class Context {
* Services, Providers, etc.)
*
* @param name The name of the desired service.
- *
+ *
* @return The service or null if the name does not exist.
- *
+ *
* @see #WINDOW_SERVICE
* @see android.view.WindowManager
* @see #LAYOUT_INFLATER_SERVICE
@@ -1062,6 +1065,8 @@ public abstract class Context {
* @see android.media.AudioManager
* @see #TELEPHONY_SERVICE
* @see android.internal.TelephonyManager
+ * @see #INPUT_METHOD_SERVICE
+ * @see android.view.inputmethod.InputMethodManager
*/
public abstract Object getSystemService(String name);
@@ -1235,6 +1240,15 @@ public abstract class Context {
public static final String CLIPBOARD_SERVICE = "clipboard";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.view.inputmethod.InputMethodManager} for accessing input
+ * methods.
+ *
+ * @see #getSystemService
+ */
+ public static final String INPUT_METHOD_SERVICE = "input_method";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/DefaultDataHandler.java b/core/java/android/content/DefaultDataHandler.java
index 7dc71b8..863c9f6 100644
--- a/core/java/android/content/DefaultDataHandler.java
+++ b/core/java/android/content/DefaultDataHandler.java
@@ -28,42 +28,47 @@ import java.io.InputStream;
import java.util.Stack;
/**
- * insert default data from InputStream, should be in XML format:
- * if the provider syncs data to the server, the imported data will be synced to the server
- * Samples:
- * insert one row
- * <row uri="content://contacts/people">
- * <Col column = "name" value = "foo feebe "/>
- * <Col column = "addr" value = "Tx"/>
- * </row>
- *
- * delete, it must be in order of uri, select and arg
- * <del uri="content://contacts/people" select="name=? and addr=?"
- * arg1 = "foo feebe" arg2 ="Tx"/>
+ * Inserts default data from InputStream, should be in XML format.
+ * If the provider syncs data to the server, the imported data will be synced to the server.
+ * <p>Samples:</p>
+ * <br/>
+ * Insert one row:
+ * <pre>
+ * &lt;row uri="content://contacts/people">
+ * &lt;Col column = "name" value = "foo feebe "/>
+ * &lt;Col column = "addr" value = "Tx"/>
+ * &lt;/row></pre>
+ * <br/>
+ * Delete, it must be in order of uri, select, and arg:
+ * <pre>
+ * &lt;del uri="content://contacts/people" select="name=? and addr=?"
+ * arg1 = "foo feebe" arg2 ="Tx"/></pre>
+ * <br/>
+ * Use first row's uri to insert into another table,
+ * content://contacts/people/1/phones:
+ * <pre>
+ * &lt;row uri="content://contacts/people">
+ * &lt;col column = "name" value = "foo feebe"/>
+ * &lt;col column = "addr" value = "Tx"/>
+ * &lt;row postfix="phones">
+ * &lt;col column="number" value="512-514-6535"/>
+ * &lt;/row>
+ * &lt;row postfix="phones">
+ * &lt;col column="cell" value="512-514-6535"/>
+ * &lt;/row>
+ * &lt;/row></pre>
+ * <br/>
+ * Insert multiple rows in to same table and same attributes:
+ * <pre>
+ * &lt;row uri="content://contacts/people" >
+ * &lt;row>
+ * &lt;col column= "name" value = "foo feebe"/>
+ * &lt;col column= "addr" value = "Tx"/>
+ * &lt;/row>
+ * &lt;row>
+ * &lt;/row>
+ * &lt;/row></pre>
*
- * use first row's uri to insert into another table
- * content://contacts/people/1/phones
- * <row uri="content://contacts/people">
- * <col column = "name" value = "foo feebe"/>
- * <col column = "addr" value = "Tx"/>
- * <row postfix="phones">
- * <col column="number" value="512-514-6535"/>
- * </row>
- * <row postfix="phones">
- * <col column="cell" value="512-514-6535"/>
- * </row>
- * </row>
- *
- * insert multiple rows in to same table and same attributes:
- * <row uri="content://contacts/people" >
- * <row>
- * <col column= "name" value = "foo feebe"/>
- * <col column= "addr" value = "Tx"/>
- * </row>
- * <row>
- * </row>
- * </row>
- *
* @hide
*/
public class DefaultDataHandler implements ContentInsertHandler {
diff --git a/core/java/android/content/DialogInterface.java b/core/java/android/content/DialogInterface.java
index fc94aa6..4afa294 100644
--- a/core/java/android/content/DialogInterface.java
+++ b/core/java/android/content/DialogInterface.java
@@ -22,10 +22,39 @@ import android.view.KeyEvent;
*
*/
public interface DialogInterface {
- public static final int BUTTON1 = -1;
- public static final int BUTTON2 = -2;
- public static final int BUTTON3 = -3;
+ /**
+ * The identifier for the positive button.
+ */
+ public static final int BUTTON_POSITIVE = -1;
+
+ /**
+ * The identifier for the negative button.
+ */
+ public static final int BUTTON_NEGATIVE = -2;
+ /**
+ * The identifier for the neutral button.
+ */
+ public static final int BUTTON_NEUTRAL = -3;
+
+ /**
+ * @deprecated Use {@link #BUTTON_POSITIVE}
+ */
+ @Deprecated
+ public static final int BUTTON1 = BUTTON_POSITIVE;
+
+ /**
+ * @deprecated Use {@link #BUTTON_NEGATIVE}
+ */
+ @Deprecated
+ public static final int BUTTON2 = BUTTON_NEGATIVE;
+
+ /**
+ * @deprecated Use {@link #BUTTON_NEUTRAL}
+ */
+ @Deprecated
+ public static final int BUTTON3 = BUTTON_NEUTRAL;
+
public void cancel();
public void dismiss();
@@ -71,9 +100,11 @@ public interface DialogInterface {
* This method will be invoked when a button in the dialog is clicked.
*
* @param dialog The dialog that received the click.
- * @param which The button that was clicked, i.e. BUTTON1 or BUTTON2 or
- * the position of the item clicked.
+ * @param which The button that was clicked (e.g.
+ * {@link DialogInterface#BUTTON1}) or the position
+ * of the item clicked.
*/
+ /* TODO: Change to use BUTTON_POSITIVE after API council */
public void onClick(DialogInterface dialog, int which);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c76158c..4a92b4c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -33,7 +33,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.TypedValue;
import com.android.internal.util.XmlUtils;
import java.io.IOException;
@@ -542,23 +541,8 @@ import java.util.Set;
* <h3>Flags</h3>
*
* <p>These are the possible flags that can be used in the Intent via
- * {@link #setFlags} and {@link #addFlags}.
- *
- * <ul>
- * <li> {@link #FLAG_GRANT_READ_URI_PERMISSION}
- * <li> {@link #FLAG_GRANT_WRITE_URI_PERMISSION}
- * <li> {@link #FLAG_FROM_BACKGROUND}
- * <li> {@link #FLAG_DEBUG_LOG_RESOLUTION}
- * <li> {@link #FLAG_ACTIVITY_NO_HISTORY}
- * <li> {@link #FLAG_ACTIVITY_SINGLE_TOP}
- * <li> {@link #FLAG_ACTIVITY_NEW_TASK}
- * <li> {@link #FLAG_ACTIVITY_MULTIPLE_TASK}
- * <li> {@link #FLAG_ACTIVITY_FORWARD_RESULT}
- * <li> {@link #FLAG_ACTIVITY_PREVIOUS_IS_TOP}
- * <li> {@link #FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS}
- * <li> {@link #FLAG_ACTIVITY_BROUGHT_TO_FRONT}
- * <li> {@link #FLAG_RECEIVER_REGISTERED_ONLY}
- * </ul>
+ * {@link #setFlags} and {@link #addFlags}. See {@link #setFlags} for a list
+ * of all possible flags.
*/
public class Intent implements Parcelable {
// ---------------------------------------------------------------------
@@ -573,6 +557,7 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_MAIN = "android.intent.action.MAIN";
+
/**
* Activity Action: Display the data to the user. This is the most common
* action performed on data -- it is the generic action you can use on
@@ -586,11 +571,13 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_VIEW = "android.intent.action.VIEW";
+
/**
* A synonym for {@link #ACTION_VIEW}, the "standard" action that is
* performed on a piece of data.
*/
public static final String ACTION_DEFAULT = ACTION_VIEW;
+
/**
* Used to indicate that some piece of data should be attached to some other
* place. For example, image data could be attached to a contact. It is up
@@ -601,6 +588,7 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_ATTACH_DATA = "android.intent.action.ATTACH_DATA";
+
/**
* Activity Action: Provide explicit editable access to the given data.
* <p>Input: {@link #getData} is URI of data to be edited.
@@ -608,6 +596,7 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_EDIT = "android.intent.action.EDIT";
+
/**
* Activity Action: Pick an existing item, or insert a new item, and then edit it.
* <p>Input: {@link #getType} is the desired MIME type of the item to create or edit.
@@ -618,6 +607,7 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT";
+
/**
* Activity Action: Pick an item from the data, returning what was selected.
* <p>Input: {@link #getData} is URI containing a directory of data
@@ -626,13 +616,15 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_PICK = "android.intent.action.PICK";
+
/**
* Activity Action: Creates a shortcut.
- * <p>Input: Nothing.
+ * <p>Input: Nothing.</p>
* <p>Output: An Intent representing the shortcut. The intent must contain three
* extras: SHORTCUT_INTENT (value: Intent), SHORTCUT_NAME (value: String),
* and SHORTCUT_ICON (value: Bitmap) or SHORTCUT_ICON_RESOURCE
- * (value: ShortcutIconResource).
+ * (value: ShortcutIconResource).</p>
+ *
* @see #EXTRA_SHORTCUT_INTENT
* @see #EXTRA_SHORTCUT_NAME
* @see #EXTRA_SHORTCUT_ICON
@@ -670,10 +662,12 @@ public class Intent implements Parcelable {
"android.intent.extra.shortcut.ICON_RESOURCE";
/**
- * Represents a shortcut icon resource.
+ * Represents a shortcut/live folder icon resource.
*
* @see Intent#ACTION_CREATE_SHORTCUT
* @see Intent#EXTRA_SHORTCUT_ICON_RESOURCE
+ * @see android.provider.LiveFolders#ACTION_CREATE_LIVE_FOLDER
+ * @see android.provider.LiveFolders#EXTRA_LIVE_FOLDER_ICON
*/
public static class ShortcutIconResource implements Parcelable {
/**
@@ -972,10 +966,13 @@ public class Intent implements Parcelable {
public static final String ACTION_SEARCH = "android.intent.action.SEARCH";
/**
* Activity Action: Perform a web search.
- * <p>Input: {@link #getData} is URI of data. If it is a url
- * starts with http or https, the site will be opened. If it is plain text,
- * Google search will be applied.
- * <p>Output: nothing.
+ * <p>
+ * Input: {@link android.app.SearchManager#QUERY
+ * getStringExtra(SearchManager.QUERY)} is the text to search for. If it is
+ * a url starts with http or https, the site will be opened. If it is plain
+ * text, Google search will be applied.
+ * <p>
+ * Output: nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
@@ -1027,7 +1024,7 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
-
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent broadcast actions (see action variable).
@@ -1318,6 +1315,14 @@ public class Intent implements Parcelable {
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_GTALK_SERVICE_DISCONNECTED =
"android.intent.action.GTALK_DISCONNECTED";
+
+ /**
+ * Broadcast Action: An input method has been changed.
+ * {@hide pending API Council approval}
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_INPUT_METHOD_CHANGED =
+ "android.intent.action.INPUT_METHOD_CHANGED";
/**
* <p>Broadcast Action: The user has switched the phone into or out of Airplane Mode. One or
@@ -1644,6 +1649,15 @@ public class Intent implements Parcelable {
*/
public static final String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT";
+ /**
+ * Used as an int extra field in {@link android.content.Intent#ACTION_VOICE_COMMAND}
+ * intents to request which audio route the voice command should prefer.
+ * The value should be a route from {@link android.media.AudioManager}, for
+ * example ROUTE_BLUETOOTH_SCO. Providing this value is optional.
+ * {@hide pending API Council approval}
+ */
+ public static final String EXTRA_AUDIO_ROUTE = "android.intent.extra.AUDIO_ROUTE";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
@@ -1671,7 +1685,10 @@ public class Intent implements Parcelable {
public static final int FLAG_DEBUG_LOG_RESOLUTION = 0x00000008;
/**
- * If set, the new activity is not kept in the history stack.
+ * If set, the new activity is not kept in the history stack. As soon as
+ * the user navigates away from it, the activity is finished. This may also
+ * be set with the {@link android.R.styleable#AndroidManifestActivity_noHistory
+ * noHistory} attribute.
*/
public static final int FLAG_ACTIVITY_NO_HISTORY = 0x40000000;
/**
@@ -1794,9 +1811,33 @@ public class Intent implements Parcelable {
*/
public static final int FLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 0x00200000;
/**
- * If set, this activity is being launched from history (longpress home key).
+ * This flag is not normally set by application code, but set for you by
+ * the system if this activity is being launched from history
+ * (longpress home key).
*/
public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 0x00100000;
+ /**
+ * If set, this marks a point in the task's activity stack that should
+ * be cleared when the task is reset. That is, the next time the task
+ * is broad to the foreground with
+ * {@link #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED} (typically as a result of
+ * the user re-launching it from home), this activity and all on top of
+ * it will be finished so that the user does not return to them, but
+ * instead returns to whatever activity preceeded it.
+ *
+ * <p>This is useful for cases where you have a logical break in your
+ * application. For example, an e-mail application may have a command
+ * to view an attachment, which launches an image view activity to
+ * display it. This activity should be part of the e-mail application's
+ * task, since it is a part of the task the user is involved in. However,
+ * if the user leaves that task, and later selects the e-mail app from
+ * home, we may like them to return to the conversation they were
+ * viewing, not the picture attachment, since that is confusing. By
+ * setting this flag when launching the image viewer, that viewer and
+ * any activities it starts will be removed the next time the user returns
+ * to mail.
+ */
+ public static final int FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET = 0x00080000;
/**
* If set, when sending a broadcast only registered receivers will be
@@ -3725,6 +3766,30 @@ public class Intent implements Parcelable {
}
/**
+ * Completely replace the extras in the Intent with the extras in the
+ * given Intent.
+ *
+ * @param src The exact extras contained in this Intent are copied
+ * into the target intent, replacing any that were previously there.
+ */
+ public Intent replaceExtras(Intent src) {
+ mExtras = src.mExtras != null ? new Bundle(src.mExtras) : null;
+ return this;
+ }
+
+ /**
+ * Completely replace the extras in the Intent with the given Bundle of
+ * extras.
+ *
+ * @param extras The new set of extras in the Intent, or null to erase
+ * all extras.
+ */
+ public Intent replaceExtras(Bundle extras) {
+ mExtras = extras != null ? new Bundle(extras) : null;
+ return this;
+ }
+
+ /**
* Remove extended data from the intent.
*
* @see #putExtra
@@ -3762,14 +3827,17 @@ public class Intent implements Parcelable {
* @see #FLAG_GRANT_WRITE_URI_PERMISSION
* @see #FLAG_DEBUG_LOG_RESOLUTION
* @see #FLAG_FROM_BACKGROUND
- * @see #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
* @see #FLAG_ACTIVITY_BROUGHT_TO_FRONT
+ * @see #FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
* @see #FLAG_ACTIVITY_CLEAR_TOP
* @see #FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
* @see #FLAG_ACTIVITY_FORWARD_RESULT
+ * @see #FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
* @see #FLAG_ACTIVITY_MULTIPLE_TASK
* @see #FLAG_ACTIVITY_NEW_TASK
* @see #FLAG_ACTIVITY_NO_HISTORY
+ * @see #FLAG_ACTIVITY_PREVIOUS_IS_TOP
+ * @see #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
* @see #FLAG_ACTIVITY_SINGLE_TOP
* @see #FLAG_RECEIVER_REGISTERED_ONLY
*/
@@ -4334,7 +4402,11 @@ public class Intent implements Parcelable {
XmlUtils.skipCurrentTag(parser);
} else if (nodeName.equals("extra")) {
- parseExtra(resources, intent, parser, attrs);
+ if (intent.mExtras == null) {
+ intent.mExtras = new Bundle();
+ }
+ resources.parseBundleExtra("extra", attrs, intent.mExtras);
+ XmlUtils.skipCurrentTag(parser);
} else {
XmlUtils.skipCurrentTag(parser);
@@ -4343,49 +4415,4 @@ public class Intent implements Parcelable {
return intent;
}
-
- private static void parseExtra(Resources resources, Intent intent, XmlPullParser parser,
- AttributeSet attrs) throws XmlPullParserException, IOException {
- TypedArray sa = resources.obtainAttributes(attrs,
- com.android.internal.R.styleable.IntentExtra);
-
- String name = sa.getString(
- com.android.internal.R.styleable.IntentExtra_name);
- if (name == null) {
- sa.recycle();
- throw new RuntimeException(
- "<extra> requires an android:name attribute at "
- + parser.getPositionDescription());
- }
-
- TypedValue v = sa.peekValue(
- com.android.internal.R.styleable.IntentExtra_value);
- if (v != null) {
- if (v.type == TypedValue.TYPE_STRING) {
- CharSequence cs = v.coerceToString();
- intent.putExtra(name, cs != null ? cs.toString() : null);
- } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) {
- intent.putExtra(name, v.data != 0);
- } else if (v.type >= TypedValue.TYPE_FIRST_INT
- && v.type <= TypedValue.TYPE_LAST_INT) {
- intent.putExtra(name, v.data);
- } else if (v.type == TypedValue.TYPE_FLOAT) {
- intent.putExtra(name, v.getFloat());
- } else {
- sa.recycle();
- throw new RuntimeException(
- "<extra> only supports string, integer, float, color, and boolean at "
- + parser.getPositionDescription());
- }
- } else {
- sa.recycle();
- throw new RuntimeException(
- "<extra> requires an android:value or android:resource attribute at "
- + parser.getPositionDescription());
- }
-
- sa.recycle();
-
- XmlUtils.skipCurrentTag(parser);
- }
}
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 2bf84e7..6bc3774 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -50,14 +50,14 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.pim.DateUtils;
-import android.pim.Time;
import android.preference.Preference;
import android.preference.PreferenceGroup;
import android.provider.Sync;
import android.provider.Settings;
import android.provider.Sync.History;
import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
import android.util.Config;
import android.util.EventLog;
import android.util.Log;
@@ -1484,7 +1484,8 @@ 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.getListenForNetworkTickles()
+ if (!force && (!syncSettings.getBackgroundData()
+ || !syncSettings.getListenForNetworkTickles()
|| !syncSettings.getSyncProviderAutomatically(
syncOperation.authority))) {
if (isLoggable) {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index f577d2d..85d877a 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -120,12 +120,19 @@ public class ActivityInfo extends ComponentInfo
*/
public static final int FLAG_ALLOW_TASK_REPARENTING = 0x0040;
/**
+ * Bit in {@link #flags} indicating that, when the user navigates away
+ * from an activity, it should be finished.
+ * Set from the
+ * {@link android.R.attr#noHistory} attribute.
+ */
+ public static final int FLAG_NO_HISTORY = 0x0080;
+ /**
* Options that have been set in the activity declaration in the
* manifest: {@link #FLAG_MULTIPROCESS},
* {@link #FLAG_FINISH_ON_TASK_LAUNCH}, {@link #FLAG_CLEAR_TASK_ON_LAUNCH},
* {@link #FLAG_ALWAYS_RETAIN_TASK_STATE},
* {@link #FLAG_STATE_NOT_NEEDED}, {@link #FLAG_EXCLUDE_FROM_RECENTS},
- * {@link #FLAG_ALLOW_TASK_REPARENTING}.
+ * {@link #FLAG_ALLOW_TASK_REPARENTING}, {@link #FLAG_NO_HISTORY}.
*/
public int flags;
@@ -247,6 +254,16 @@ public class ActivityInfo extends ComponentInfo
*/
public int configChanges;
+ /**
+ * The desired soft input mode for this activity's main window.
+ * Set from the {@link android.R.attr#windowSoftInputMode} attribute
+ * in the activity's manifest. May be any of the same values allowed
+ * for {@link android.view.WindowManager.LayoutParams#softInputMode
+ * WindowManager.LayoutParams.softInputMode}. If 0 (unspecified),
+ * the mode from the theme will be used.
+ */
+ public int softInputMode;
+
public ActivityInfo() {
}
@@ -260,6 +277,7 @@ public class ActivityInfo extends ComponentInfo
flags = orig.flags;
screenOrientation = orig.screenOrientation;
configChanges = orig.configChanges;
+ softInputMode = orig.softInputMode;
}
/**
@@ -280,9 +298,10 @@ public class ActivityInfo extends ComponentInfo
+ " targetActivity=" + targetActivity);
pw.println(prefix + "launchMode=" + launchMode
+ " flags=0x" + Integer.toHexString(flags)
- + " theme=0x" + Integer.toHexString(theme)
- + " orien=" + screenOrientation
- + " configChanges=0x" + Integer.toHexString(configChanges));
+ + " theme=0x" + Integer.toHexString(theme));
+ pw.println(prefix + "screenOrientation=" + screenOrientation
+ + " configChanges=0x" + Integer.toHexString(configChanges)
+ + " softInputMode=0x" + Integer.toHexString(softInputMode));
super.dumpBack(pw, prefix);
}
@@ -306,6 +325,7 @@ public class ActivityInfo extends ComponentInfo
dest.writeInt(flags);
dest.writeInt(screenOrientation);
dest.writeInt(configChanges);
+ dest.writeInt(softInputMode);
}
public static final Parcelable.Creator<ActivityInfo> CREATOR
@@ -328,5 +348,6 @@ public class ActivityInfo extends ComponentInfo
flags = source.readInt();
screenOrientation = source.readInt();
configChanges = source.readInt();
+ softInputMode = source.readInt();
}
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 22d01dc..8d727ed 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -112,6 +112,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* android:allowClearUserData} of the &lt;application&gt; tag.
*/
public static final int FLAG_ALLOW_CLEAR_USER_DATA = 1<<6;
+
+
+ /**
+ * Value for {@link #flags}: default value for the corresponding ActivityInfo flag.
+ * {@hide}
+ */
+ public static final int FLAG_UPDATED_SYSTEM_APP = 1<<7;
/**
* Flags associated with the application. Any combination of
@@ -195,7 +202,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
sb = ab.packageName;
}
- return sCollator.compare(sa, sb);
+ return sCollator.compare(sa.toString(), sb.toString());
}
private final Collator sCollator = Collator.getInstance();
diff --git a/core/java/android/content/pm/ConfigurationInfo.java b/core/java/android/content/pm/ConfigurationInfo.java
new file mode 100755
index 0000000..9115225
--- /dev/null
+++ b/core/java/android/content/pm/ConfigurationInfo.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.content.res.Configuration;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information you can retrieve about hardware configuration preferences
+ * declared by an application. This corresponds to information collected from the
+ * AndroidManifest.xml's &lt;uses-configuration&gt; tags.
+ */
+public class ConfigurationInfo implements Parcelable {
+ /**
+ * The kind of touch screen attached to the device.
+ * One of: {@link android.content.res.Configuration#TOUCHSCREEN_NOTOUCH},
+ * {@link android.content.res.Configuration#TOUCHSCREEN_STYLUS},
+ * {@link android.content.res.Configuration#TOUCHSCREEN_FINGER}.
+ */
+ public int reqTouchScreen;
+
+ /**
+ * Application's input method preference.
+ * One of: {@link android.content.res.Configuration#KEYBOARD_UNDEFINED},
+ * {@link android.content.res.Configuration#KEYBOARD_NOKEYS},
+ * {@link android.content.res.Configuration#KEYBOARD_QWERTY},
+ * {@link android.content.res.Configuration#KEYBOARD_12KEY}
+ */
+ public int reqKeyboardType;
+
+ /**
+ * A flag indicating whether any keyboard is available.
+ * one of: {@link android.content.res.Configuration#NAVIGATION_UNDEFINED},
+ * {@link android.content.res.Configuration#NAVIGATION_DPAD},
+ * {@link android.content.res.Configuration#NAVIGATION_TRACKBALL},
+ * {@link android.content.res.Configuration#NAVIGATION_WHEEL}
+ */
+ public int reqNavigation;
+
+ /**
+ * Value for {@link #reqInputFeatures}: if set, indicates that the application
+ * requires a hard keyboard
+ */
+ public static final int INPUT_FEATURE_HARD_KEYBOARD = 0x00000001;
+
+ /**
+ * Value for {@link #reqInputFeatures}: if set, indicates that the application
+ * requires a hard keyboard
+ */
+ public static final int INPUT_FEATURE_FIVE_WAY_NAV = 0x00000002;
+
+ /**
+ * Flags associated with the application. Any combination of
+ * {@link #INPUT_FEATURE_HARD_KEYBOARD},
+ * {@link #INPUT_FEATURE_FIVE_WAY_NAV}
+ */
+ public int reqInputFeatures = 0;
+
+ public ConfigurationInfo() {
+ }
+
+ public ConfigurationInfo(ConfigurationInfo orig) {
+ reqTouchScreen = orig.reqTouchScreen;
+ reqKeyboardType = orig.reqKeyboardType;
+ reqNavigation = orig.reqNavigation;
+ reqInputFeatures = orig.reqInputFeatures;
+ }
+
+ public String toString() {
+ return "ApplicationHardwarePreferences{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + ", touchscreen = " + reqTouchScreen + "}"
+ + ", inputMethod = " + reqKeyboardType + "}"
+ + ", navigation = " + reqNavigation + "}"
+ + ", reqInputFeatures = " + reqInputFeatures + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeInt(reqTouchScreen);
+ dest.writeInt(reqKeyboardType);
+ dest.writeInt(reqNavigation);
+ dest.writeInt(reqInputFeatures);
+ }
+
+ public static final Creator<ConfigurationInfo> CREATOR =
+ new Creator<ConfigurationInfo>() {
+ public ConfigurationInfo createFromParcel(Parcel source) {
+ return new ConfigurationInfo(source);
+ }
+ public ConfigurationInfo[] newArray(int size) {
+ return new ConfigurationInfo[size];
+ }
+ };
+
+ private ConfigurationInfo(Parcel source) {
+ reqTouchScreen = source.readInt();
+ reqKeyboardType = source.readInt();
+ reqNavigation = source.readInt();
+ reqInputFeatures = source.readInt();
+ }
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index c79655d..fdb2a2f 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -76,6 +76,8 @@ interface IPackageManager {
String getNameForUid(int uid);
+ int getUidForSharedUser(String sharedUserName);
+
ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags);
List<ResolveInfo> queryIntentActivities(in Intent intent,
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index e6745da..30ca002 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -73,6 +73,7 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
dest.writeString(publicSourceDir);
dest.writeString(dataDir);
dest.writeInt((handleProfiling == false) ? 0 : 1);
+ dest.writeInt((functionalTest == false) ? 0 : 1);
}
public static final Parcelable.Creator<InstrumentationInfo> CREATOR
@@ -92,5 +93,6 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
publicSourceDir = source.readString();
dataDir = source.readString();
handleProfiling = source.readInt() != 0;
+ functionalTest = source.readInt() != 0;
}
}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 7d694c7..994afc8 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -103,6 +103,15 @@ public class PackageInfo implements Parcelable {
* in if the flag {@link PackageManager#GET_SIGNATURES} was set.
*/
public Signature[] signatures;
+
+ /**
+ * Application specified preferred configuration
+ * {@link android.R.styleable#AndroidManifestUsesConfiguration
+ * &lt;uses-configuration&gt;} tags included under &lt;manifest&gt;,
+ * or null if there were none. This is only filled in if the flag
+ * {@link PackageManager#GET_CONFIGURATIONS} was set.
+ */
+ public ConfigurationInfo[] configPreferences;
public PackageInfo() {
}
@@ -136,6 +145,7 @@ public class PackageInfo implements Parcelable {
dest.writeTypedArray(permissions, parcelableFlags);
dest.writeStringArray(requestedPermissions);
dest.writeTypedArray(signatures, parcelableFlags);
+ dest.writeTypedArray(configPreferences, parcelableFlags);
}
public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -166,5 +176,6 @@ public class PackageInfo implements Parcelable {
permissions = source.createTypedArray(PermissionInfo.CREATOR);
requestedPermissions = source.createStringArray();
signatures = source.createTypedArray(Signature.CREATOR);
+ configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR);
}
}
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index 406a3eb..46e7ca4 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -90,7 +90,10 @@ public class PackageItemInfo {
return label;
}
}
- return name;
+ if(name != null) {
+ return name;
+ }
+ return packageName;
}
/**
@@ -179,7 +182,7 @@ public class PackageItemInfo {
if (sa == null) sa = aa.name;
CharSequence sb = ab.loadLabel(mPM);
if (sb == null) sb = ab.name;
- return sCollator.compare(sa, sb);
+ return sCollator.compare(sa.toString(), sb.toString());
}
private final Collator sCollator = Collator.getInstance();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index db00a9a..a544550 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -148,6 +148,21 @@ public abstract class PackageManager {
* {@link PackageInfo#permissions}.
*/
public static final int GET_PERMISSIONS = 0x00001000;
+
+ /**
+ * Flag parameter to retrieve all applications(even uninstalled ones) with data directories.
+ * This state could have resulted if applications have been deleted with flag
+ * DONT_DELETE_DATA
+ * with a possibility of being replaced or reinstalled in future
+ */
+ public static final int GET_UNINSTALLED_PACKAGES = 0x00002000;
+
+ /**
+ * {@link PackageInfo} flag: return information about
+ * hardware preferences
+ * {@link PackageInfo#configPreferences}
+ */
+ public static final int GET_CONFIGURATIONS = 0x00004000;
/**
* Permission check result: this is returned by {@link #checkPermission}
@@ -427,16 +442,38 @@ public abstract class PackageManager {
*
* @param packageName The full name (i.e. com.google.apps.contacts) of the
* desired package.
- * @param flags Optional flags to control what information is returned. If
- * 0, none of the optional information is returned.
- *
- * @return Returns a PackageInfo containing information about the package.
- *
+
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_ACTIVITIES},
+ * {@link #GET_GIDS},
+ * {@link #GET_CONFIGURATIONS},
+ * {@link #GET_INSTRUMENTATION},
+ * {@link #GET_PERMISSIONS},
+ * {@link #GET_PROVIDERS},
+ * {@link #GET_RECEIVERS},
+ * {@link #GET_SERVICES},
+ * {@link #GET_SIGNATURES},
+ * {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
+ *
+ * @return Returns a PackageInfo object containing information about the package.
+ * If flag GET_UNINSTALLED_PACKAGES is set and if the package is not
+ * found in the list of installed applications, the package information is
+ * retrieved from the list of uninstalled applications(which includes
+ * installed applications as well as applications
+ * with data directory ie applications which had been
+ * deleted with DONT_DELTE_DATA flag set).
+ *
* @see #GET_ACTIVITIES
+ * @see #GET_GIDS
+ * @see #GET_CONFIGURATIONS
+ * @see #GET_INSTRUMENTATION
+ * @see #GET_PERMISSIONS
+ * @see #GET_PROVIDERS
* @see #GET_RECEIVERS
* @see #GET_SERVICES
- * @see #GET_INSTRUMENTATION
* @see #GET_SIGNATURES
+ * @see #GET_UNINSTALLED_PACKAGES
+ *
*/
public abstract PackageInfo getPackageInfo(String packageName, int flags)
throws NameNotFoundException;
@@ -530,10 +567,23 @@ public abstract class PackageManager {
*
* @param packageName The full name (i.e. com.google.apps.contacts) of an
* application.
- * @param flags Additional option flags. Currently should always be 0.
- *
- * @return {@link ApplicationInfo} containing information about the
- * application.
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+ * {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
+ *
+ * @return {@link ApplicationInfo} Returns ApplicationInfo object containing
+ * information about the package.
+ * If flag GET_UNINSTALLED_PACKAGES is set and if the package is not
+ * found in the list of installed applications,
+ * the application information is retrieved from the
+ * list of uninstalled applications(which includes
+ * installed applications as well as applications
+ * with data directory ie applications which had been
+ * deleted with DONT_DELTE_DATA flag set).
+ *
+ * @see #GET_META_DATA
+ * @see #GET_SHARED_LIBRARY_FILES
+ * @see #GET_UNINSTALLED_PACKAGES
*/
public abstract ApplicationInfo getApplicationInfo(String packageName,
int flags) throws NameNotFoundException;
@@ -548,11 +598,15 @@ public abstract class PackageManager {
* @param className The full name (i.e.
* com.google.apps.contacts.ContactsList) of an Activity
* class.
- * @param flags Additional option flags. Usually 0.
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+ * to modify the data (in ApplicationInfo) returned.
*
* @return {@link ActivityInfo} containing information about the activity.
*
* @see #GET_INTENT_FILTERS
+ * @see #GET_META_DATA
+ * @see #GET_SHARED_LIBRARY_FILES
*/
public abstract ActivityInfo getActivityInfo(ComponentName className,
int flags) throws NameNotFoundException;
@@ -567,11 +621,15 @@ public abstract class PackageManager {
* @param className The full name (i.e.
* com.google.apps.contacts.CalendarAlarm) of a Receiver
* class.
- * @param flags Additional option flags. Usually 0.
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+ * to modify the data returned.
*
* @return {@link ActivityInfo} containing information about the receiver.
*
* @see #GET_INTENT_FILTERS
+ * @see #GET_META_DATA
+ * @see #GET_SHARED_LIBRARY_FILES
*/
public abstract ActivityInfo getReceiverInfo(ComponentName className,
int flags) throws NameNotFoundException;
@@ -586,9 +644,14 @@ public abstract class PackageManager {
* @param className The full name (i.e.
* com.google.apps.media.BackgroundPlayback) of a Service
* class.
- * @param flags Additional option flags. Currently should always be 0.
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+ * to modify the data returned.
*
* @return ServiceInfo containing information about the service.
+ *
+ * @see #GET_META_DATA
+ * @see #GET_SHARED_LIBRARY_FILES
*/
public abstract ServiceInfo getServiceInfo(ComponentName className,
int flags) throws NameNotFoundException;
@@ -597,18 +660,36 @@ public abstract class PackageManager {
* Return a List of all packages that are installed
* on the device.
*
- * @param flags Optional flags to control what information is returned. If
- * 0, none of the optional information is returned.
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_ACTIVITIES},
+ * {@link #GET_GIDS},
+ * {@link #GET_CONFIGURATIONS},
+ * {@link #GET_INSTRUMENTATION},
+ * {@link #GET_PERMISSIONS},
+ * {@link #GET_PROVIDERS},
+ * {@link #GET_RECEIVERS},
+ * {@link #GET_SERVICES},
+ * {@link #GET_SIGNATURES},
+ * {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
*
* @return A List of PackageInfo objects, one for each package that is
* installed on the device. In the unlikely case of there being no
- * installed packages, an empty list is returned.
+ * installed packages, an empty list is returned.
+ * If flag GET_UNINSTALLED_PACKAGES is set, a list of all
+ * applications including those deleted with DONT_DELETE_DATA
+ * (partially installed apps with data directory) will be returned.
*
* @see #GET_ACTIVITIES
+ * @see #GET_GIDS
+ * @see #GET_CONFIGURATIONS
+ * @see #GET_INSTRUMENTATION
+ * @see #GET_PERMISSIONS
+ * @see #GET_PROVIDERS
* @see #GET_RECEIVERS
* @see #GET_SERVICES
- * @see #GET_INSTRUMENTATION
* @see #GET_SIGNATURES
+ * @see #GET_UNINSTALLED_PACKAGES
+ *
*/
public abstract List<PackageInfo> getInstalledPackages(int flags);
@@ -731,16 +812,42 @@ public abstract class PackageManager {
* user id is not currently assigned.
*/
public abstract String getNameForUid(int uid);
+
+ /**
+ * Return the user id associated with a shared user name. Multiple
+ * applications can specify a shared user name in their manifest and thus
+ * end up using a common uid. This might be used for new applications
+ * that use an existing shared user name and need to know the uid of the
+ * shared user.
+ *
+ * @param sharedUserName The shared user name whose uid is to be retrieved.
+ * @return Returns the uid associated with the shared user, or NameNotFoundException
+ * if the shared user name is not being used by any installed packages
+ * @hide
+ */
+ public abstract int getUidForSharedUser(String sharedUserName)
+ throws NameNotFoundException;
/**
* Return a List of all application packages that are installed on the
- * device.
- *
- * @param flags Additional option flags. Currently should always be 0.
+ * device. If flag GET_UNINSTALLED_PACKAGES has been set, a list of all
+ * applications including those deleted with DONT_DELETE_DATA(partially
+ * installed apps with data directory) will be returned.
+ *
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+ * {link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
*
* @return A List of ApplicationInfo objects, one for each application that
* is installed on the device. In the unlikely case of there being
- * no installed applications, an empty list is returned.
+ * no installed applications, an empty list is returned.
+ * If flag GET_UNINSTALLED_PACKAGES is set, a list of all
+ * applications including those deleted with DONT_DELETE_DATA
+ * (partially installed apps with data directory) will be returned.
+ *
+ * @see #GET_META_DATA
+ * @see #GET_SHARED_LIBRARY_FILES
+ * @see #GET_UNINSTALLED_PACKAGES
*/
public abstract List<ApplicationInfo> getInstalledApplications(int flags);
@@ -1139,17 +1246,30 @@ public abstract class PackageManager {
* in a package archive file
*
* @param archiveFilePath The path to the archive file
- * @param flags Optional flags to control what information is returned. If
- * 0, none of the optional information is returned.
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_ACTIVITIES},
+ * {@link #GET_GIDS},
+ * {@link #GET_CONFIGURATIONS},
+ * {@link #GET_INSTRUMENTATION},
+ * {@link #GET_PERMISSIONS},
+ * {@link #GET_PROVIDERS},
+ * {@link #GET_RECEIVERS},
+ * {@link #GET_SERVICES},
+ * {@link #GET_SIGNATURES}, to modify the data returned.
*
* @return Returns the information about the package. Returns
* null if the package could not be successfully parsed.
*
* @see #GET_ACTIVITIES
+ * @see #GET_GIDS
+ * @see #GET_CONFIGURATIONS
+ * @see #GET_INSTRUMENTATION
+ * @see #GET_PERMISSIONS
+ * @see #GET_PROVIDERS
* @see #GET_RECEIVERS
* @see #GET_SERVICES
- * @see #GET_INSTRUMENTATION
* @see #GET_SIGNATURES
+ *
*/
public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
PackageParser packageParser = new PackageParser(archiveFilePath);
@@ -1314,16 +1434,28 @@ public abstract class PackageManager {
* first package on the list is the most preferred, the last is the
* least preferred.
*
- * @param flags Optional flags to control what information is returned. If
- * 0, none of the optional information is returned.
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_ACTIVITIES},
+ * {@link #GET_GIDS},
+ * {@link #GET_CONFIGURATIONS},
+ * {@link #GET_INSTRUMENTATION},
+ * {@link #GET_PERMISSIONS},
+ * {@link #GET_PROVIDERS},
+ * {@link #GET_RECEIVERS},
+ * {@link #GET_SERVICES},
+ * {@link #GET_SIGNATURES}, to modify the data returned.
*
* @return Returns a list of PackageInfo objects describing each
* preferred application, in order of preference.
*
* @see #GET_ACTIVITIES
+ * @see #GET_GIDS
+ * @see #GET_CONFIGURATIONS
+ * @see #GET_INSTRUMENTATION
+ * @see #GET_PERMISSIONS
+ * @see #GET_PROVIDERS
* @see #GET_RECEIVERS
* @see #GET_SERVICES
- * @see #GET_INSTRUMENTATION
* @see #GET_SIGNATURES
*/
public abstract List<PackageInfo> getPreferredPackages(int flags);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5a90261..e08f1d1 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -23,6 +23,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.AssetManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -104,6 +105,15 @@ public class PackageParser {
if ((flags&PackageManager.GET_GIDS) != 0) {
pi.gids = gids;
}
+ if ((flags&PackageManager.GET_CONFIGURATIONS) != 0) {
+ int N = p.configPreferences.size();
+ if (N > 0) {
+ pi.configPreferences = new ConfigurationInfo[N];
+ for (int i=0; i<N; i++) {
+ pi.configPreferences[i] = p.configPreferences.get(i);
+ }
+ }
+ }
if ((flags&PackageManager.GET_ACTIVITIES) != 0) {
int N = p.activities.size();
if (N > 0) {
@@ -245,18 +255,24 @@ public class PackageParser {
XmlResourceParser parser = null;
AssetManager assmgr = null;
+ boolean assetError = true;
try {
assmgr = new AssetManager();
- assmgr.addAssetPath(mArchiveSourcePath);
- parser = assmgr.openXmlResourceParser("AndroidManifest.xml");
+ if(assmgr.addAssetPath(mArchiveSourcePath) != 0) {
+ parser = assmgr.openXmlResourceParser("AndroidManifest.xml");
+ assetError = false;
+ } else {
+ Log.w(TAG, "Failed adding asset path:"+mArchiveSourcePath);
+ }
} catch (Exception e) {
- if (assmgr != null) assmgr.close();
Log.w(TAG, "Unable to read AndroidManifest.xml of "
+ mArchiveSourcePath, e);
+ }
+ if(assetError) {
+ if (assmgr != null) assmgr.close();
mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
return null;
}
-
String[] errorText = new String[1];
Package pkg = null;
Exception errorException = null;
@@ -626,7 +642,35 @@ public class PackageParser {
XmlUtils.skipCurrentTag(parser);
- } else if (tagName.equals("uses-sdk")) {
+ } else if (tagName.equals("uses-configuration")) {
+ ConfigurationInfo cPref = new ConfigurationInfo();
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestUsesConfiguration);
+ cPref.reqTouchScreen = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqTouchScreen,
+ Configuration.TOUCHSCREEN_UNDEFINED);
+ cPref.reqKeyboardType = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqKeyboardType,
+ Configuration.KEYBOARD_UNDEFINED);
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqHardKeyboard,
+ false)) {
+ cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD;
+ }
+ cPref.reqNavigation = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqNavigation,
+ Configuration.NAVIGATION_UNDEFINED);
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestUsesConfiguration_reqFiveWayNav,
+ false)) {
+ cPref.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV;
+ }
+ sa.recycle();
+ pkg.configPreferences.add(cPref);
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals("uses-sdk")) {
if (mSdkVersion > 0) {
sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestUsesSdk);
@@ -650,6 +694,10 @@ public class PackageParser {
if (parseInstrumentation(pkg, res, parser, attrs, outError) == null) {
return null;
}
+ } else if (tagName.equals("eat-comment")) {
+ // Just skip this tag
+ XmlUtils.skipCurrentTag(parser);
+ continue;
} else if (RIGID_PARSER) {
outError[0] = "Bad element under <manifest>: "
+ parser.getName();
@@ -1038,11 +1086,6 @@ public class PackageParser {
if (outError[0] != null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
- } else if (ai.processName != null && !ai.processName.equals(ai.packageName)
- && ai.className != null) {
- Log.w(TAG, "In package " + ai.packageName
- + " <application> specifies both a name and a process; ignoring the process");
- ai.processName = null;
}
final int innerDepth = parser.getDepth();
@@ -1258,6 +1301,12 @@ public class PackageParser {
}
if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_noHistory,
+ false)) {
+ a.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
+ }
+
+ if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestActivity_alwaysRetainTaskState,
false)) {
a.info.flags |= ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE;
@@ -1291,6 +1340,9 @@ public class PackageParser {
a.info.configChanges = sa.getInt(
com.android.internal.R.styleable.AndroidManifestActivity_configChanges,
0);
+ a.info.softInputMode = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestActivity_windowSoftInputMode,
+ 0);
} else {
a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
a.info.configChanges = 0;
@@ -2002,6 +2054,12 @@ public class PackageParser {
// Additional data supplied by callers.
public Object mExtras;
+
+ /*
+ * Applications hardware preferences
+ */
+ public final ArrayList<ConfigurationInfo> configPreferences =
+ new ArrayList<ConfigurationInfo>();
public Package(String _name) {
packageName = _name;
@@ -2031,7 +2089,7 @@ public class PackageParser {
metaData = clone.metaData;
}
}
-
+
public final static class Permission extends Component<IntentInfo> {
public final PermissionInfo info;
public boolean tree;
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index ee88c89..4b1e678 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -16,6 +16,10 @@
package android.content.res;
+import com.google.android.collect.Lists;
+
+import com.android.internal.util.ArrayUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -30,8 +34,6 @@ import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Arrays;
-import com.android.internal.util.ArrayUtils;
-
/**
*
* Lets you map {@link android.view.View} state sets to colors.
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 78a90de..7e4b7ac 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -34,6 +34,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration
*/
public Locale locale;
+ /**
+ * Locale should persist on setting
+ * @hide pending API council approval
+ */
+ public boolean userSetLocale;
+
public static final int TOUCHSCREEN_UNDEFINED = 0;
public static final int TOUCHSCREEN_NOTOUCH = 1;
public static final int TOUCHSCREEN_STYLUS = 2;
@@ -60,14 +66,30 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public static final int KEYBOARDHIDDEN_UNDEFINED = 0;
public static final int KEYBOARDHIDDEN_NO = 1;
public static final int KEYBOARDHIDDEN_YES = 2;
+ /** Constant matching actual resource implementation. {@hide} */
+ public static final int KEYBOARDHIDDEN_SOFT = 3;
/**
- * A flag indicating whether the keyboard has been hidden. This will
- * be set on a device with a mechanism to hide the keyboard from the
- * user, when that mechanism is closed.
+ * A flag indicating whether any keyboard is available. Unlike
+ * {@link #hardKeyboardHidden}, this also takes into account a soft
+ * keyboard, so if the hard keyboard is hidden but there is soft
+ * keyboard available, it will be set to NO. Value is one of:
+ * {@link #KEYBOARDHIDDEN_NO}, {@link #KEYBOARDHIDDEN_YES}.
*/
public int keyboardHidden;
+ public static final int HARDKEYBOARDHIDDEN_UNDEFINED = 0;
+ public static final int HARDKEYBOARDHIDDEN_NO = 1;
+ public static final int HARDKEYBOARDHIDDEN_YES = 2;
+
+ /**
+ * A flag indicating whether the hard keyboard has been hidden. This will
+ * be set on a device with a mechanism to hide the keyboard from the
+ * user, when that mechanism is closed. One of:
+ * {@link #HARDKEYBOARDHIDDEN_NO}, {@link #HARDKEYBOARDHIDDEN_YES}.
+ */
+ public int hardKeyboardHidden;
+
public static final int NAVIGATION_UNDEFINED = 0;
public static final int NAVIGATION_NONAV = 1;
public static final int NAVIGATION_DPAD = 2;
@@ -111,9 +133,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if (o.locale != null) {
locale = (Locale) o.locale.clone();
}
+ userSetLocale = o.userSetLocale;
touchscreen = o.touchscreen;
keyboard = o.keyboard;
keyboardHidden = o.keyboardHidden;
+ hardKeyboardHidden = o.hardKeyboardHidden;
navigation = o.navigation;
orientation = o.orientation;
}
@@ -122,7 +146,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
return "{ scale=" + fontScale + " imsi=" + mcc + "/" + mnc
+ " locale=" + locale
+ " touch=" + touchscreen + " key=" + keyboard + "/"
- + keyboardHidden
+ + keyboardHidden + "/" + hardKeyboardHidden
+ " nav=" + navigation + " orien=" + orientation + " }";
}
@@ -133,9 +157,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
fontScale = 1;
mcc = mnc = 0;
locale = Locale.getDefault();
+ userSetLocale = false;
touchscreen = TOUCHSCREEN_UNDEFINED;
keyboard = KEYBOARD_UNDEFINED;
keyboardHidden = KEYBOARDHIDDEN_UNDEFINED;
+ hardKeyboardHidden = HARDKEYBOARDHIDDEN_UNDEFINED;
navigation = NAVIGATION_UNDEFINED;
orientation = ORIENTATION_UNDEFINED;
}
@@ -173,6 +199,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
locale = delta.locale != null
? (Locale) delta.locale.clone() : null;
}
+ if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))
+ {
+ userSetLocale = true;
+ changed |= ActivityInfo.CONFIG_LOCALE;
+ }
if (delta.touchscreen != TOUCHSCREEN_UNDEFINED
&& touchscreen != delta.touchscreen) {
changed |= ActivityInfo.CONFIG_TOUCHSCREEN;
@@ -188,6 +219,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
keyboardHidden = delta.keyboardHidden;
}
+ if (delta.hardKeyboardHidden != HARDKEYBOARDHIDDEN_UNDEFINED
+ && hardKeyboardHidden != delta.hardKeyboardHidden) {
+ changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+ hardKeyboardHidden = delta.hardKeyboardHidden;
+ }
if (delta.navigation != NAVIGATION_UNDEFINED
&& navigation != delta.navigation) {
changed |= ActivityInfo.CONFIG_NAVIGATION;
@@ -252,6 +288,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
&& keyboardHidden != delta.keyboardHidden) {
changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
}
+ if (delta.hardKeyboardHidden != HARDKEYBOARDHIDDEN_UNDEFINED
+ && hardKeyboardHidden != delta.hardKeyboardHidden) {
+ changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
+ }
if (delta.navigation != NAVIGATION_UNDEFINED
&& navigation != delta.navigation) {
changed |= ActivityInfo.CONFIG_NAVIGATION;
@@ -298,9 +338,15 @@ public final class Configuration implements Parcelable, Comparable<Configuration
dest.writeString(locale.getCountry());
dest.writeString(locale.getVariant());
}
+ if(userSetLocale) {
+ dest.writeInt(1);
+ } else {
+ dest.writeInt(0);
+ }
dest.writeInt(touchscreen);
dest.writeInt(keyboard);
dest.writeInt(keyboardHidden);
+ dest.writeInt(hardKeyboardHidden);
dest.writeInt(navigation);
dest.writeInt(orientation);
}
@@ -327,9 +373,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
locale = new Locale(source.readString(), source.readString(),
source.readString());
}
+ userSetLocale = (source.readInt()==1);
touchscreen = source.readInt();
keyboard = source.readInt();
keyboardHidden = source.readInt();
+ hardKeyboardHidden = source.readInt();
navigation = source.readInt();
orientation = source.readInt();
}
@@ -356,6 +404,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if (n != 0) return n;
n = this.keyboardHidden - that.keyboardHidden;
if (n != 0) return n;
+ n = this.hardKeyboardHidden - that.hardKeyboardHidden;
+ if (n != 0) return n;
n = this.navigation - that.navigation;
if (n != 0) return n;
n = this.orientation - that.orientation;
@@ -380,7 +430,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public int hashCode() {
return ((int)this.fontScale) + this.mcc + this.mnc
+ this.locale.hashCode() + this.touchscreen
- + this.keyboard + this.keyboardHidden + this.navigation
- + this.orientation;
+ + this.keyboard + this.keyboardHidden + this.hardKeyboardHidden
+ + this.navigation + this.orientation;
}
-} \ No newline at end of file
+}
diff --git a/core/java/android/content/res/PluralRules.java b/core/java/android/content/res/PluralRules.java
index 2d09ef0..2dce3c1 100644
--- a/core/java/android/content/res/PluralRules.java
+++ b/core/java/android/content/res/PluralRules.java
@@ -23,7 +23,7 @@ import java.util.Locale;
* object has been integrated to android, we should switch to that. For now, yuck-o.
*/
-abstract class PluralRule {
+abstract class PluralRules {
static final int QUANTITY_OTHER = 0x0000;
static final int QUANTITY_ZERO = 0x0001;
@@ -37,7 +37,7 @@ abstract class PluralRule {
abstract int quantityForNumber(int n);
final int attrForNumber(int n) {
- return PluralRule.attrForQuantity(quantityForNumber(n));
+ return PluralRules.attrForQuantity(quantityForNumber(n));
}
static final int attrForQuantity(int quantity) {
@@ -69,7 +69,7 @@ abstract class PluralRule {
}
}
- static final PluralRule ruleForLocale(Locale locale) {
+ static final PluralRules ruleForLocale(Locale locale) {
String lang = locale.getLanguage();
if ("cs".equals(lang)) {
if (cs == null) cs = new cs();
@@ -81,8 +81,8 @@ abstract class PluralRule {
}
}
- private static PluralRule cs;
- private static class cs extends PluralRule {
+ private static PluralRules cs;
+ private static class cs extends PluralRules {
int quantityForNumber(int n) {
if (n == 1) {
return QUANTITY_ONE;
@@ -96,8 +96,8 @@ abstract class PluralRule {
}
}
- private static PluralRule en;
- private static class en extends PluralRule {
+ private static PluralRules en;
+ private static class en extends PluralRules {
int quantityForNumber(int n) {
if (n == 1) {
return QUANTITY_ONE;
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 1014eee..10eced6 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -17,9 +17,16 @@
package android.content.res;
+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;
+import android.os.Bundle;
import android.os.SystemProperties;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -27,6 +34,7 @@ import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
+import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
@@ -70,7 +78,7 @@ public class Resources {
/*package*/ final AssetManager mAssets;
private final Configuration mConfiguration = new Configuration();
/*package*/ final DisplayMetrics mMetrics = new DisplayMetrics();
- PluralRule mPluralRule;
+ PluralRules mPluralRule;
/**
* This exception is thrown by the resource APIs when a requested resource
@@ -157,24 +165,24 @@ public class Resources {
* possibly styled text information.
*/
public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
- PluralRule rule = getPluralRule();
+ PluralRules rule = getPluralRule();
CharSequence res = mAssets.getResourceBagText(id, rule.attrForNumber(quantity));
if (res != null) {
return res;
}
- res = mAssets.getResourceBagText(id, PluralRule.ID_OTHER);
+ res = mAssets.getResourceBagText(id, PluralRules.ID_OTHER);
if (res != null) {
return res;
}
throw new NotFoundException("Plural resource ID #0x" + Integer.toHexString(id)
+ " quantity=" + quantity
- + " item=" + PluralRule.stringForQuantity(rule.quantityForNumber(quantity)));
+ + " item=" + PluralRules.stringForQuantity(rule.quantityForNumber(quantity)));
}
- private PluralRule getPluralRule() {
+ private PluralRules getPluralRule() {
synchronized (mSync) {
if (mPluralRule == null) {
- mPluralRule = PluralRule.ruleForLocale(mConfiguration.locale);
+ mPluralRule = PluralRules.ruleForLocale(mConfiguration.locale);
}
return mPluralRule;
}
@@ -573,6 +581,33 @@ public class Resources {
}
/**
+ * Return a boolean associated with a particular resource ID. This can be
+ * used with any integral resource value, and will return true if it is
+ * non-zero.
+ *
+ * @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.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ *
+ * @return Returns the boolean value contained in the resource.
+ */
+ public boolean getBoolean(int id) throws NotFoundException {
+ synchronized (mTmpValue) {
+ TypedValue value = mTmpValue;
+ getValue(id, value, true);
+ if (value.type >= TypedValue.TYPE_FIRST_INT
+ && value.type <= TypedValue.TYPE_LAST_INT) {
+ return value.data != 0;
+ }
+ throw new NotFoundException(
+ "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ + Integer.toHexString(value.type) + " is not valid");
+ }
+ }
+
+ /**
* Return an integer associated with a particular resource ID.
*
* @param id The desired resource identifier, as generated by the aapt
@@ -1157,12 +1192,18 @@ public class Resources {
width = mMetrics.heightPixels;
height = mMetrics.widthPixels;
}
+ int keyboardHidden = mConfiguration.keyboardHidden;
+ if (keyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO
+ && mConfiguration.hardKeyboardHidden
+ == Configuration.HARDKEYBOARDHIDDEN_YES) {
+ keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
+ }
mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
locale, mConfiguration.orientation,
mConfiguration.touchscreen,
(int)(mMetrics.density*160), mConfiguration.keyboard,
- mConfiguration.keyboardHidden,
- mConfiguration.navigation, width, height, sSdkVersion);
+ keyboardHidden, mConfiguration.navigation, width, height,
+ sSdkVersion);
int N = mDrawableCache.size();
if (DEBUG_CONFIG) {
Log.d(TAG, "Cleaning up drawables config changes: 0x"
@@ -1198,12 +1239,26 @@ public class Resources {
}
synchronized (mSync) {
if (mPluralRule != null) {
- mPluralRule = PluralRule.ruleForLocale(config.locale);
+ mPluralRule = PluralRules.ruleForLocale(config.locale);
}
}
}
/**
+ * Update the system resources configuration if they have previously
+ * been initialized.
+ *
+ * @hide
+ */
+ public static void updateSystemConfiguration(Configuration config, DisplayMetrics metrics) {
+ if (mSystem != null) {
+ mSystem.updateConfiguration(config, metrics);
+ //Log.i(TAG, "Updated system resources " + mSystem
+ // + ": " + mSystem.getConfiguration());
+ }
+ }
+
+ /**
* Return the current display metrics that are in effect for this resource
* object. The returned object should be treated as read-only.
*
@@ -1330,6 +1385,102 @@ public class Resources {
}
/**
+ * Parse a series of {@link android.R.styleable#Extra &lt;extra&gt;} tags from
+ * an XML file. You call this when you are at the parent tag of the
+ * extra tags, and it return once all of the child tags have been parsed.
+ * This will call {@link #parseBundleExtra} for each extra tag encountered.
+ *
+ * @param parser The parser from which to retrieve the extras.
+ * @param outBundle A Bundle in which to place all parsed extras.
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
+ public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle)
+ throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String nodeName = parser.getName();
+ if (nodeName.equals("extra")) {
+ parseBundleExtra("extra", parser, outBundle);
+ XmlUtils.skipCurrentTag(parser);
+
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ /**
+ * Parse a name/value pair out of an XML tag holding that data. The
+ * AttributeSet must be holding the data defined by
+ * {@link android.R.styleable#Extra}. The following value types are supported:
+ * <ul>
+ * <li> {@link TypedValue#TYPE_STRING}:
+ * {@link Bundle#putCharSequence Bundle.putCharSequence()}
+ * <li> {@link TypedValue#TYPE_INT_BOOLEAN}:
+ * {@link Bundle#putCharSequence Bundle.putBoolean()}
+ * <li> {@link TypedValue#TYPE_FIRST_INT}-{@link TypedValue#TYPE_LAST_INT}:
+ * {@link Bundle#putCharSequence Bundle.putBoolean()}
+ * <li> {@link TypedValue#TYPE_FLOAT}:
+ * {@link Bundle#putCharSequence Bundle.putFloat()}
+ * </ul>
+ *
+ * @param tagName The name of the tag these attributes come from; this is
+ * only used for reporting error messages.
+ * @param attrs The attributes from which to retrieve the name/value pair.
+ * @param outBundle The Bundle in which to place the parsed value.
+ * @throws XmlPullParserException If the attributes are not valid.
+ */
+ public void parseBundleExtra(String tagName, AttributeSet attrs,
+ Bundle outBundle) throws XmlPullParserException {
+ TypedArray sa = obtainAttributes(attrs,
+ com.android.internal.R.styleable.Extra);
+
+ String name = sa.getString(
+ com.android.internal.R.styleable.Extra_name);
+ if (name == null) {
+ sa.recycle();
+ throw new XmlPullParserException("<" + tagName
+ + "> requires an android:name attribute at "
+ + attrs.getPositionDescription());
+ }
+
+ TypedValue v = sa.peekValue(
+ com.android.internal.R.styleable.Extra_value);
+ if (v != null) {
+ if (v.type == TypedValue.TYPE_STRING) {
+ CharSequence cs = v.coerceToString();
+ outBundle.putCharSequence(name, cs);
+ } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) {
+ outBundle.putBoolean(name, v.data != 0);
+ } else if (v.type >= TypedValue.TYPE_FIRST_INT
+ && v.type <= TypedValue.TYPE_LAST_INT) {
+ outBundle.putInt(name, v.data);
+ } else if (v.type == TypedValue.TYPE_FLOAT) {
+ outBundle.putFloat(name, v.getFloat());
+ } else {
+ sa.recycle();
+ throw new XmlPullParserException("<" + tagName
+ + "> only supports string, integer, float, color, and boolean at "
+ + attrs.getPositionDescription());
+ }
+ } else {
+ sa.recycle();
+ throw new XmlPullParserException("<" + tagName
+ + "> requires an android:value or android:resource attribute at "
+ + attrs.getPositionDescription());
+ }
+
+ sa.recycle();
+ }
+
+ /**
* Retrieve underlying AssetManager storage for these resources.
*/
public final AssetManager getAssets() {
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index da32cc8..3df7708 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -96,6 +96,7 @@ final class StringBlock {
mStyleIDs.subId = nativeIndexOfString(mNative, "sub");
mStyleIDs.strikeId = nativeIndexOfString(mNative, "strike");
mStyleIDs.listItemId = nativeIndexOfString(mNative, "li");
+ mStyleIDs.marqueeId = nativeIndexOfString(mNative, "marquee");
if (localLOGV) Log.v(TAG, "BoldId=" + mStyleIDs.boldId
+ ", ItalicId=" + mStyleIDs.italicId
@@ -127,6 +128,7 @@ final class StringBlock {
private int supId;
private int strikeId;
private int listItemId;
+ private int marqueeId;
}
private CharSequence applyStyles(String str, int[] style, StyleIDs ids) {
@@ -179,6 +181,10 @@ final class StringBlock {
buffer.setSpan(new BulletSpan(10),
style[i+1], style[i+2]+1,
Spannable.SPAN_PARAGRAPH);
+ } else if (type == ids.marqueeId) {
+ buffer.setSpan(TextUtils.TruncateAt.MARQUEE,
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_INCLUSIVE_INCLUSIVE);
} else {
String tag = nativeGetString(mNative, type);
@@ -216,6 +222,15 @@ final class StringBlock {
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
+ } else if (tag.startsWith("a;")) {
+ String sub;
+
+ sub = subtag(tag, ";href=");
+ if (sub != null) {
+ buffer.setSpan(new URLSpan(sub),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
}
}
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index e81f7f8..76f0860 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -21,6 +21,10 @@ import android.net.Uri;
import android.util.Config;
import android.util.Log;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
import java.lang.ref.WeakReference;
import java.lang.UnsupportedOperationException;
@@ -457,9 +461,24 @@ public abstract class AbstractCursor implements CrossProcessCursor {
mContentObservable.unregisterObserver(observer);
}
}
-
+
+ /**
+ * @hide pending API council approval
+ */
+ protected void notifyDataSetChange() {
+ mDataSetObservable.notifyChanged();
+ }
+
+ /**
+ * @hide pending API council approval
+ */
+ protected DataSetObservable getDataSetObservable() {
+ return mDataSetObservable;
+
+ }
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
+
}
public void unregisterDataSetObserver(DataSetObserver observer) {
diff --git a/core/java/android/database/AbstractWindowedCursor.java b/core/java/android/database/AbstractWindowedCursor.java
index 1ec4312..4ac0aef 100644
--- a/core/java/android/database/AbstractWindowedCursor.java
+++ b/core/java/android/database/AbstractWindowedCursor.java
@@ -172,7 +172,7 @@ public abstract class AbstractWindowedCursor extends AbstractCursor
super.checkPosition();
if (mWindow == null) {
- throw new StaleDataException("This cursor has changed, you must call requery()");
+ throw new StaleDataException("Access closed cursor");
}
}
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index 72dc3a9..8e26730 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -409,8 +409,13 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* change across a call to clear().
*/
public void clear() {
- mStartPos = 0;
- native_clear();
+ acquireReference();
+ try {
+ mStartPos = 0;
+ native_clear();
+ } finally {
+ releaseReference();
+ }
}
/** Clears out the native side of things */
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index ab0dc3f..2ff7294 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -241,6 +241,21 @@ public class DatabaseUtils {
}
/**
+ * Concatenates two SQL WHERE clauses, handling empty or null values.
+ * @hide
+ */
+ public static String concatenateWhere(String a, String b) {
+ if (TextUtils.isEmpty(a)) {
+ return b;
+ }
+ if (TextUtils.isEmpty(b)) {
+ return a;
+ }
+
+ return "(" + a + ") AND (" + b + ")";
+ }
+
+ /**
* return the collation key
* @param name
* @return the collation key
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index ae2fc95..70b9b83 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -18,7 +18,12 @@ package android.database.sqlite;
import android.database.AbstractWindowedCursor;
import android.database.CursorWindow;
+import android.database.DataSetObserver;
import android.database.SQLException;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.Process;
import android.text.TextUtils;
import android.util.Config;
import android.util.Log;
@@ -26,6 +31,7 @@ import android.util.Log;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
/**
* A Cursor implementation that exposes results from a query on a
@@ -59,7 +65,131 @@ public class SQLiteCursor extends AbstractWindowedCursor {
/** Used to find out where a cursor was allocated in case it never got
* released. */
private StackTraceElement[] mStackTraceElements;
-
+
+ /**
+ * mMaxRead is the max items that each cursor window reads
+ * default to a very high value
+ */
+ private int mMaxRead = Integer.MAX_VALUE;
+ private int mInitialRead = Integer.MAX_VALUE;
+ private int mCursorState = 0;
+ private ReentrantLock mLock = null;
+ private boolean mPendingData = false;
+
+ /**
+ * support for a cursor variant that doesn't always read all results
+ * initialRead is the initial number of items that cursor window reads
+ * if query contains more than this number of items, a thread will be
+ * created and handle the left over items so that caller can show
+ * results as soon as possible
+ * @param initialRead initial number of items that cursor read
+ * @param maxRead leftover items read at maxRead items per time
+ * @hide
+ */
+ public void setLoadStyle(int initialRead, int maxRead) {
+ mMaxRead = maxRead;
+ mInitialRead = initialRead;
+ mLock = new ReentrantLock(true);
+ }
+
+ private void queryThreadLock() {
+ if (mLock != null) {
+ mLock.lock();
+ }
+ }
+
+ private void queryThreadUnlock() {
+ if (mLock != null) {
+ mLock.unlock();
+ }
+ }
+
+
+ /**
+ * @hide
+ */
+ final private class QueryThread implements Runnable {
+ private final int mThreadState;
+ QueryThread(int version) {
+ mThreadState = version;
+ }
+ private void sendMessage() {
+ if (mNotificationHandler != null) {
+ mNotificationHandler.sendEmptyMessage(1);
+ mPendingData = false;
+ } else {
+ mPendingData = true;
+ }
+
+ }
+ public void run() {
+ // use cached mWindow, to avoid get null mWindow
+ CursorWindow cw = mWindow;
+ Process.setThreadPriority(Process.myTid(), Process.THREAD_PRIORITY_BACKGROUND);
+ // the cursor's state doesn't change
+ while (true) {
+ mLock.lock();
+ if (mCursorState != mThreadState) {
+ mLock.unlock();
+ break;
+ }
+ try {
+ int count = mQuery.fillWindow(cw, mMaxRead, mCount);
+ // return -1 means not finished
+ if (count != 0) {
+ if (count == NO_COUNT){
+ mCount += mMaxRead;
+ sendMessage();
+ } else {
+ mCount = count;
+ sendMessage();
+ break;
+ }
+ } else {
+ break;
+ }
+ } catch (Exception e) {
+ // end the tread when the cursor is close
+ break;
+ } finally {
+ mLock.unlock();
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ protected class MainThreadNotificationHandler extends Handler {
+ public void handleMessage(Message msg) {
+ notifyDataSetChange();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ protected MainThreadNotificationHandler mNotificationHandler;
+
+ public void registerDataSetObserver(DataSetObserver observer) {
+ super.registerDataSetObserver(observer);
+ if ((Integer.MAX_VALUE != mMaxRead || Integer.MAX_VALUE != mInitialRead) &&
+ mNotificationHandler == null) {
+ queryThreadLock();
+ try {
+ mNotificationHandler = new MainThreadNotificationHandler();
+ if (mPendingData) {
+ notifyDataSetChange();
+ mPendingData = false;
+ }
+ } finally {
+ queryThreadUnlock();
+ }
+ }
+
+ }
+
/**
* Execute a query and provide access to its result set through a Cursor
* interface. For a query such as: {@code SELECT name, birth, phone FROM
@@ -146,11 +276,22 @@ public class SQLiteCursor extends AbstractWindowedCursor {
// If there isn't a window set already it will only be accessed locally
mWindow = new CursorWindow(true /* the window is local only */);
} else {
- mWindow.clear();
+ mCursorState++;
+ queryThreadLock();
+ try {
+ mWindow.clear();
+ } finally {
+ queryThreadUnlock();
+ }
}
-
- // mWindow must be cleared
- mCount = mQuery.fillWindow(mWindow, startPos);
+ mWindow.setStartPosition(startPos);
+ mCount = mQuery.fillWindow(mWindow, mInitialRead, 0);
+ // return -1 means not finished
+ if (mCount == NO_COUNT){
+ mCount = startPos + mInitialRead;
+ Thread t = new Thread(new QueryThread(mCursorState), "query thread");
+ t.start();
+ }
}
@Override
@@ -344,6 +485,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
private void deactivateCommon() {
if (Config.LOGV) Log.v(TAG, "<<< Releasing cursor " + this);
+ mCursorState = 0;
if (mWindow != null) {
mWindow.close();
mWindow = null;
@@ -368,6 +510,9 @@ public class SQLiteCursor extends AbstractWindowedCursor {
@Override
public boolean requery() {
+ if (isClosed()) {
+ return false;
+ }
long timeStart = 0;
if (Config.LOGV) {
timeStart = System.currentTimeMillis();
@@ -385,8 +530,13 @@ public class SQLiteCursor extends AbstractWindowedCursor {
// This one will recreate the temp table, and get its count
mDriver.cursorRequeried(this);
mCount = NO_COUNT;
- // Requery the program that runs over the temp table
- mQuery.requery();
+ mCursorState++;
+ queryThreadLock();
+ try {
+ mQuery.requery();
+ } finally {
+ queryThreadUnlock();
+ }
} finally {
mDatabase.unlock();
}
@@ -405,9 +555,15 @@ public class SQLiteCursor extends AbstractWindowedCursor {
}
@Override
- public void setWindow(CursorWindow window) {
+ public void setWindow(CursorWindow window) {
if (mWindow != null) {
- mWindow.close();
+ mCursorState++;
+ queryThreadLock();
+ try {
+ mWindow.close();
+ } finally {
+ queryThreadUnlock();
+ }
mCount = NO_COUNT;
}
mWindow = window;
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index e497190..fa062c8 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -54,6 +54,69 @@ public class SQLiteDatabase extends SQLiteClosable {
private final static String TAG = "Database";
/**
+ * Algorithms used in ON CONFLICT clause
+ * http://www.sqlite.org/lang_conflict.html
+ * @hide
+ */
+ public enum ConflictAlgorithm {
+ /**
+ * When a constraint violation occurs, an immediate ROLLBACK occurs,
+ * thus ending the current transaction, and the command aborts with a
+ * return code of SQLITE_CONSTRAINT. If no transaction is active
+ * (other than the implied transaction that is created on every command)
+ * then this algorithm works the same as ABORT.
+ */
+ ROLLBACK("ROLLBACK"),
+
+ /**
+ * When a constraint violation occurs,no ROLLBACK is executed
+ * so changes from prior commands within the same transaction
+ * are preserved. This is the default behavior.
+ */
+ ABORT("ABORT"),
+
+ /**
+ * When a constraint violation occurs, the command aborts with a return
+ * code SQLITE_CONSTRAINT. But any changes to the database that
+ * the command made prior to encountering the constraint violation
+ * are preserved and are not backed out.
+ */
+ FAIL("FAIL"),
+
+ /**
+ * When a constraint violation occurs, the one row that contains
+ * the constraint violation is not inserted or changed.
+ * But the command continues executing normally. Other rows before and
+ * after the row that contained the constraint violation continue to be
+ * inserted or updated normally. No error is returned.
+ */
+ IGNORE("IGNORE"),
+
+ /**
+ * When a UNIQUE constraint violation occurs, the pre-existing rows that
+ * are causing the constraint violation are removed prior to inserting
+ * or updating the current row. Thus the insert or update always occurs.
+ * The command continues executing normally. No error is returned.
+ * If a NOT NULL constraint violation occurs, the NULL value is replaced
+ * by the default value for that column. If the column has no default
+ * value, then the ABORT algorithm is used. If a CHECK constraint
+ * violation occurs then the IGNORE algorithm is used. When this conflict
+ * resolution strategy deletes rows in order to satisfy a constraint,
+ * it does not invoke delete triggers on those rows.
+ * This behavior might change in a future release.
+ */
+ REPLACE("REPLACE");
+
+ private final String mValue;
+ ConflictAlgorithm(String value) {
+ mValue = value;
+ }
+ public String value() {
+ return mValue;
+ }
+ }
+
+ /**
* Maximum Length Of A LIKE Or GLOB Pattern
* The pattern matching algorithm used in the default LIKE and GLOB implementation
* of SQLite can exhibit O(N^2) performance (where N is the number of characters in
@@ -437,8 +500,26 @@ public class SQLiteDatabase extends SQLiteClosable {
* successful so far. Do not call setTransactionSuccessful before calling this. When this
* returns a new transaction will have been created but not marked as successful.
* @return true if the transaction was yielded
+ * @deprecated if the db is locked more than once (becuase of nested transactions) then the lock
+ * will not be yielded. Use yieldIfContendedSafely instead.
*/
public boolean yieldIfContended() {
+ return yieldIfContendedHelper(false /* do not check yielding */);
+ }
+
+ /**
+ * Temporarily end the transaction to let other threads run. The transaction is assumed to be
+ * successful so far. Do not call setTransactionSuccessful before calling this. When this
+ * returns a new transaction will have been created but not marked as successful. This assumes
+ * that there are no nested transactions (beginTransaction has only been called once) and will
+ * through an exception if that is not the case.
+ * @return true if the transaction was yielded
+ */
+ public boolean yieldIfContendedSafely() {
+ return yieldIfContendedHelper(true /* check yielding */);
+ }
+
+ private boolean yieldIfContendedHelper(boolean checkFullyYielded) {
if (mLock.getQueueLength() == 0) {
// Reset the lock acquire time since we know that the thread was willing to yield
// the lock at this time.
@@ -448,6 +529,12 @@ public class SQLiteDatabase extends SQLiteClosable {
}
setTransactionSuccessful();
endTransaction();
+ if (checkFullyYielded) {
+ if (this.isDbLockedByCurrentThread()) {
+ throw new IllegalStateException(
+ "Db locked more than once. yielfIfContended cannot yield");
+ }
+ }
beginTransaction();
return true;
}
@@ -1031,6 +1118,28 @@ public class SQLiteDatabase extends SQLiteClosable {
}
/**
+ * Runs the provided SQL and returns a cursor over the result set.
+ * The cursor will read an initial set of rows and the return to the caller.
+ * It will continue to read in batches and send data changed notifications
+ * when the later batches are ready.
+ * @param sql the SQL query. The SQL string must not be ; terminated
+ * @param selectionArgs You may include ?s in where clause in the query,
+ * which will be replaced by the values from selectionArgs. The
+ * values will be bound as Strings.
+ * @param initialRead set the initial count of items to read from the cursor
+ * @param maxRead set the count of items to read on each iteration after the first
+ * @return A {@link Cursor} object, which is positioned before the first entry
+ * @hide pending API council approval
+ */
+ public Cursor rawQuery(String sql, String[] selectionArgs,
+ int initialRead, int maxRead) {
+ SQLiteCursor c = (SQLiteCursor)rawQueryWithFactory(
+ null, sql, selectionArgs, null);
+ c.setLoadStyle(initialRead, maxRead);
+ return c;
+ }
+
+ /**
* Convenience method for inserting a row into the database.
*
* @param table the table to insert the row into
@@ -1044,7 +1153,7 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
public long insert(String table, String nullColumnHack, ContentValues values) {
try {
- return insertOrReplace(table, nullColumnHack, values, false);
+ return insertWithOnConflict(table, nullColumnHack, values, null);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + values, e);
return -1;
@@ -1066,7 +1175,7 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
public long insertOrThrow(String table, String nullColumnHack, ContentValues values)
throws SQLException {
- return insertOrReplace(table, nullColumnHack, values, false) ;
+ return insertWithOnConflict(table, nullColumnHack, values, null);
}
/**
@@ -1082,7 +1191,8 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
public long replace(String table, String nullColumnHack, ContentValues initialValues) {
try {
- return insertOrReplace(table, nullColumnHack, initialValues, true);
+ return insertWithOnConflict(table, nullColumnHack, initialValues,
+ ConflictAlgorithm.REPLACE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + initialValues, e);
return -1;
@@ -1103,22 +1213,38 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
public long replaceOrThrow(String table, String nullColumnHack,
ContentValues initialValues) throws SQLException {
- return insertOrReplace(table, nullColumnHack, initialValues, true);
+ return insertWithOnConflict(table, nullColumnHack, initialValues,
+ ConflictAlgorithm.REPLACE);
}
- private long insertOrReplace(String table, String nullColumnHack,
- ContentValues initialValues, boolean allowReplace) {
+ /**
+ * General method for inserting a row into the database.
+ *
+ * @param table the table to insert the row into
+ * @param nullColumnHack SQL doesn't allow inserting a completely empty row,
+ * so if initialValues is empty this column will explicitly be
+ * assigned a NULL value
+ * @param initialValues this map contains the initial column values for the
+ * row. The keys should be the column names and the values the
+ * column values
+ * @param algorithm {@link ConflictAlgorithm} for insert conflict resolver
+ * @return the row ID of the newly inserted row, or -1 if an error occurred
+ * @hide
+ */
+ public long insertWithOnConflict(String table, String nullColumnHack,
+ ContentValues initialValues, ConflictAlgorithm algorithm) {
if (!isOpen()) {
throw new IllegalStateException("database not open");
}
// Measurements show most sql lengths <= 152
StringBuilder sql = new StringBuilder(152);
- sql.append("INSERT ");
- if (allowReplace) {
- sql.append("OR REPLACE ");
+ sql.append("INSERT");
+ if (algorithm != null) {
+ sql.append(" OR ");
+ sql.append(algorithm.value());
}
- sql.append("INTO ");
+ sql.append(" INTO ");
sql.append(table);
// Measurements show most values lengths < 40
StringBuilder values = new StringBuilder(40);
@@ -1241,6 +1367,23 @@ public class SQLiteDatabase extends SQLiteClosable {
* @return the number of rows affected
*/
public int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
+ return updateWithOnConflict(table, values, whereClause, whereArgs, null);
+ }
+
+ /**
+ * Convenience method for updating rows in the database.
+ *
+ * @param table the table to update in
+ * @param values a map from column names to new column values. null is a
+ * valid value that will be translated to NULL.
+ * @param whereClause the optional WHERE clause to apply when updating.
+ * Passing null will update all rows.
+ * @param algorithm {@link ConflictAlgorithm} for update conflict resolver
+ * @return the number of rows affected
+ * @hide
+ */
+ public int updateWithOnConflict(String table, ContentValues values,
+ String whereClause, String[] whereArgs, ConflictAlgorithm algorithm) {
if (!isOpen()) {
throw new IllegalStateException("database not open");
}
@@ -1251,6 +1394,11 @@ public class SQLiteDatabase extends SQLiteClosable {
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
+ if (algorithm != null) {
+ sql.append(" OR ");
+ sql.append(algorithm.value());
+ }
+
sql.append(table);
sql.append(" SET ");
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index f6872ac..35bf645 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -26,8 +26,6 @@ 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.
- *
- * @see com.google.provider.NotePad.NotePadProvider
*/
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 e0341a2..f89c87d 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -239,7 +239,9 @@ public abstract class SQLiteProgram extends SQLiteClosable {
Log.d(TAG, " " + ste);
}
}
- onAllReferencesReleased();
+ // when in finalize() it is already removed from weakhashmap
+ // so it is safe to not removed itself from db
+ onAllReferencesReleasedFromContainer();
}
}
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index 40855b6..22c53ab 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -40,9 +40,8 @@ public class SQLiteQuery extends SQLiteProgram {
* Create a persistent query object.
*
* @param db The database that this query object is associated with
- * @param query The SQL string for this query. It must include "INDEX -1
- * OFFSET ?" at the end
- * @param offsetIndex The 1-based index to the OFFSET parameter
+ * @param query The SQL string for this query.
+ * @param offsetIndex The 1-based index to the OFFSET parameter,
*/
/* package */ SQLiteQuery(SQLiteDatabase db, String query, int offsetIndex, String[] bindArgs) {
super(db, query);
@@ -59,24 +58,28 @@ public class SQLiteQuery extends SQLiteProgram {
* @param startPos The position to start reading rows from
* @return number of total rows in the query
*/
- /* package */ int fillWindow(CursorWindow window, int startPos) {
- if (startPos < 0) {
- throw new IllegalArgumentException("startPos should > 0");
- }
- window.setStartPosition(startPos);
+ /* package */ int fillWindow(CursorWindow window,
+ int maxRead, int lastPos) {
mDatabase.lock();
try {
acquireReference();
- window.acquireReference();
- return native_fill_window(window, startPos, mOffsetIndex);
- } catch (IllegalStateException e){
- // simply ignore it
- return 0;
- } catch (SQLiteDatabaseCorruptException e) {
- mDatabase.onCorruption();
- throw e;
+ try {
+ window.acquireReference();
+ // 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,
+ maxRead, lastPos);
+ } catch (IllegalStateException e){
+ // simply ignore it
+ return 0;
+ } catch (SQLiteDatabaseCorruptException e) {
+ mDatabase.onCorruption();
+ throw e;
+ } finally {
+ window.releaseReference();
+ }
} finally {
- window.releaseReference();
releaseReference();
mDatabase.unlock();
}
@@ -113,7 +116,13 @@ public class SQLiteQuery extends SQLiteProgram {
releaseReference();
}
}
-
+
+ /** {@hide pending API Council approval} */
+ @Override
+ public String toString() {
+ return "SQLiteQuery: " + mQuery;
+ }
+
@Override
public void close() {
super.close();
@@ -124,11 +133,6 @@ public class SQLiteQuery extends SQLiteProgram {
* Called by SQLiteCursor when it is requeried.
*/
/* package */ void requery() {
- boolean oldMClosed = mClosed;
- if (mClosed) {
- mClosed = false;
- compile(mQuery, false);
- }
if (mBindArgs != null) {
int len = mBindArgs.length;
try {
@@ -136,8 +140,7 @@ public class SQLiteQuery extends SQLiteProgram {
super.bindString(i + 1, mBindArgs[i]);
}
} catch (SQLiteMisuseException e) {
- StringBuilder errMsg = new StringBuilder
- ("old mClosed " + oldMClosed + " mQuery " + mQuery);
+ StringBuilder errMsg = new StringBuilder("mQuery " + mQuery);
for (int i = 0; i < len; i++) {
errMsg.append(" ");
errMsg.append(mBindArgs[i]);
@@ -174,7 +177,8 @@ public class SQLiteQuery extends SQLiteProgram {
if (!mClosed) super.bindString(index, value);
}
- private final native int native_fill_window(CursorWindow window, int startPos, int offsetParam);
+ private final native int native_fill_window(CursorWindow window,
+ int startPos, int offsetParam, int maxRead, int lastPos);
private final native int native_column_count();
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 8330750..dc75748 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.io.IOException;
import android.util.Log;
import android.view.Surface;
@@ -98,13 +99,28 @@ public class Camera {
}
/**
+ * Reconnect to the camera after passing it to MediaRecorder. To save
+ * setup/teardown time, a client of Camara can pass an initialized Camera
+ * object to a MediaRecorder to use for video recording. Once the
+ * MediaRecorder is done with the Camera, this method can be used to
+ * re-establish a connection with the camera hardware.
+ *
+ * @throws IOException if the method fails.
+ *
+ * FIXME: Unhide after approval
+ * @hide
+ */
+ public native final void reconnect() throws IOException;
+
+ /**
* Sets the SurfaceHolder to be used for a picture preview. If the surface
* changed since the last call, the screen will blank. Nothing happens
* if the same surface is re-set.
*
* @param holder the SurfaceHolder upon which to place the picture preview
+ * @throws IOException if the method fails.
*/
- public final void setPreviewDisplay(SurfaceHolder holder) {
+ public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
setPreviewDisplay(holder.getSurface());
}
@@ -263,10 +279,19 @@ public class Camera {
};
/**
- * Registers a callback to be invoked when a picture is taken.
+ * Triggers an asynchronous image capture. The camera service
+ * will initiate a series of callbacks to the application as the
+ * image capture progresses. The shutter callback occurs after
+ * the image is captured. This can be used to trigger a sound
+ * to let the user know that image has been captured. The raw
+ * callback occurs when the raw image data is available. The jpeg
+ * callback occurs when the compressed image is available. If the
+ * application does not need a particular callback, a null can be
+ * passed instead of a callback method.
*
- * @param raw the callback to run for raw images, may be null
- * @param jpeg the callback to run for jpeg images, may be null
+ * @param shutter callback after the image is captured, may be null
+ * @param raw callback with raw image data, may be null
+ * @param jpeg callback with jpeg image data, may be null
*/
public final void takePicture(ShutterCallback shutter, PictureCallback raw,
PictureCallback jpeg) {
diff --git a/core/java/android/hardware/ISensorService.aidl b/core/java/android/hardware/ISensorService.aidl
index b6ac3ab..8aad9b4 100644
--- a/core/java/android/hardware/ISensorService.aidl
+++ b/core/java/android/hardware/ISensorService.aidl
@@ -26,5 +26,4 @@ interface ISensorService
{
ParcelFileDescriptor getDataChanel();
boolean enableSensor(IBinder listener, int sensor, int enable);
- oneway void reportAccuracy(int sensor, int value);
}
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
new file mode 100644
index 0000000..0ce2f7b
--- /dev/null
+++ b/core/java/android/hardware/Sensor.java
@@ -0,0 +1,146 @@
+/*
+ * 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.hardware;
+
+/**
+ * Class representing a sensor. Use {@link SensorManager#getSensorList}
+ * to get the list of available Sensors.
+ */
+public class Sensor {
+
+ /**
+ * A constant describing an accelerometer sensor type.
+ * See {@link android.hardware.SensorEvent SensorEvent}
+ * for more details.
+ */
+ public static final int TYPE_ACCELEROMETER = 1;
+
+ /**
+ * A constant describing a magnetic field sensor type.
+ * See {@link android.hardware.SensorEvent SensorEvent}
+ * for more details.
+ */
+ public static final int TYPE_MAGNETIC_FIELD = 2;
+
+ /**
+ * A constant describing an orientation sensor type.
+ * See {@link android.hardware.SensorEvent SensorEvent}
+ * for more details.
+ */
+ public static final int TYPE_ORIENTATION = 3;
+
+ /** A constant describing a gyroscope sensor type */
+ public static final int TYPE_GYROSCOPE = 4;
+ /** A constant describing a light sensor type */
+ public static final int TYPE_LIGHT = 5;
+ /** A constant describing a pressure sensor type */
+ public static final int TYPE_PRESSURE = 6;
+ /** A constant describing a temperature sensor type */
+ public static final int TYPE_TEMPERATURE = 7;
+ /** A constant describing a proximity sensor type */
+ public static final int TYPE_PROXIMITY = 8;
+
+
+ /**
+ * A constant describing all sensor types.
+ */
+ public static final int TYPE_ALL = -1;
+
+ /* Some of these fields are set only by the native bindings in
+ * SensorManager.
+ */
+ private String mName;
+ private String mVendor;
+ private int mVersion;
+ private int mHandle;
+ private int mType;
+ private float mMaxRange;
+ private float mResolution;
+ private float mPower;
+ private int mLegacyType;
+
+
+ Sensor() {
+ }
+
+ /**
+ * @return name string of the sensor.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * @return vendor string of this sensor.
+ */
+ public String getVendor() {
+ return mVendor;
+ }
+
+ /**
+ * @return generic type of this sensor.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * @return version of the sensor's module.
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * @return maximum range of the sensor in the sensor's unit.
+ */
+ public float getMaximumRange() {
+ return mMaxRange;
+ }
+
+ /**
+ * @return resolution of the sensor in the sensor's unit.
+ */
+ public float getResolution() {
+ return mResolution;
+ }
+
+ /**
+ * @return the power in mA used by this sensor while in use
+ */
+ public float getPower() {
+ return mPower;
+ }
+
+ int getHandle() {
+ return mHandle;
+ }
+
+ void setRange(float max, float res) {
+ mMaxRange = max;
+ mResolution = res;
+ }
+
+ void setLegacyType(int legacyType) {
+ mLegacyType = legacyType;
+ }
+
+ int getLegacyType() {
+ return mLegacyType;
+ }
+}
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
new file mode 100644
index 0000000..cf939c5
--- /dev/null
+++ b/core/java/android/hardware/SensorEvent.java
@@ -0,0 +1,146 @@
+/*
+ * 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.hardware;
+
+/**
+ * This class represents a sensor event and holds informations such as the
+ * sensor type (eg: accelerometer, orientation, etc...), the time-stamp,
+ * accuracy and of course the sensor's {@link SensorEvent#values data}.
+ *
+ * <p><u>Definition of the coordinate system used by the SensorEvent API.</u><p>
+ *
+ * <pre>
+ * The coordinate space is defined relative to the screen of the phone
+ * in its default orientation. The axes are not swapped when the device's
+ * screen orientation changes.
+ *
+ * The OpenGL ES coordinate system is used. The origin is in the
+ * lower-left corner with respect to the screen, with the X axis horizontal
+ * and pointing right, the Y axis vertical and pointing up and the Z axis
+ * pointing outside the front face of the screen. In this system, coordinates
+ * behind the screen have negative Z values.
+ *
+ * <b>Note:</b> This coordinate system is different from the one used in the
+ * Android 2D APIs where the origin is in the top-left corner.
+ *
+ * x<0 x>0
+ * ^
+ * |
+ * +-----------+--> y>0
+ * | |
+ * | |
+ * | |
+ * | | / z<0
+ * | | /
+ * | | /
+ * O-----------+/
+ * |[] [ ] []/
+ * +----------/+ y<0
+ * /
+ * /
+ * |/ z>0 (toward the sky)
+ *
+ * O: Origin (x=0,y=0,z=0)
+ * </pre>
+ */
+
+public class SensorEvent {
+ /**
+ * The length and contents of the values array vary depending on which
+ * sensor type is being monitored (see also {@link SensorEvent} for a
+ * definition of the coordinate system used):
+ *
+ * <p>{@link android.hardware.Sensor#TYPE_ORIENTATION Sensor.TYPE_ORIENTATION}:<p>
+ * All values are angles in degrees.
+ *
+ * <p>values[0]: Azimuth, angle between the magnetic north direction and
+ * the Y axis, around the Z axis (0 to 359).
+ * 0=North, 90=East, 180=South, 270=West
+ *
+ * <p>values[1]: Pitch, rotation around X axis (-180 to 180),
+ * with positive values when the z-axis moves <b>toward</b> the y-axis.
+ *
+ * <p>values[2]: Roll, rotation around Y axis (-90 to 90), with
+ * positive values when the x-axis moves <b>away</b> from the z-axis.
+ *
+ * <p><b>Note:</b> This definition is different from <b>yaw, pitch and
+ * roll</b> used in aviation where the X axis is along the long side of
+ * the plane (tail to nose).
+ *
+ * <p><b>Note:</b> It is preferable to use
+ * {@link android.hardware.SensorManager#getRotationMatrix
+ * getRotationMatrix()} in conjunction with
+ * {@link android.hardware.SensorManager#remapCoordinateSystem
+ * remapCoordinateSystem()} and
+ * {@link android.hardware.SensorManager#getOrientation getOrientation()}
+ * to compute these values; while it may be more expensive, it is usually
+ * more accurate.
+ *
+ * <p>{@link android.hardware.Sensor#TYPE_ACCELEROMETER Sensor.TYPE_ACCELEROMETER}:<p>
+ * All values are in SI units (m/s^2) and measure the acceleration applied
+ * to the phone minus the force of gravity.
+ *
+ * <p>values[0]: Acceleration minus Gx on the x-axis
+ * <p>values[1]: Acceleration minus Gy on the y-axis
+ * <p>values[2]: Acceleration minus Gz on the z-axis
+ *
+ * <p><u>Examples</u>:
+ * <li>When the device lies flat on a table and is pushed on its left
+ * side toward the right, the x acceleration value is positive.</li>
+ *
+ * <li>When the device lies flat on a table, the acceleration value is
+ * +9.81, which correspond to the acceleration of the device (0 m/s^2)
+ * minus the force of gravity (-9.81 m/s^2).</li>
+ *
+ * <li>When the device lies flat on a table and is pushed toward the sky
+ * with an acceleration of A m/s^2, the acceleration value is equal to
+ * A+9.81 which correspond to the acceleration of the
+ * device (+A m/s^2) minus the force of gravity (-9.81 m/s^2).</li>
+ *
+ *
+ * <p>{@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD Sensor.TYPE_MAGNETIC_FIELD}:<p>
+ * All values are in micro-Tesla (uT) and measure the ambient magnetic
+ * field in the X, Y and Z axis.
+ *
+ */
+ public final float[] values;
+
+ /**
+ * The sensor that generated this event.
+ * See {@link android.hardware.SensorManager SensorManager}
+ * for details.
+ */
+ public Sensor sensor;
+
+ /**
+ * The accuracy of this event.
+ * See {@link android.hardware.SensorManager SensorManager}
+ * for details.
+ */
+ public int accuracy;
+
+
+ /**
+ * The time in nanosecond at which the event happened
+ */
+ public long timestamp;
+
+
+ SensorEvent(int size) {
+ values = new float[size];
+ }
+}
diff --git a/core/java/android/hardware/SensorEventListener.java b/core/java/android/hardware/SensorEventListener.java
new file mode 100644
index 0000000..716d0d4
--- /dev/null
+++ b/core/java/android/hardware/SensorEventListener.java
@@ -0,0 +1,49 @@
+/*
+ * 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.hardware;
+
+/**
+ * Used for receiving notifications from the SensorManager when
+ * sensor values have changed.
+ */
+public interface SensorEventListener {
+
+ /**
+ * Called when sensor values have changed.
+ * <p>See {@link android.hardware.SensorManager SensorManager}
+ * for details on possible sensor types.
+ * <p>See also {@link android.hardware.SensorEvent SensorEvent}.
+ *
+ * <p><b>NOTE:</b> The application doesn't own the
+ * {@link android.hardware.SensorEvent event}
+ * object passed as a parameter and therefore cannot hold on o it.
+ * The object may be part of an internal pool and may be reused by
+ * the framework.
+ *
+ * @param event the {@link android.hardware.SensorEvent SensorEvent}.
+ */
+ public void onSensorChanged(SensorEvent event);
+
+ /**
+ * Called when the accuracy of a sensor has changed.
+ * <p>See {@link android.hardware.SensorManager SensorManager}
+ * for details.
+ *
+ * @param accuracy The new accuracy of this sensor
+ */
+ public void onAccuracyChanged(Sensor sensor, int accuracy);
+}
diff --git a/core/java/android/hardware/SensorListener.java b/core/java/android/hardware/SensorListener.java
index d676a5e..cfa184b 100644
--- a/core/java/android/hardware/SensorListener.java
+++ b/core/java/android/hardware/SensorListener.java
@@ -19,18 +19,74 @@ package android.hardware;
/**
* Used for receiving notifications from the SensorManager when
* sensor values have changed.
+ *
+ * This interface is deprecated, use
+ * {@link android.hardware.SensorEventListener SensorEventListener} instead.
+ *
*/
+@Deprecated
public interface SensorListener {
/**
- * Called when sensor values have changed.
+ * <p>Called when sensor values have changed.
* The length and contents of the values array vary
* depending on which sensor is being monitored.
* See {@link android.hardware.SensorManager SensorManager}
- * for details on possible sensor types and values.
+ * for details on possible sensor types.
*
+ * <p><u>Definition of the coordinate system used below.</u><p>
+ * <p>The X axis refers to the screen's horizontal axis
+ * (the small edge in portrait mode, the long edge in landscape mode) and
+ * points to the right.
+ * <p>The Y axis refers to the screen's vertical axis and points towards
+ * the top of the screen (the origin is in the lower-left corner).
+ * <p>The Z axis points toward the sky when the device is lying on its back
+ * on a table.
+ * <p> <b>IMPORTANT NOTE:</b> The axis <b><u>are swapped</u></b> when the
+ * device's screen orientation changes. To access the unswapped values,
+ * use indices 3, 4 and 5 in values[].
+ *
+ * <p>{@link android.hardware.SensorManager#SENSOR_ORIENTATION SENSOR_ORIENTATION},
+ * {@link android.hardware.SensorManager#SENSOR_ORIENTATION_RAW SENSOR_ORIENTATION_RAW}:<p>
+ * All values are angles in degrees.
+ *
+ * <p>values[0]: Azimuth, rotation around the Z axis (0<=azimuth<360).
+ * 0 = North, 90 = East, 180 = South, 270 = West
+ *
+ * <p>values[1]: Pitch, rotation around X axis (-180<=pitch<=180), with positive
+ * values when the z-axis moves toward the y-axis.
+ *
+ * <p>values[2]: Roll, rotation around Y axis (-90<=roll<=90), with positive values
+ * when the z-axis moves toward the x-axis.
+ *
+ * <p>Note that this definition of yaw, pitch and roll is different from the
+ * traditional definition used in aviation where the X axis is along the long
+ * side of the plane (tail to nose).
+ *
+ * <p>{@link android.hardware.SensorManager#SENSOR_ACCELEROMETER SENSOR_ACCELEROMETER}:<p>
+ * All values are in SI units (m/s^2) and measure contact forces.
+ *
+ * <p>values[0]: force applied by the device on the x-axis
+ * <p>values[1]: force applied by the device on the y-axis
+ * <p>values[2]: force applied by the device on the z-axis
+ *
+ * <p><u>Examples</u>:
+ * <li>When the device is pushed on its left side toward the right, the
+ * x acceleration value is negative (the device applies a reaction force
+ * to the push toward the left)</li>
+ *
+ * <li>When the device lies flat on a table, the acceleration value is
+ * {@link android.hardware.SensorManager#STANDARD_GRAVITY -STANDARD_GRAVITY},
+ * which correspond to the force the device applies on the table in reaction
+ * to gravity.</li>
+ *
+ * <p>{@link android.hardware.SensorManager#SENSOR_MAGNETIC_FIELD SENSOR_MAGNETIC_FIELD}:<p>
+ * All values are in micro-Tesla (uT) and measure the ambient magnetic
+ * field in the X, Y and -Z axis.
+ * <p><b><u>Note:</u></b> the magnetic field's Z axis is inverted.
+ *
* @param sensor The ID of the sensor being monitored
- * @param values The new values for the sensor
+ * @param values The new values for the sensor.
*/
public void onSensorChanged(int sensor, float[] values);
@@ -40,7 +96,7 @@ public interface SensorListener {
* for details.
*
* @param sensor The ID of the sensor being monitored
- * @param accuracy The new accuracy of this sensor
+ * @param accuracy The new accuracy of this sensor.
*/
public void onAccuracyChanged(int sensor, int accuracy);
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 9b88fff..f02094e 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -26,6 +26,7 @@ import android.os.Handler;
import android.os.Message;
import android.os.ServiceManager;
import android.util.Log;
+import android.util.SparseArray;
import android.view.IRotationWatcher;
import android.view.IWindowManager;
import android.view.Surface;
@@ -33,7 +34,9 @@ import android.view.Surface;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
/**
* Class that lets you access the device's sensors. Get an instance of this
@@ -43,126 +46,119 @@ import java.util.Arrays;
public class SensorManager extends IRotationWatcher.Stub
{
private static final String TAG = "SensorManager";
+ private static final float[] mTempMatrix = new float[16];
- /** NOTE: sensor IDs must be a power of 2 */
+ /* NOTE: sensor IDs must be a power of 2 */
- /** A constant describing an orientation sensor.
- * Sensor values are yaw, pitch and roll
- *
- * Yaw is the compass heading in degrees, range [0, 360[
- * 0 = North, 90 = East, 180 = South, 270 = West
- *
- * Pitch indicates the tilt of the top of the device,
- * with range -90 to 90.
- * Positive values indicate that the bottom of the device is tilted up
- * and negative values indicate the top of the device is tilted up.
- *
- * Roll indicates the side to side tilt of the device,
- * with range -90 to 90.
- * Positive values indicate that the left side of the device is tilted up
- * and negative values indicate the right side of the device is tilted up.
+ /**
+ * A constant describing an orientation sensor.
+ * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
+ @Deprecated
public static final int SENSOR_ORIENTATION = 1 << 0;
- /** A constant describing an accelerometer.
- * Sensor values are acceleration in the X, Y and Z axis,
- * where the X axis has positive direction toward the right side of the device,
- * the Y axis has positive direction toward the top of the device
- * and the Z axis has positive direction toward the front of the device.
- *
- * The direction of the force of gravity is indicated by acceleration values in the
- * X, Y and Z axes. The typical case where the device is flat relative to the surface
- * of the Earth appears as -STANDARD_GRAVITY in the Z axis
- * and X and Z values close to zero.
- *
- * Acceleration values are given in SI units (m/s^2)
- *
+ /**
+ * A constant describing an accelerometer.
+ * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
+ @Deprecated
public static final int SENSOR_ACCELEROMETER = 1 << 1;
- /** A constant describing a temperature sensor
- * Only the first value is defined for this sensor and it
- * contains the ambient temperature in degree C.
+ /**
+ * A constant describing a temperature sensor
+ * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
+ @Deprecated
public static final int SENSOR_TEMPERATURE = 1 << 2;
- /** A constant describing a magnetic sensor
- * Sensor values are the magnetic vector in the X, Y and Z axis,
- * where the X axis has positive direction toward the right side of the device,
- * the Y axis has positive direction toward the top of the device
- * and the Z axis has positive direction toward the front of the device.
- *
- * Magnetic values are given in micro-Tesla (uT)
- *
+ /**
+ * A constant describing a magnetic sensor
+ * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
+ @Deprecated
public static final int SENSOR_MAGNETIC_FIELD = 1 << 3;
- /** A constant describing an ambient light sensor
- * Only the first value is defined for this sensor and it contains
- * the ambient light measure in lux.
- *
+ /**
+ * A constant describing an ambient light sensor
+ * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
+ @Deprecated
public static final int SENSOR_LIGHT = 1 << 4;
- /** A constant describing a proximity sensor
- * Only the first value is defined for this sensor and it contains
- * the distance between the sensor and the object in meters (m)
+ /**
+ * A constant describing a proximity sensor
+ * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
+ @Deprecated
public static final int SENSOR_PROXIMITY = 1 << 5;
- /** A constant describing a Tricorder
- * When this sensor is available and enabled, the device can be
- * used as a fully functional Tricorder. All values are returned in
- * SI units.
+ /**
+ * A constant describing a Tricorder
+ * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
+ @Deprecated
public static final int SENSOR_TRICORDER = 1 << 6;
- /** A constant describing an orientation sensor.
- * Sensor values are yaw, pitch and roll
- *
- * Yaw is the compass heading in degrees, 0 <= range < 360
- * 0 = North, 90 = East, 180 = South, 270 = West
- *
- * This is similar to SENSOR_ORIENTATION except the data is not
- * smoothed or filtered in any way.
+ /**
+ * A constant describing an orientation sensor.
+ * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
+ @Deprecated
public static final int SENSOR_ORIENTATION_RAW = 1 << 7;
/** A constant that includes all sensors */
+ @Deprecated
public static final int SENSOR_ALL = 0x7F;
/** Smallest sensor ID */
+ @Deprecated
public static final int SENSOR_MIN = SENSOR_ORIENTATION;
/** Largest sensor ID */
+ @Deprecated
public static final int SENSOR_MAX = ((SENSOR_ALL + 1)>>1);
- /** Index of the X value in the array returned by
+ /** Index of the X value in the array returned by
* {@link android.hardware.SensorListener#onSensorChanged} */
+ @Deprecated
public static final int DATA_X = 0;
- /** Index of the Y value in the array returned by
+ /** Index of the Y value in the array returned by
* {@link android.hardware.SensorListener#onSensorChanged} */
+ @Deprecated
public static final int DATA_Y = 1;
- /** Index of the Z value in the array returned by
+ /** Index of the Z value in the array returned by
* {@link android.hardware.SensorListener#onSensorChanged} */
+ @Deprecated
public static final int DATA_Z = 2;
-
- /** Offset to the raw values in the array returned by
+
+ /** Offset to the untransformed values in the array returned by
* {@link android.hardware.SensorListener#onSensorChanged} */
+ @Deprecated
public static final int RAW_DATA_INDEX = 3;
- /** Index of the raw X value in the array returned by
+ /** Index of the untransformed X value in the array returned by
* {@link android.hardware.SensorListener#onSensorChanged} */
+ @Deprecated
public static final int RAW_DATA_X = 3;
- /** Index of the raw X value in the array returned by
+ /** Index of the untransformed Y value in the array returned by
* {@link android.hardware.SensorListener#onSensorChanged} */
+ @Deprecated
public static final int RAW_DATA_Y = 4;
- /** Index of the raw X value in the array returned by
+ /** Index of the untransformed Z value in the array returned by
* {@link android.hardware.SensorListener#onSensorChanged} */
+ @Deprecated
public static final int RAW_DATA_Z = 5;
-
-
+
+
/** Standard gravity (g) on Earth. This value is equivalent to 1G */
public static final float STANDARD_GRAVITY = 9.80665f;
@@ -177,7 +173,7 @@ public class SensorManager extends IRotationWatcher.Stub
public static final float GRAVITY_JUPITER = 23.12f;
public static final float GRAVITY_SATURN = 8.96f;
public static final float GRAVITY_URANUS = 8.69f;
- public static final float GRAVITY_NEPTUN = 11.0f;
+ public static final float GRAVITY_NEPTUNE = 11.0f;
public static final float GRAVITY_PLUTO = 0.6f;
public static final float GRAVITY_DEATH_STAR_I = 0.000000353036145f;
public static final float GRAVITY_THE_ISLAND = 4.815162342f;
@@ -208,52 +204,84 @@ public class SensorManager extends IRotationWatcher.Stub
/** rate suitable for the user interface */
public static final int SENSOR_DELAY_UI = 2;
/** rate (default) suitable for screen orientation changes */
- public static final int SENSOR_DELAY_NORMAL = 3;
+ public static final int SENSOR_DELAY_NORMAL = 3;
+
-
/** The values returned by this sensor cannot be trusted, calibration
* is needed or the environment doesn't allow readings */
public static final int SENSOR_STATUS_UNRELIABLE = 0;
-
+
/** This sensor is reporting data with low accuracy, calibration with the
* environment is needed */
public static final int SENSOR_STATUS_ACCURACY_LOW = 1;
- /** This sensor is reporting data with an average level of accuracy,
+ /** This sensor is reporting data with an average level of accuracy,
* calibration with the environment may improve the readings */
public static final int SENSOR_STATUS_ACCURACY_MEDIUM = 2;
-
+
/** This sensor is reporting data with maximum accuracy */
public static final int SENSOR_STATUS_ACCURACY_HIGH = 3;
-
+ /** see {@link #remapCoordinateSystem} */
+ public static final int AXIS_X = 1;
+ /** see {@link #remapCoordinateSystem} */
+ public static final int AXIS_Y = 2;
+ /** see {@link #remapCoordinateSystem} */
+ public static final int AXIS_Z = 3;
+ /** see {@link #remapCoordinateSystem} */
+ public static final int AXIS_MINUS_X = AXIS_X | 0x80;
+ /** see {@link #remapCoordinateSystem} */
+ public static final int AXIS_MINUS_Y = AXIS_Y | 0x80;
+ /** see {@link #remapCoordinateSystem} */
+ public static final int AXIS_MINUS_Z = AXIS_Z | 0x80;
+
+ /*-----------------------------------------------------------------------*/
- private static final int SENSOR_DISABLE = -1;
- private static final int SENSOR_ORDER_MASK = 0x1F;
- private static final int SENSOR_STATUS_SHIFT = 28;
private ISensorService mSensorService;
- private Looper mLooper;
+ Looper mMainLooper;
+ @SuppressWarnings("deprecation")
+ private HashMap<SensorListener, LegacyListener> mLegacyListenersMap =
+ new HashMap<SensorListener, LegacyListener>();
- private static IWindowManager sWindowManager;
- private static int sRotation = 0;
+ /*-----------------------------------------------------------------------*/
- /* The thread and the sensor list are global to the process
+ private static final int SENSOR_DISABLE = -1;
+ private static boolean sSensorModuleInitialized = false;
+ private static ArrayList<Sensor> sFullSensorsList = new ArrayList<Sensor>();
+ private static SparseArray<List<Sensor>> sSensorListByType = new SparseArray<List<Sensor>>();
+ private static IWindowManager sWindowManager;
+ private static int sRotation = Surface.ROTATION_0;
+ /* The thread and the sensor list are global to the process
* but the actual thread is spawned on demand */
- static final private SensorThread sSensorThread = new SensorThread();
- static final private ArrayList<ListenerDelegate> sListeners =
+ private static SensorThread sSensorThread;
+
+ // Used within this module from outside SensorManager, don't make private
+ static SparseArray<Sensor> sHandleToSensor = new SparseArray<Sensor>();
+ static final ArrayList<ListenerDelegate> sListeners =
new ArrayList<ListenerDelegate>();
+ /*-----------------------------------------------------------------------*/
static private class SensorThread {
- private Thread mThread;
+ Thread mThread;
+
+ SensorThread() {
+ // this gets to the sensor module. We can have only one per process.
+ sensors_data_init();
+ }
+
+ @Override
+ protected void finalize() {
+ sensors_data_uninit();
+ }
// must be called with sListeners lock
void startLocked(ISensorService service) {
try {
if (mThread == null) {
ParcelFileDescriptor fd = service.getDataChanel();
- mThread = new Thread(new SensorThreadRunnable(fd, service),
+ mThread = new Thread(new SensorThreadRunnable(fd),
SensorThread.class.getName());
mThread.start();
}
@@ -263,162 +291,165 @@ public class SensorManager extends IRotationWatcher.Stub
}
private class SensorThreadRunnable implements Runnable {
- private ISensorService mSensorService;
private ParcelFileDescriptor mSensorDataFd;
- private final byte mAccuracies[] = new byte[32];
- SensorThreadRunnable(ParcelFileDescriptor fd, ISensorService service) {
+ SensorThreadRunnable(ParcelFileDescriptor fd) {
mSensorDataFd = fd;
- mSensorService = service;
- Arrays.fill(mAccuracies, (byte)-1);
}
public void run() {
- int sensors_of_interest;
- float[] values = new float[6];
+ //Log.d(TAG, "entering main sensor thread");
+ final float[] values = new float[3];
+ final int[] status = new int[1];
+ final long timestamp[] = new long[1];
Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
- synchronized (sListeners) {
- _sensors_data_open(mSensorDataFd.getFileDescriptor());
- try {
- mSensorDataFd.close();
- } catch (IOException e) {
- // *shrug*
- Log.e(TAG, "IOException: ", e);
- }
- mSensorDataFd = null;
- //mSensorDataFd.
- // the first time, compute the sensors we need. this is not
- // a big deal if it changes by the time we call
- // _sensors_data_poll, it'll get recomputed for the next
- // round.
- sensors_of_interest = 0;
- final int size = sListeners.size();
- for (int i=0 ; i<size ; i++) {
- sensors_of_interest |= sListeners.get(i).mSensors;
- if ((sensors_of_interest & SENSOR_ALL) == SENSOR_ALL)
- break;
- }
+ if (mSensorDataFd == null) {
+ Log.e(TAG, "mSensorDataFd == NULL, exiting");
+ return;
}
+ // this thread is guaranteed to be unique
+ sensors_data_open(mSensorDataFd.getFileDescriptor());
+ try {
+ mSensorDataFd.close();
+ } catch (IOException e) {
+ // *shrug*
+ Log.e(TAG, "IOException: ", e);
+ }
+ mSensorDataFd = null;
+
while (true) {
// wait for an event
- final int sensor_result = _sensors_data_poll(values, sensors_of_interest);
- final int sensor_order = sensor_result & SENSOR_ORDER_MASK;
- final int sensor = 1 << sensor_result;
- int accuracy = sensor_result>>>SENSOR_STATUS_SHIFT;
-
- if ((sensors_of_interest & sensor)!=0) {
- // show the notification only if someone is listening for
- // this sensor
- if (accuracy != mAccuracies[sensor_order]) {
- try {
- mSensorService.reportAccuracy(sensor, accuracy);
- mAccuracies[sensor_order] = (byte)accuracy;
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in reportAccuracy: ", e);
- }
- } else {
- accuracy = -1;
- }
+ final int sensor = sensors_data_poll(values, status, timestamp);
+
+ if (sensor == -1) {
+ // we lost the connection to the event stream. this happens
+ // when the last listener is removed.
+ Log.d(TAG, "_sensors_data_poll() failed, we bail out.");
+ break;
}
-
+
+ int accuracy = status[0];
synchronized (sListeners) {
if (sListeners.isEmpty()) {
// we have no more listeners, terminate the thread
- _sensors_data_close();
+ sensors_data_close();
mThread = null;
break;
}
- // convert for the current screen orientation
- mapSensorDataToWindow(sensor, values, SensorManager.getRotation());
- // report the sensor event to all listeners that
- // care about it.
- sensors_of_interest = 0;
- final int size = sListeners.size();
- for (int i=0 ; i<size ; i++) {
- ListenerDelegate listener = sListeners.get(i);
- sensors_of_interest |= listener.mSensors;
- if (listener.hasSensor(sensor)) {
- // this is asynchronous (okay to call
- // with sListeners lock held.
- listener.onSensorChanged(sensor, values, accuracy);
+ final Sensor sensorObject = sHandleToSensor.get(sensor);
+ if (sensorObject != null) {
+ // report the sensor event to all listeners that
+ // care about it.
+ final int size = sListeners.size();
+ for (int i=0 ; i<size ; i++) {
+ ListenerDelegate listener = sListeners.get(i);
+ if (listener.hasSensor(sensorObject)) {
+ // this is asynchronous (okay to call
+ // with sListeners lock held).
+ listener.onSensorChangedLocked(sensorObject,
+ values, timestamp, accuracy);
+ }
}
}
}
}
+ //Log.d(TAG, "exiting main sensor thread");
}
}
}
- private class ListenerDelegate extends Binder {
-
- private SensorListener mListener;
- private int mSensors;
- private float[] mValuesPool;
+ /*-----------------------------------------------------------------------*/
- ListenerDelegate(SensorListener listener, int sensors) {
- mListener = listener;
- mSensors = sensors;
- mValuesPool = new float[6];
+ private class ListenerDelegate extends Binder {
+ final SensorEventListener mSensorEventListener;
+ private final ArrayList<Sensor> mSensorList = new ArrayList<Sensor>();
+ private final Handler mHandler;
+ private SensorEvent mValuesPool;
+ public int mSensors;
+
+ ListenerDelegate(SensorEventListener listener, Sensor sensor, Handler handler) {
+ mSensorEventListener = listener;
+ Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
+ // currently we create one Handler instance per listener, but we could
+ // have one per looper (we'd need to pass the ListenerDelegate
+ // instance to handleMessage and keep track of them separately).
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ SensorEvent t = (SensorEvent)msg.obj;
+ if (t.accuracy >= 0) {
+ mSensorEventListener.onAccuracyChanged(t.sensor, t.accuracy);
+ }
+ mSensorEventListener.onSensorChanged(t);
+ returnToPool(t);
+ }
+ };
+ addSensor(sensor);
}
- int addSensors(int sensors) {
- mSensors |= sensors;
- return mSensors;
- }
- int removeSensors(int sensors) {
- mSensors &= ~sensors;
- return mSensors;
- }
- boolean hasSensor(int sensor) {
- return ((mSensors & sensor) != 0);
+ protected SensorEvent createSensorEvent() {
+ // maximal size for all legacy events is 3
+ return new SensorEvent(3);
}
- void onSensorChanged(int sensor, float[] values, int accuracy) {
- float[] v;
+ protected SensorEvent getFromPool() {
+ SensorEvent t = null;
synchronized (this) {
// remove the array from the pool
- v = mValuesPool;
+ t = mValuesPool;
mValuesPool = null;
}
+ if (t == null) {
+ // the pool was empty, we need a new one
+ t = createSensorEvent();
+ }
+ return t;
+ }
- if (v != null) {
- v[0] = values[0];
- v[1] = values[1];
- v[2] = values[2];
- v[3] = values[3];
- v[4] = values[4];
- v[5] = values[5];
- } else {
- // the pool was empty, we need to dup the array
- v = values.clone();
+ protected void returnToPool(SensorEvent t) {
+ synchronized (this) {
+ // put back the array into the pool
+ if (mValuesPool == null) {
+ mValuesPool = t;
+ }
}
+ }
+
+ Object getListener() {
+ return mSensorEventListener;
+ }
+
+ int addSensor(Sensor sensor) {
+ mSensors |= 1<<sensor.getHandle();
+ mSensorList.add(sensor);
+ return mSensors;
+ }
+ int removeSensor(Sensor sensor) {
+ mSensors &= ~(1<<sensor.getHandle());
+ mSensorList.remove(sensor);
+ return mSensors;
+ }
+ boolean hasSensor(Sensor sensor) {
+ return ((mSensors & (1<<sensor.getHandle())) != 0);
+ }
+ List<Sensor> getSensors() {
+ return mSensorList;
+ }
+ void onSensorChangedLocked(Sensor sensor, float[] values, long[] timestamp, int accuracy) {
+ SensorEvent t = getFromPool();
+ final float[] v = t.values;
+ v[0] = values[0];
+ v[1] = values[1];
+ v[2] = values[2];
+ t.timestamp = timestamp[0];
+ t.accuracy = accuracy;
+ t.sensor = sensor;
Message msg = Message.obtain();
- msg.what = sensor;
- msg.obj = v;
- msg.arg1 = accuracy;
+ msg.what = 0;
+ msg.obj = t;
mHandler.sendMessage(msg);
}
-
- private final Handler mHandler = new Handler(mLooper) {
- @Override public void handleMessage(Message msg) {
- if (msg.arg1 >= 0) {
- try {
- mListener.onAccuracyChanged(msg.what, msg.arg1);
- } catch (AbstractMethodError e) {
- // old app that doesn't implement this method
- // just ignore it.
- }
- }
- mListener.onSensorChanged(msg.what, (float[])msg.obj);
- synchronized (this) {
- // put back the array into the pool
- if (mValuesPool == null) {
- mValuesPool = (float[])msg.obj;
- }
- }
- }
- };
}
/**
@@ -427,41 +458,157 @@ public class SensorManager extends IRotationWatcher.Stub
public SensorManager(Looper mainLooper) {
mSensorService = ISensorService.Stub.asInterface(
ServiceManager.getService(Context.SENSOR_SERVICE));
-
- sWindowManager = IWindowManager.Stub.asInterface(
- ServiceManager.getService("window"));
+ mMainLooper = mainLooper;
- if (sWindowManager != null) {
- // if it's null we're running in the system process
- // which won't get the rotated values
- try {
- sWindowManager.watchRotation(this);
- } catch (RemoteException e) {
+
+ synchronized(sListeners) {
+ if (!sSensorModuleInitialized) {
+ sSensorModuleInitialized = true;
+
+ nativeClassInit();
+
+ sWindowManager = IWindowManager.Stub.asInterface(
+ ServiceManager.getService("window"));
+ if (sWindowManager != null) {
+ // if it's null we're running in the system process
+ // which won't get the rotated values
+ try {
+ sRotation = sWindowManager.watchRotation(this);
+ } catch (RemoteException e) {
+ }
+ }
+
+ // initialize the sensor list
+ sensors_module_init();
+ final ArrayList<Sensor> fullList = sFullSensorsList;
+ int i = 0;
+ do {
+ Sensor sensor = new Sensor();
+ i = sensors_module_get_next_sensor(sensor, i);
+
+ if (i>=0) {
+ Log.d(TAG, "found sensor: " + sensor.getName() +
+ ", handle=" + sensor.getHandle());
+ sensor.setLegacyType(getLegacySensorType(sensor.getType()));
+ fullList.add(sensor);
+ sHandleToSensor.append(sensor.getHandle(), sensor);
+ }
+ } while (i>0);
+
+ sSensorThread = new SensorThread();
}
}
+ }
- mLooper = mainLooper;
+ private int getLegacySensorType(int type) {
+ switch (type) {
+ case Sensor.TYPE_ACCELEROMETER:
+ return SENSOR_ACCELEROMETER;
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ return SENSOR_MAGNETIC_FIELD;
+ case Sensor.TYPE_ORIENTATION:
+ return SENSOR_ORIENTATION_RAW;
+ case Sensor.TYPE_TEMPERATURE:
+ return SENSOR_TEMPERATURE;
+ }
+ return 0;
}
- /** @return available sensors */
+ /** @return available sensors.
+ * @deprecated This method is deprecated, use
+ * {@link SensorManager#getSensorList(int)} instead
+ */
+ @Deprecated
public int getSensors() {
- return _sensors_data_get_sensors();
+ int result = 0;
+ final ArrayList<Sensor> fullList = sFullSensorsList;
+ for (Sensor i : fullList) {
+ switch (i.getType()) {
+ case Sensor.TYPE_ACCELEROMETER:
+ result |= SensorManager.SENSOR_ACCELEROMETER;
+ break;
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ result |= SensorManager.SENSOR_MAGNETIC_FIELD;
+ break;
+ case Sensor.TYPE_ORIENTATION:
+ result |= SensorManager.SENSOR_ORIENTATION |
+ SensorManager.SENSOR_ORIENTATION_RAW;
+ break;
+ }
+ }
+ return result;
}
/**
+ * Use this method to get the list of available sensors of a certain
+ * type. Make multiple calls to get sensors of different types or use
+ * {@link android.hardware.Sensor#TYPE_ALL Sensor.TYPE_ALL} to get all
+ * the sensors.
+ *
+ * @param type of sensors requested
+ * @return a list of sensors matching the asked type.
+ */
+ public List<Sensor> getSensorList(int type) {
+ // cache the returned lists the first time
+ List<Sensor> list;
+ final ArrayList<Sensor> fullList = sFullSensorsList;
+ synchronized(fullList) {
+ list = sSensorListByType.get(type);
+ if (list == null) {
+ if (type == Sensor.TYPE_ALL) {
+ list = fullList;
+ } else {
+ list = new ArrayList<Sensor>();
+ for (Sensor i : fullList) {
+ if (i.getType() == type)
+ list.add(i);
+ }
+ }
+ list = Collections.unmodifiableList(list);
+ sSensorListByType.append(type, list);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Use this method to get the default sensor for a given type. Note that
+ * the returned sensor could be a composite sensor, and its data could be
+ * averaged or filtered. If you need to access the raw sensors use
+ * {@link SensorManager#getSensorList(int) getSensorList}.
+ *
+ *
+ * @param type of sensors requested
+ * @return the default sensors matching the asked type.
+ */
+ public Sensor getDefaultSensor(int type) {
+ // TODO: need to be smarter, for now, just return the 1st sensor
+ List<Sensor> l = getSensorList(type);
+ return l.isEmpty() ? null : l.get(0);
+ }
+
+
+ /**
* Registers a listener for given sensors.
+ * @deprecated This method is deprecated, use
+ * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)}
+ * instead.
*
* @param listener sensor listener object
* @param sensors a bit masks of the sensors to register to
*
* @return true if the sensor is supported and successfully enabled
*/
+ @Deprecated
public boolean registerListener(SensorListener listener, int sensors) {
return registerListener(listener, sensors, SENSOR_DELAY_NORMAL);
}
/**
- * Registers a listener for given sensors.
+ * Registers a SensorListener for given sensors.
+ * @deprecated This method is deprecated, use
+ * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)}
+ * instead.
*
* @param listener sensor listener object
* @param sensors a bit masks of the sensors to register to
@@ -471,9 +618,189 @@ public class SensorManager extends IRotationWatcher.Stub
*
* @return true if the sensor is supported and successfully enabled
*/
+ @Deprecated
public boolean registerListener(SensorListener listener, int sensors, int rate) {
- boolean result;
+ boolean result = false;
+ result = registerLegacyListener(SENSOR_ACCELEROMETER, Sensor.TYPE_ACCELEROMETER,
+ listener, sensors, rate) || result;
+ result = registerLegacyListener(SENSOR_MAGNETIC_FIELD, Sensor.TYPE_MAGNETIC_FIELD,
+ listener, sensors, rate) || result;
+ result = registerLegacyListener(SENSOR_ORIENTATION_RAW, Sensor.TYPE_ORIENTATION,
+ listener, sensors, rate) || result;
+ result = registerLegacyListener(SENSOR_ORIENTATION, Sensor.TYPE_ORIENTATION,
+ listener, sensors, rate) || result;
+ result = registerLegacyListener(SENSOR_TEMPERATURE, Sensor.TYPE_TEMPERATURE,
+ listener, sensors, rate) || result;
+ return result;
+ }
+ @SuppressWarnings("deprecation")
+ private boolean registerLegacyListener(int legacyType, int type,
+ SensorListener listener, int sensors, int rate)
+ {
+ boolean result = false;
+ // Are we activating this legacy sensor?
+ if ((sensors & legacyType) != 0) {
+ // if so, find a suitable Sensor
+ Sensor sensor = getDefaultSensor(type);
+ if (sensor != null) {
+ // If we don't already have one, create a LegacyListener
+ // to wrap this listener and process the events as
+ // they are expected by legacy apps.
+ LegacyListener legacyListener = null;
+ synchronized (mLegacyListenersMap) {
+ legacyListener = mLegacyListenersMap.get(listener);
+ if (legacyListener == null) {
+ // we didn't find a LegacyListener for this client,
+ // create one, and put it in our list.
+ legacyListener = new LegacyListener(listener);
+ mLegacyListenersMap.put(listener, legacyListener);
+ }
+ }
+ // register this legacy sensor with this legacy listener
+ legacyListener.registerSensor(legacyType);
+ // and finally, register the legacy listener with the new apis
+ result = registerListener(legacyListener, sensor, rate);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Unregisters a listener for the sensors with which it is registered.
+ * @deprecated This method is deprecated, use
+ * {@link SensorManager#unregisterListener(SensorEventListener, Sensor)}
+ * instead.
+ *
+ * @param listener a SensorListener object
+ * @param sensors a bit masks of the sensors to unregister from
+ */
+ @Deprecated
+ public void unregisterListener(SensorListener listener, int sensors) {
+ unregisterLegacyListener(SENSOR_ACCELEROMETER, Sensor.TYPE_ACCELEROMETER,
+ listener, sensors);
+ unregisterLegacyListener(SENSOR_MAGNETIC_FIELD, Sensor.TYPE_MAGNETIC_FIELD,
+ listener, sensors);
+ unregisterLegacyListener(SENSOR_ORIENTATION_RAW, Sensor.TYPE_ORIENTATION,
+ listener, sensors);
+ unregisterLegacyListener(SENSOR_ORIENTATION, Sensor.TYPE_ORIENTATION,
+ listener, sensors);
+ unregisterLegacyListener(SENSOR_TEMPERATURE, Sensor.TYPE_TEMPERATURE,
+ listener, sensors);
+ }
+
+ @SuppressWarnings("deprecation")
+ private void unregisterLegacyListener(int legacyType, int type,
+ SensorListener listener, int sensors)
+ {
+ // do we know about this listener?
+ LegacyListener legacyListener = null;
+ synchronized (mLegacyListenersMap) {
+ legacyListener = mLegacyListenersMap.get(listener);
+ }
+ if (legacyListener != null) {
+ // Are we deactivating this legacy sensor?
+ if ((sensors & legacyType) != 0) {
+ // if so, find the corresponding Sensor
+ Sensor sensor = getDefaultSensor(type);
+ if (sensor != null) {
+ // unregister this legacy sensor and if we don't
+ // need the corresponding Sensor, unregister it too
+ if (legacyListener.unregisterSensor(legacyType)) {
+ // corresponding sensor not needed, unregister
+ unregisterListener(legacyListener, sensor);
+ // finally check if we still need the legacyListener
+ // in our mapping, if not, get rid of it too.
+ synchronized(sListeners) {
+ boolean found = false;
+ for (ListenerDelegate i : sListeners) {
+ if (i.getListener() == legacyListener) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ synchronized (mLegacyListenersMap) {
+ mLegacyListenersMap.remove(listener);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Unregisters a listener for all sensors.
+ * @deprecated This method is deprecated, use
+ * {@link SensorManager#unregisterListener(SensorEventListener)}
+ * instead.
+ *
+ * @param listener a SensorListener object
+ */
+ @Deprecated
+ public void unregisterListener(SensorListener listener) {
+ unregisterListener(listener, SENSOR_ALL);
+ }
+
+ /**
+ * Unregisters a listener for the sensors with which it is registered.
+ *
+ * @param listener a SensorEventListener object
+ * @param sensor the sensor to unregister from
+ *
+ */
+ public void unregisterListener(SensorEventListener listener, Sensor sensor) {
+ unregisterListener((Object)listener, sensor);
+ }
+
+ /**
+ * Unregisters a listener for all sensors.
+ *
+ * @param listener a SensorListener object
+ *
+ */
+ public void unregisterListener(SensorEventListener listener) {
+ unregisterListener((Object)listener);
+ }
+
+
+ /**
+ * Registers a {@link android.hardware.SensorEventListener SensorEventListener}
+ * for the given sensor.
+ *
+ * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object.
+ * @param sensor The {@link android.hardware.Sensor Sensor} to register to.
+ * @param rate The rate {@link android.hardware.SensorEvent sensor events} are delivered at.
+ * This is only a hint to the system. Events may be received faster or
+ * slower than the specified rate. Usually events are received faster.
+ *
+ * @return true if the sensor is supported and successfully enabled.
+ *
+ */
+ public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate) {
+ return registerListener(listener, sensor, rate, null);
+ }
+
+ /**
+ * Registers a {@link android.hardware.SensorEventListener SensorEventListener}
+ * for the given sensor.
+ *
+ * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object.
+ * @param sensor The {@link android.hardware.Sensor Sensor} to register to.
+ * @param rate The rate {@link android.hardware.SensorEvent sensor events} are delivered at.
+ * This is only a hint to the system. Events may be received faster or
+ * slower than the specified rate. Usually events are received faster.
+ * @param handler The {@link android.os.Handler Handler} the
+ * {@link android.hardware.SensorEvent sensor events} will be delivered to.
+ *
+ * @return true if the sensor is supported and successfully enabled.
+ *
+ */
+ public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate,
+ Handler handler) {
+ boolean result;
int delay = -1;
switch (rate) {
case SENSOR_DELAY_FASTEST:
@@ -496,15 +823,15 @@ public class SensorManager extends IRotationWatcher.Stub
synchronized (sListeners) {
ListenerDelegate l = null;
for (ListenerDelegate i : sListeners) {
- if (i.mListener == listener) {
+ if (i.getListener() == listener) {
l = i;
break;
}
}
if (l == null) {
- l = new ListenerDelegate(listener, sensors);
- result = mSensorService.enableSensor(l, sensors, delay);
+ l = new ListenerDelegate(listener, sensor, handler);
+ result = mSensorService.enableSensor(l, sensor.getHandle(), delay);
if (result) {
sListeners.add(l);
sListeners.notify();
@@ -513,9 +840,9 @@ public class SensorManager extends IRotationWatcher.Stub
sSensorThread.startLocked(mSensorService);
}
} else {
- result = mSensorService.enableSensor(l, sensors, delay);
+ result = mSensorService.enableSensor(l, sensor.getHandle(), delay);
if (result) {
- l.addSensors(sensors);
+ l.addSensor(sensor);
}
}
}
@@ -526,25 +853,42 @@ public class SensorManager extends IRotationWatcher.Stub
return result;
}
- /**
- * Unregisters a listener for the sensors with which it is registered.
- *
- * @param listener a SensorListener object
- * @param sensors a bit masks of the sensors to unregister from
- */
- public void unregisterListener(SensorListener listener, int sensors) {
+ private void unregisterListener(Object listener, Sensor sensor) {
try {
synchronized (sListeners) {
final int size = sListeners.size();
for (int i=0 ; i<size ; i++) {
ListenerDelegate l = sListeners.get(i);
- if (l.mListener == listener) {
+ if (l.getListener() == listener) {
// disable these sensors
- mSensorService.enableSensor(l, sensors, SENSOR_DISABLE);
+ int handle = sensor.getHandle();
+ mSensorService.enableSensor(l, handle, SENSOR_DISABLE);
// if we have no more sensors enabled on this listener,
// take it off the list.
- if (l.removeSensors(sensors) == 0)
+ if (l.removeSensor(sensor) == 0) {
sListeners.remove(i);
+ }
+ break;
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in unregisterListener: ", e);
+ }
+ }
+
+ private void unregisterListener(Object listener) {
+ try {
+ synchronized (sListeners) {
+ final int size = sListeners.size();
+ for (int i=0 ; i<size ; i++) {
+ ListenerDelegate l = sListeners.get(i);
+ if (l.getListener() == listener) {
+ // disable all sensors for this listener
+ for (Sensor sensor : l.getSensors()) {
+ mSensorService.enableSensor(l, sensor.getHandle(), SENSOR_DISABLE);
+ }
+ sListeners.remove(i);
break;
}
}
@@ -555,65 +899,541 @@ public class SensorManager extends IRotationWatcher.Stub
}
/**
- * Unregisters a listener for all sensors.
+ * Computes the inclination matrix <b>I</b> as well as the rotation
+ * matrix <b>R</b> transforming a vector from the
+ * device coordinate system to the world's coordinate system which is
+ * defined as a direct orthonormal basis, where:
+ *
+ * <li>X is defined as the vector product <b>Y.Z</b> (It is tangential to
+ * the ground at the device's current location and roughly points East).</li>
+ * <li>Y is tangential to the ground at the device's current location and
+ * points towards the magnetic North Pole.</li>
+ * <li>Z points towards the sky and is perpendicular to the ground.</li>
+ * <p>
+ * <hr>
+ * <p>By definition:
+ * <p>[0 0 g] = <b>R</b> * <b>gravity</b> (g = magnitude of gravity)
+ * <p>[0 m 0] = <b>I</b> * <b>R</b> * <b>geomagnetic</b>
+ * (m = magnitude of geomagnetic field)
+ * <p><b>R</b> is the identity matrix when the device is aligned with the
+ * world's coordinate system, that is, when the device's X axis points
+ * toward East, the Y axis points to the North Pole and the device is facing
+ * the sky.
*
- * @param listener a SensorListener object
+ * <p><b>I</b> is a rotation matrix transforming the geomagnetic
+ * vector into the same coordinate space as gravity (the world's coordinate
+ * space). <b>I</b> is a simple rotation around the X axis.
+ * The inclination angle in radians can be computed with
+ * {@link #getInclination}.
+ * <hr>
+ *
+ * <p> Each matrix is returned either as a 3x3 or 4x4 row-major matrix
+ * depending on the length of the passed array:
+ * <p><u>If the array length is 16:</u>
+ * <pre>
+ * / M[ 0] M[ 1] M[ 2] M[ 3] \
+ * | M[ 4] M[ 5] M[ 6] M[ 7] |
+ * | M[ 8] M[ 9] M[10] M[11] |
+ * \ M[12] M[13] M[14] M[15] /
+ *</pre>
+ * This matrix is ready to be used by OpenGL ES's
+ * {@link javax.microedition.khronos.opengles.GL10#glLoadMatrixf(float[], int)
+ * glLoadMatrixf(float[], int)}.
+ * <p>Note that because OpenGL matrices are column-major matrices you must
+ * transpose the matrix before using it. However, since the matrix is a
+ * rotation matrix, its transpose is also its inverse, conveniently, it is
+ * often the inverse of the rotation that is needed for rendering; it can
+ * therefore be used with OpenGL ES directly.
+ * <p>
+ * Also note that the returned matrices always have this form:
+ * <pre>
+ * / M[ 0] M[ 1] M[ 2] 0 \
+ * | M[ 4] M[ 5] M[ 6] 0 |
+ * | M[ 8] M[ 9] M[10] 0 |
+ * \ 0 0 0 1 /
+ *</pre>
+ * <p><u>If the array length is 9:</u>
+ * <pre>
+ * / M[ 0] M[ 1] M[ 2] \
+ * | M[ 3] M[ 4] M[ 5] |
+ * \ M[ 6] M[ 7] M[ 8] /
+ *</pre>
+ *
+ * <hr>
+ * <p>The inverse of each matrix can be computed easily by taking its
+ * transpose.
+ *
+ * <p>The matrices returned by this function are meaningful only when the
+ * device is not free-falling and it is not close to the magnetic north.
+ * If the device is accelerating, or placed into a strong magnetic field,
+ * the returned matrices may be inaccurate.
+ *
+ * @param R is an array of 9 floats holding the rotation matrix <b>R</b>
+ * when this function returns. R can be null.<p>
+ * @param I is an array of 9 floats holding the rotation matrix <b>I</b>
+ * when this function returns. I can be null.<p>
+ * @param gravity is an array of 3 floats containing the gravity vector
+ * expressed in the device's coordinate. You can simply use the
+ * {@link android.hardware.SensorEvent#values values}
+ * returned by a {@link android.hardware.SensorEvent SensorEvent} of a
+ * {@link android.hardware.Sensor Sensor} of type
+ * {@link android.hardware.Sensor#TYPE_ACCELEROMETER TYPE_ACCELEROMETER}.<p>
+ * @param geomagnetic is an array of 3 floats containing the geomagnetic
+ * vector expressed in the device's coordinate. You can simply use the
+ * {@link android.hardware.SensorEvent#values values}
+ * returned by a {@link android.hardware.SensorEvent SensorEvent} of a
+ * {@link android.hardware.Sensor Sensor} of type
+ * {@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD TYPE_MAGNETIC_FIELD}.
+ * @return
+ * true on success<p>
+ * false on failure (for instance, if the device is in free fall).
+ * On failure the output matrices are not modified.
*/
- public void unregisterListener(SensorListener listener) {
- unregisterListener(listener, SENSOR_ALL);
+
+ public static boolean getRotationMatrix(float[] R, float[] I,
+ float[] gravity, float[] geomagnetic) {
+ // TODO: move this to native code for efficiency
+ float Ax = gravity[0];
+ float Ay = gravity[1];
+ float Az = gravity[2];
+ final float Ex = geomagnetic[0];
+ final float Ey = geomagnetic[1];
+ final float Ez = geomagnetic[2];
+ float Hx = Ey*Az - Ez*Ay;
+ float Hy = Ez*Ax - Ex*Az;
+ float Hz = Ex*Ay - Ey*Ax;
+ final float normH = (float)Math.sqrt(Hx*Hx + Hy*Hy + Hz*Hz);
+ if (normH < 0.1f) {
+ // device is close to free fall (or in space?), or close to
+ // magnetic north pole. Typical values are > 100.
+ return false;
+ }
+ final float invH = 1.0f / normH;
+ Hx *= invH;
+ Hy *= invH;
+ Hz *= invH;
+ final float invA = 1.0f / (float)Math.sqrt(Ax*Ax + Ay*Ay + Az*Az);
+ Ax *= invA;
+ Ay *= invA;
+ Az *= invA;
+ final float Mx = Ay*Hz - Az*Hy;
+ final float My = Az*Hx - Ax*Hz;
+ final float Mz = Ax*Hy - Ay*Hx;
+ if (R != null) {
+ if (R.length == 9) {
+ R[0] = Hx; R[1] = Hy; R[2] = Hz;
+ R[3] = Mx; R[4] = My; R[5] = Mz;
+ R[6] = Ax; R[7] = Ay; R[8] = Az;
+ } else if (R.length == 16) {
+ R[0] = Hx; R[1] = Hy; R[2] = Hz; R[3] = 0;
+ R[4] = Mx; R[5] = My; R[6] = Mz; R[7] = 0;
+ R[8] = Ax; R[9] = Ay; R[10] = Az; R[11] = 0;
+ R[12] = 0; R[13] = 0; R[14] = 0; R[15] = 1;
+ }
+ }
+ if (I != null) {
+ // compute the inclination matrix by projecting the geomagnetic
+ // vector onto the Z (gravity) and X (horizontal component
+ // of geomagnetic vector) axes.
+ final float invE = 1.0f / (float)Math.sqrt(Ex*Ex + Ey*Ey + Ez*Ez);
+ final float c = (Ex*Mx + Ey*My + Ez*Mz) * invE;
+ final float s = (Ex*Ax + Ey*Ay + Ez*Az) * invE;
+ if (I.length == 9) {
+ I[0] = 1; I[1] = 0; I[2] = 0;
+ I[3] = 0; I[4] = c; I[5] = s;
+ I[6] = 0; I[7] =-s; I[8] = c;
+ } else if (I.length == 16) {
+ I[0] = 1; I[1] = 0; I[2] = 0;
+ I[4] = 0; I[5] = c; I[6] = s;
+ I[8] = 0; I[9] =-s; I[10]= c;
+ I[3] = I[7] = I[11] = I[12] = I[13] = I[14] = 0;
+ I[15] = 1;
+ }
+ }
+ return true;
}
+ /**
+ * Computes the geomagnetic inclination angle in radians from the
+ * inclination matrix <b>I</b> returned by {@link #getRotationMatrix}.
+ * @param I inclination matrix see {@link #getRotationMatrix}.
+ * @return The geomagnetic inclination angle in radians.
+ */
+ public static float getInclination(float[] I) {
+ if (I.length == 9) {
+ return (float)Math.atan2(I[5], I[4]);
+ } else {
+ return (float)Math.atan2(I[6], I[5]);
+ }
+ }
/**
- * Helper function to convert the specified sensor's data to the windows's
- * coordinate space from the device's coordinate space.
- */
-
- private static void mapSensorDataToWindow(int sensor, float[] values, int orientation) {
- final float x = values[DATA_X];
- final float y = values[DATA_Y];
- final float z = values[DATA_Z];
- // copy the raw raw values...
- values[RAW_DATA_X] = x;
- values[RAW_DATA_Y] = y;
- values[RAW_DATA_Z] = z;
- // TODO: add support for 180 and 270 orientations
- if (orientation == Surface.ROTATION_90) {
- switch (sensor) {
- case SENSOR_ACCELEROMETER:
- case SENSOR_MAGNETIC_FIELD:
- values[DATA_X] =-y;
- values[DATA_Y] = x;
- values[DATA_Z] = z;
- break;
- case SENSOR_ORIENTATION:
- case SENSOR_ORIENTATION_RAW:
- values[DATA_X] = x + ((x < 270) ? 90 : -270);
- values[DATA_Y] = z;
- values[DATA_Z] = y;
- break;
+ * Rotates the supplied rotation matrix so it is expressed in a
+ * different coordinate system. This is typically used when an application
+ * needs to compute the three orientation angles of the device (see
+ * {@link #getOrientation}) in a different coordinate system.
+ *
+ * <p>When the rotation matrix is used for drawing (for instance with
+ * OpenGL ES), it usually <b>doesn't need</b> to be transformed by this
+ * function, unless the screen is physically rotated, such as when used
+ * in landscape mode.
+ *
+ * <p><u>Examples:</u><p>
+ *
+ * <li>Using the camera (Y axis along the camera's axis) for an augmented
+ * reality application where the rotation angles are needed :</li><p>
+ *
+ * <code>remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR);</code><p>
+ *
+ * <li>Using the device as a mechanical compass in landscape mode:</li><p>
+ *
+ * <code>remapCoordinateSystem(inR, AXIS_Y, AXIS_MINUS_X, outR);</code><p>
+ *
+ * Beware of the above example. This call is needed only if the device is
+ * physically used in landscape mode to calculate the rotation angles (see
+ * {@link #getOrientation}).
+ * If the rotation matrix is also used for rendering, it may not need to
+ * be transformed, for instance if your {@link android.app.Activity
+ * Activity} is running in landscape mode.
+ *
+ * <p>Since the resulting coordinate system is orthonormal, only two axes
+ * need to be specified.
+ *
+ * @param inR the rotation matrix to be transformed. Usually it is the
+ * matrix returned by {@link #getRotationMatrix}.
+ * @param X defines on which world axis and direction the X axis of the
+ * device is mapped.
+ * @param Y defines on which world axis and direction the Y axis of the
+ * device is mapped.
+ * @param outR the transformed rotation matrix. inR and outR can be the same
+ * array, but it is not recommended for performance reason.
+ * @return true on success. false if the input parameters are incorrect, for
+ * instance if X and Y define the same axis. Or if inR and outR don't have
+ * the same length.
+ */
+
+ public static boolean remapCoordinateSystem(float[] inR, int X, int Y,
+ float[] outR)
+ {
+ if (inR == outR) {
+ final float[] temp = mTempMatrix;
+ synchronized(temp) {
+ // we don't expect to have a lot of contention
+ if (remapCoordinateSystemImpl(inR, X, Y, temp)) {
+ final int size = outR.length;
+ for (int i=0 ; i<size ; i++)
+ outR[i] = temp[i];
+ return true;
+ }
}
}
+ return remapCoordinateSystemImpl(inR, X, Y, outR);
}
+ private static boolean remapCoordinateSystemImpl(float[] inR, int X, int Y,
+ float[] outR)
+ {
+ /*
+ * X and Y define a rotation matrix 'r':
+ *
+ * (X==1)?((X&0x80)?-1:1):0 (X==2)?((X&0x80)?-1:1):0 (X==3)?((X&0x80)?-1:1):0
+ * (Y==1)?((Y&0x80)?-1:1):0 (Y==2)?((Y&0x80)?-1:1):0 (Y==3)?((X&0x80)?-1:1):0
+ * r[0] ^ r[1]
+ *
+ * where the 3rd line is the vector product of the first 2 lines
+ *
+ */
+
+ final int length = outR.length;
+ if (inR.length != length)
+ return false; // invalid parameter
+ if ((X & 0x7C)!=0 || (Y & 0x7C)!=0)
+ return false; // invalid parameter
+ if (((X & 0x3)==0) || ((Y & 0x3)==0))
+ return false; // no axis specified
+ if ((X & 0x3) == (Y & 0x3))
+ return false; // same axis specified
+
+ // Z is "the other" axis, its sign is either +/- sign(X)*sign(Y)
+ // this can be calculated by exclusive-or'ing X and Y; except for
+ // the sign inversion (+/-) which is calculated below.
+ int Z = X ^ Y;
+
+ // extract the axis (remove the sign), offset in the range 0 to 2.
+ final int x = (X & 0x3)-1;
+ final int y = (Y & 0x3)-1;
+ final int z = (Z & 0x3)-1;
+
+ // compute the sign of Z (whether it needs to be inverted)
+ final int axis_y = (z+1)%3;
+ final int axis_z = (z+2)%3;
+ if (((x^axis_y)|(y^axis_z)) != 0)
+ Z ^= 0x80;
+
+ final boolean sx = (X>=0x80);
+ final boolean sy = (Y>=0x80);
+ final boolean sz = (Z>=0x80);
+
+ // Perform R * r, in avoiding actual muls and adds.
+ final int rowLength = ((length==16)?4:3);
+ for (int j=0 ; j<3 ; j++) {
+ final int offset = j*rowLength;
+ for (int i=0 ; i<3 ; i++) {
+ if (x==i) outR[offset+i] = sx ? -inR[offset+0] : inR[offset+0];
+ if (y==i) outR[offset+i] = sy ? -inR[offset+1] : inR[offset+1];
+ if (z==i) outR[offset+i] = sz ? -inR[offset+2] : inR[offset+2];
+ }
+ }
+ if (length == 16) {
+ outR[3] = outR[7] = outR[11] = outR[12] = outR[13] = outR[14] = 0;
+ outR[15] = 1;
+ }
+ return true;
+ }
- private static native int _sensors_data_open(FileDescriptor fd);
- private static native int _sensors_data_close();
- // returns the sensor's status in the top 4 bits of "res".
- private static native int _sensors_data_poll(float[] values, int sensors);
- private static native int _sensors_data_get_sensors();
+ /**
+ * Computes the device's orientation based on the rotation matrix.
+ * <p> When it returns, the array values is filled with the result:
+ * <li>values[0]: <i>azimuth</i>, rotation around the Z axis.</li>
+ * <li>values[1]: <i>pitch</i>, rotation around the X axis.</li>
+ * <li>values[2]: <i>roll</i>, rotation around the Y axis.</li>
+ * <p>
+ *
+ * @param R rotation matrix see {@link #getRotationMatrix}.
+ * @param values an array of 3 floats to hold the result.
+ * @return The array values passed as argument.
+ */
+ public static float[] getOrientation(float[] R, float values[]) {
+ /*
+ * 4x4 (length=16) case:
+ * / R[ 0] R[ 1] R[ 2] 0 \
+ * | R[ 4] R[ 5] R[ 6] 0 |
+ * | R[ 8] R[ 9] R[10] 0 |
+ * \ 0 0 0 1 /
+ *
+ * 3x3 (length=9) case:
+ * / R[ 0] R[ 1] R[ 2] \
+ * | R[ 3] R[ 4] R[ 5] |
+ * \ R[ 6] R[ 7] R[ 8] /
+ *
+ */
+ if (R.length == 9) {
+ values[0] = (float)Math.atan2(R[1], R[4]);
+ values[1] = (float)Math.asin(-R[7]);
+ values[2] = (float)Math.atan2(-R[6], R[8]);
+ } else {
+ values[0] = (float)Math.atan2(R[1], R[5]);
+ values[1] = (float)Math.asin(-R[9]);
+ values[2] = (float)Math.atan2(-R[8], R[10]);
+ }
+ return values;
+ }
- /** {@hide} */
+
+ /**
+ * {@hide}
+ */
public void onRotationChanged(int rotation) {
synchronized(sListeners) {
sRotation = rotation;
}
}
-
- private static int getRotation() {
+
+ static int getRotation() {
synchronized(sListeners) {
return sRotation;
}
}
-}
+ private class LegacyListener implements SensorEventListener {
+ private float mValues[] = new float[6];
+ @SuppressWarnings("deprecation")
+ private SensorListener mTarget;
+ private int mSensors;
+ private final LmsFilter mYawfilter = new LmsFilter();
+
+ @SuppressWarnings("deprecation")
+ LegacyListener(SensorListener target) {
+ mTarget = target;
+ mSensors = 0;
+ }
+
+ void registerSensor(int legacyType) {
+ mSensors |= legacyType;
+ }
+
+ boolean unregisterSensor(int legacyType) {
+ mSensors &= ~legacyType;
+ int mask = SENSOR_ORIENTATION|SENSOR_ORIENTATION_RAW;
+ if (((legacyType&mask)!=0) && ((mSensors&mask)!=0)) {
+ return false;
+ }
+ return true;
+ }
+
+ @SuppressWarnings("deprecation")
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ try {
+ mTarget.onAccuracyChanged(sensor.getLegacyType(), accuracy);
+ } catch (AbstractMethodError e) {
+ // old app that doesn't implement this method
+ // just ignore it.
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public void onSensorChanged(SensorEvent event) {
+ final float v[] = mValues;
+ v[0] = event.values[0];
+ v[1] = event.values[1];
+ v[2] = event.values[2];
+ int legacyType = event.sensor.getLegacyType();
+ mapSensorDataToWindow(legacyType, v, SensorManager.getRotation());
+ if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
+ if ((mSensors & SENSOR_ORIENTATION_RAW)!=0) {
+ mTarget.onSensorChanged(SENSOR_ORIENTATION_RAW, v);
+ }
+ if ((mSensors & SENSOR_ORIENTATION)!=0) {
+ v[0] = mYawfilter.filter(event.timestamp, v[0]);
+ mTarget.onSensorChanged(SENSOR_ORIENTATION, v);
+ }
+ } else {
+ mTarget.onSensorChanged(legacyType, v);
+ }
+ }
+
+ /*
+ * Helper function to convert the specified sensor's data to the windows's
+ * coordinate space from the device's coordinate space.
+ *
+ * output: 3,4,5: values in the old API format
+ * 0,1,2: transformed values in the old API format
+ *
+ */
+ private void mapSensorDataToWindow(int sensor,
+ float[] values, int orientation) {
+ float x = values[0];
+ float y = values[1];
+ float z = values[2];
+
+ switch (sensor) {
+ case SensorManager.SENSOR_ORIENTATION:
+ case SensorManager.SENSOR_ORIENTATION_RAW:
+ z = -z;
+ break;
+ case SensorManager.SENSOR_ACCELEROMETER:
+ x = -x;
+ y = -y;
+ z = -z;
+ break;
+ case SensorManager.SENSOR_MAGNETIC_FIELD:
+ x = -x;
+ y = -y;
+ break;
+ }
+ values[0] = x;
+ values[1] = y;
+ values[2] = z;
+ values[3] = x;
+ values[4] = y;
+ values[5] = z;
+ // TODO: add support for 180 and 270 orientations
+ if (orientation == Surface.ROTATION_90) {
+ switch (sensor) {
+ case SENSOR_ACCELEROMETER:
+ case SENSOR_MAGNETIC_FIELD:
+ values[0] =-y;
+ values[1] = x;
+ values[2] = z;
+ break;
+ case SENSOR_ORIENTATION:
+ case SENSOR_ORIENTATION_RAW:
+ values[0] = x + ((x < 270) ? 90 : -270);
+ values[1] = z;
+ values[2] = y;
+ break;
+ }
+ }
+ }
+ }
+
+ class LmsFilter {
+ private static final int SENSORS_RATE_MS = 20;
+ private static final int COUNT = 12;
+ private static final float PREDICTION_RATIO = 1.0f/3.0f;
+ private static final float PREDICTION_TIME = (SENSORS_RATE_MS*COUNT/1000.0f)*PREDICTION_RATIO;
+ private float mV[] = new float[COUNT*2];
+ private float mT[] = new float[COUNT*2];
+ private int mIndex;
+
+ public LmsFilter() {
+ mIndex = COUNT;
+ }
+
+ public float filter(long time, float in) {
+ float v = in;
+ final float ns = 1.0f / 1000000000.0f;
+ final float t = time*ns;
+ float v1 = mV[mIndex];
+ if ((v-v1) > 180) {
+ v -= 360;
+ } else if ((v1-v) > 180) {
+ v += 360;
+ }
+ /* Manage the circular buffer, we write the data twice spaced
+ * by COUNT values, so that we don't have to copy the array
+ * when it's full
+ */
+ mIndex++;
+ if (mIndex >= COUNT*2)
+ mIndex = COUNT;
+ mV[mIndex] = v;
+ mT[mIndex] = t;
+ mV[mIndex-COUNT] = v;
+ mT[mIndex-COUNT] = t;
+
+ float A, B, C, D, E;
+ float a, b;
+ int i;
+
+ A = B = C = D = E = 0;
+ for (i=0 ; i<COUNT-1 ; i++) {
+ final int j = mIndex - 1 - i;
+ final float Z = mV[j];
+ final float T = 0.5f*(mT[j] + mT[j+1]) - t;
+ float dT = mT[j] - mT[j+1];
+ dT *= dT;
+ A += Z*dT;
+ B += T*(T*dT);
+ C += (T*dT);
+ D += Z*(T*dT);
+ E += dT;
+ }
+ b = (A*B + C*D) / (E*B + C*C);
+ a = (E*b - A) / C;
+ float f = b + PREDICTION_TIME*a;
+
+ // Normalize
+ f *= (1.0f / 360.0f);
+ if (((f>=0)?f:-f) >= 0.5f)
+ f = f - (float)Math.ceil(f + 0.5f) + 1.0f;
+ if (f < 0)
+ f += 1.0f;
+ f *= 360.0f;
+ return f;
+ }
+ }
+
+
+ private static native void nativeClassInit();
+
+ private static native int sensors_module_init();
+ private static native int sensors_module_get_next_sensor(Sensor sensor, int next);
+
+ // Used within this module from outside SensorManager, don't make private
+ static native int sensors_data_init();
+ static native int sensors_data_uninit();
+ static native int sensors_data_open(FileDescriptor fd);
+ static native int sensors_data_close();
+ static native int sensors_data_poll(float[] values, int[] status, long[] timestamp);
+}
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
new file mode 100644
index 0000000..7d02f65
--- /dev/null
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -0,0 +1,170 @@
+/*
+ * 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.inputmethodservice;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodSession;
+
+/**
+ * AbstractInputMethodService provides a abstract base class for input methods.
+ * Normal input method implementations will not derive from this directly,
+ * instead building on top of {@link InputMethodService} or another more
+ * complete base class. Be sure to read {@link InputMethod} for more
+ * information on the basics of writing input methods.
+ *
+ * <p>This class combines a Service (representing the input method component
+ * to the system with the InputMethod interface that input methods must
+ * implement. This base class takes care of reporting your InputMethod from
+ * the service when clients bind to it, but provides no standard implementation
+ * of the InputMethod interface itself. Derived classes must implement that
+ * interface.
+ */
+public abstract class AbstractInputMethodService extends Service
+ implements KeyEvent.Callback {
+ private InputMethod mInputMethod;
+
+ /**
+ * Base class for derived classes to implement their {@link InputMethod}
+ * interface. This takes care of basic maintenance of the input method,
+ * but most behavior must be implemented in a derived class.
+ */
+ public abstract class AbstractInputMethodImpl implements InputMethod {
+ /**
+ * Instantiate a new client session for the input method, by calling
+ * back to {@link AbstractInputMethodService#onCreateInputMethodSessionInterface()
+ * AbstractInputMethodService.onCreateInputMethodSessionInterface()}.
+ */
+ public void createSession(SessionCallback callback) {
+ callback.sessionCreated(onCreateInputMethodSessionInterface());
+ }
+
+ /**
+ * Take care of enabling or disabling an existing session by calling its
+ * {@link AbstractInputMethodSessionImpl#revokeSelf()
+ * AbstractInputMethodSessionImpl.setEnabled()} method.
+ */
+ public void setSessionEnabled(InputMethodSession session, boolean enabled) {
+ ((AbstractInputMethodSessionImpl)session).setEnabled(enabled);
+ }
+
+ /**
+ * Take care of killing an existing session by calling its
+ * {@link AbstractInputMethodSessionImpl#revokeSelf()
+ * AbstractInputMethodSessionImpl.revokeSelf()} method.
+ */
+ public void revokeSession(InputMethodSession session) {
+ ((AbstractInputMethodSessionImpl)session).revokeSelf();
+ }
+ }
+
+ /**
+ * Base class for derived classes to implement their {@link InputMethodSession}
+ * interface. This takes care of basic maintenance of the session,
+ * but most behavior must be implemented in a derived class.
+ */
+ public abstract class AbstractInputMethodSessionImpl implements InputMethodSession {
+ boolean mEnabled = true;
+ boolean mRevoked;
+
+ /**
+ * Check whether this session has been enabled by the system. If not
+ * enabled, you should not execute any calls on to it.
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ /**
+ * Check whether this session has been revoked by the system. Revoked
+ * session is also always disabled, so there is generally no need to
+ * explicitly check for this.
+ */
+ public boolean isRevoked() {
+ return mRevoked;
+ }
+
+ /**
+ * Change the enabled state of the session. This only works if the
+ * session has not been revoked.
+ */
+ public void setEnabled(boolean enabled) {
+ if (!mRevoked) {
+ mEnabled = enabled;
+ }
+ }
+
+ /**
+ * Revoke the session from the client. This disabled the session, and
+ * prevents it from ever being enabled again.
+ */
+ public void revokeSelf() {
+ mRevoked = true;
+ mEnabled = false;
+ }
+
+ /**
+ * Take care of dispatching incoming key events to the appropriate
+ * callbacks on the service, and tell the client when this is done.
+ */
+ public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback) {
+ boolean handled = event.dispatch(AbstractInputMethodService.this);
+ if (callback != null) {
+ callback.finishedEvent(seq, handled);
+ }
+ }
+
+ /**
+ * Take care of dispatching incoming trackball events to the appropriate
+ * callbacks on the service, and tell the client when this is done.
+ */
+ public void dispatchTrackballEvent(int seq, MotionEvent event, EventCallback callback) {
+ boolean handled = onTrackballEvent(event);
+ if (callback != null) {
+ callback.finishedEvent(seq, handled);
+ }
+ }
+ }
+
+ /**
+ * Called by the framework during initialization, when the InputMethod
+ * interface for this service needs to be created.
+ */
+ public abstract AbstractInputMethodImpl onCreateInputMethodInterface();
+
+ /**
+ * Called by the framework when a new InputMethodSession interface is
+ * needed for a new client of the input method.
+ */
+ public abstract AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
+
+ @Override
+ final public IBinder onBind(Intent intent) {
+ if (mInputMethod == null) {
+ mInputMethod = onCreateInputMethodInterface();
+ }
+ return new IInputMethodWrapper(this, mInputMethod);
+ }
+
+ public boolean onTrackballEvent(MotionEvent event) {
+ return false;
+ }
+}
diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
new file mode 100644
index 0000000..e59f38b
--- /dev/null
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -0,0 +1,23 @@
+package android.inputmethodservice;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.EditText;
+
+/***
+ * Specialization of {@link EditText} for showing and interacting with the
+ * extracted text in a full-screen input method.
+ */
+public class ExtractEditText extends EditText {
+ public ExtractEditText(Context context) {
+ super(context, null);
+ }
+
+ public ExtractEditText(Context context, AttributeSet attrs) {
+ super(context, attrs, com.android.internal.R.attr.editTextStyle);
+ }
+
+ public ExtractEditText(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+}
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
new file mode 100644
index 0000000..40c03cd
--- /dev/null
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -0,0 +1,151 @@
+package android.inputmethodservice;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.view.IInputMethodCallback;
+import com.android.internal.view.IInputMethodSession;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.InputMethodSession;
+import android.view.inputmethod.EditorInfo;
+
+class IInputMethodSessionWrapper extends IInputMethodSession.Stub
+ implements HandlerCaller.Callback {
+ private static final String TAG = "InputMethodWrapper";
+ private static final boolean DEBUG = false;
+
+ private static final int DO_FINISH_INPUT = 60;
+ private static final int DO_DISPLAY_COMPLETIONS = 65;
+ private static final int DO_UPDATE_EXTRACTED_TEXT = 67;
+ private static final int DO_DISPATCH_KEY_EVENT = 70;
+ private static final int DO_DISPATCH_TRACKBALL_EVENT = 80;
+ 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;
+
+ final HandlerCaller mCaller;
+ final InputMethodSession mInputMethodSession;
+
+ // NOTE: we should have a cache of these.
+ 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 IInputMethodSessionWrapper(Context context,
+ InputMethodSession inputMethodSession) {
+ mCaller = new HandlerCaller(context, this);
+ mInputMethodSession = inputMethodSession;
+ }
+
+ public InputMethodSession getInternalInputMethodSession() {
+ return mInputMethodSession;
+ }
+
+ public void executeMessage(Message msg) {
+ switch (msg.what) {
+ case DO_FINISH_INPUT:
+ mInputMethodSession.finishInput();
+ return;
+ case DO_DISPLAY_COMPLETIONS:
+ mInputMethodSession.displayCompletions((CompletionInfo[])msg.obj);
+ return;
+ case DO_UPDATE_EXTRACTED_TEXT:
+ mInputMethodSession.updateExtractedText(msg.arg1,
+ (ExtractedText)msg.obj);
+ return;
+ case DO_DISPATCH_KEY_EVENT: {
+ HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ mInputMethodSession.dispatchKeyEvent(msg.arg1,
+ (KeyEvent)args.arg1,
+ new InputMethodEventCallbackWrapper(
+ (IInputMethodCallback)args.arg2));
+ mCaller.recycleArgs(args);
+ return;
+ }
+ case DO_DISPATCH_TRACKBALL_EVENT: {
+ HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ mInputMethodSession.dispatchTrackballEvent(msg.arg1,
+ (MotionEvent)args.arg1,
+ new InputMethodEventCallbackWrapper(
+ (IInputMethodCallback)args.arg2));
+ mCaller.recycleArgs(args);
+ return;
+ }
+ case DO_UPDATE_SELECTION: {
+ HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ mInputMethodSession.updateSelection(args.argi1, args.argi2,
+ args.argi3, args.argi4);
+ mCaller.recycleArgs(args);
+ return;
+ }
+ case DO_UPDATE_CURSOR: {
+ mInputMethodSession.updateCursor((Rect)msg.obj);
+ return;
+ }
+ case DO_APP_PRIVATE_COMMAND: {
+ HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ mInputMethodSession.appPrivateCommand((String)args.arg1,
+ (Bundle)args.arg2);
+ mCaller.recycleArgs(args);
+ return;
+ }
+ }
+ Log.w(TAG, "Unhandled message code: " + msg.what);
+ }
+
+ public void finishInput() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_INPUT));
+ }
+
+ public void displayCompletions(CompletionInfo[] completions) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(
+ DO_DISPLAY_COMPLETIONS, completions));
+ }
+
+ public void updateExtractedText(int token, ExtractedText text) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
+ DO_UPDATE_EXTRACTED_TEXT, token, text));
+ }
+
+ public void dispatchKeyEvent(int seq, KeyEvent event, IInputMethodCallback callback) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_DISPATCH_KEY_EVENT, seq,
+ event, callback));
+ }
+
+ public void dispatchTrackballEvent(int seq, MotionEvent event, IInputMethodCallback callback) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_DISPATCH_TRACKBALL_EVENT, seq,
+ event, callback));
+ }
+
+ public void updateSelection(int oldSelStart, int oldSelEnd,
+ int newSelStart, int newSelEnd) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIIII(DO_UPDATE_SELECTION,
+ oldSelStart, oldSelEnd, newSelStart, newSelEnd));
+ }
+
+ public void updateCursor(Rect newCursor) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_UPDATE_CURSOR,
+ newCursor));
+ }
+
+ public void appPrivateCommand(String action, Bundle data) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data));
+ }
+}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
new file mode 100644
index 0000000..4108bdd
--- /dev/null
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -0,0 +1,172 @@
+package android.inputmethodservice;
+
+import com.android.internal.os.HandlerCaller;
+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;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodSession;
+
+/**
+ * Implements the internal IInputMethod interface to convert incoming calls
+ * on to it back to calls on the public InputMethod interface, scheduling
+ * them on the main thread of the process.
+ */
+class IInputMethodWrapper extends IInputMethod.Stub
+ implements HandlerCaller.Callback {
+ private static final String TAG = "InputMethodWrapper";
+ private static final boolean DEBUG = false;
+
+ 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;
+ private static final int DO_START_INPUT = 32;
+ private static final int DO_RESTART_INPUT = 34;
+ private static final int DO_CREATE_SESSION = 40;
+ private static final int DO_SET_SESSION_ENABLED = 45;
+ private static final int DO_REVOKE_SESSION = 50;
+ private static final int DO_SHOW_SOFT_INPUT = 60;
+ private static final int DO_HIDE_SOFT_INPUT = 70;
+
+ final HandlerCaller mCaller;
+ final InputMethod mInputMethod;
+
+ // NOTE: we should have a cache of these.
+ static class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
+ final Context mContext;
+ final IInputMethodCallback mCb;
+ InputMethodSessionCallbackWrapper(Context context, IInputMethodCallback cb) {
+ mContext = context;
+ mCb = cb;
+ }
+ public void sessionCreated(InputMethodSession session) {
+ try {
+ if (session != null) {
+ IInputMethodSessionWrapper wrap =
+ new IInputMethodSessionWrapper(mContext, session);
+ mCb.sessionCreated(wrap);
+ } else {
+ mCb.sessionCreated(null);
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ public IInputMethodWrapper(Context context, InputMethod inputMethod) {
+ mCaller = new HandlerCaller(context, this);
+ mInputMethod = inputMethod;
+ }
+
+ public InputMethod getInternalInputMethod() {
+ return mInputMethod;
+ }
+
+ public void executeMessage(Message msg) {
+ switch (msg.what) {
+ case DO_ATTACH_TOKEN: {
+ mInputMethod.attachToken((IBinder)msg.obj);
+ return;
+ }
+ case DO_SET_INPUT_CONTEXT: {
+ mInputMethod.bindInput((InputBinding)msg.obj);
+ return;
+ }
+ case DO_UNSET_INPUT_CONTEXT:
+ mInputMethod.unbindInput();
+ return;
+ case DO_START_INPUT:
+ mInputMethod.startInput((EditorInfo)msg.obj);
+ return;
+ case DO_RESTART_INPUT:
+ mInputMethod.restartInput((EditorInfo)msg.obj);
+ return;
+ case DO_CREATE_SESSION: {
+ mInputMethod.createSession(new InputMethodSessionCallbackWrapper(
+ mCaller.mContext, (IInputMethodCallback)msg.obj));
+ return;
+ }
+ case DO_SET_SESSION_ENABLED:
+ mInputMethod.setSessionEnabled((InputMethodSession)msg.obj,
+ msg.arg1 != 0);
+ return;
+ case DO_REVOKE_SESSION:
+ mInputMethod.revokeSession((InputMethodSession)msg.obj);
+ return;
+ case DO_SHOW_SOFT_INPUT:
+ mInputMethod.showSoftInput();
+ return;
+ case DO_HIDE_SOFT_INPUT:
+ mInputMethod.hideSoftInput();
+ return;
+ }
+ Log.w(TAG, "Unhandled message code: " + msg.what);
+ }
+
+ public void attachToken(IBinder token) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
+ }
+
+ public void bindInput(InputBinding binding) {
+ InputConnection ic = new InputConnectionWrapper(
+ IInputContext.Stub.asInterface(binding.getConnectionToken()));
+ InputBinding nu = new InputBinding(ic, binding);
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
+ }
+
+ public void unbindInput() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
+ }
+
+ public void startInput(EditorInfo attribute) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_START_INPUT, attribute));
+ }
+
+ public void restartInput(EditorInfo attribute) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RESTART_INPUT, attribute));
+ }
+
+ public void createSession(IInputMethodCallback callback) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CREATE_SESSION, callback));
+ }
+
+ public void setSessionEnabled(IInputMethodSession session, boolean enabled) {
+ try {
+ InputMethodSession ls = ((IInputMethodSessionWrapper)
+ session).getInternalInputMethodSession();
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
+ DO_SET_SESSION_ENABLED, enabled ? 1 : 0, ls));
+ } catch (ClassCastException e) {
+ Log.w(TAG, "Incoming session not of correct type: " + session, e);
+ }
+ }
+
+ public void revokeSession(IInputMethodSession session) {
+ try {
+ InputMethodSession ls = ((IInputMethodSessionWrapper)
+ session).getInternalInputMethodSession();
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REVOKE_SESSION, ls));
+ } catch (ClassCastException e) {
+ Log.w(TAG, "Incoming session not of correct type: " + session, e);
+ }
+ }
+
+ public void showSoftInput() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_SHOW_SOFT_INPUT));
+ }
+
+ public void hideSoftInput() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_HIDE_SOFT_INPUT));
+ }
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
new file mode 100644
index 0000000..9ebf127
--- /dev/null
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -0,0 +1,864 @@
+/*
+ * 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.inputmethodservice;
+
+import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.EditorInfo;
+import android.widget.FrameLayout;
+
+/**
+ * InputMethodService provides a standard implementation of an InputMethod,
+ * which final implementations can derive from and customize. See the
+ * base class {@link AbstractInputMethodService} and the {@link InputMethod}
+ * interface for more information on the basics of writing input methods.
+ */
+public class InputMethodService extends AbstractInputMethodService {
+ static final String TAG = "InputMethodService";
+ static final boolean DEBUG = false;
+
+ LayoutInflater mInflater;
+ View mRootView;
+ SoftInputWindow mWindow;
+ boolean mWindowCreated;
+ boolean mWindowAdded;
+ boolean mWindowVisible;
+ FrameLayout mExtractFrame;
+ FrameLayout mCandidatesFrame;
+ FrameLayout mInputFrame;
+
+ IBinder mToken;
+
+ InputBinding mInputBinding;
+ InputConnection mInputConnection;
+ boolean mInputStarted;
+ EditorInfo mInputInfo;
+
+ boolean mShowInputRequested;
+ boolean mShowCandidatesRequested;
+
+ boolean mFullscreenApplied;
+ boolean mIsFullscreen;
+ View mExtractView;
+ ExtractEditText mExtractEditText;
+ ExtractedText mExtractedText;
+ int mExtractedToken;
+
+ View mInputView;
+ boolean mIsInputViewShown;
+
+ int mStatusIcon;
+
+ final Insets mTmpInsets = new Insets();
+ final int[] mTmpLocation = new int[2];
+
+ final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
+ new ViewTreeObserver.OnComputeInternalInsetsListener() {
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
+ if (isFullscreenMode()) {
+ // In fullscreen mode, we just say the window isn't covering
+ // any content so we don't impact whatever is behind.
+ View decor = getWindow().getWindow().getDecorView();
+ info.contentInsets.top = info.visibleInsets.top
+ = decor.getHeight();
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+ } else {
+ onComputeInsets(mTmpInsets);
+ info.contentInsets.top = mTmpInsets.contentTopInsets;
+ info.visibleInsets.top = mTmpInsets.visibleTopInsets;
+ info.setTouchableInsets(mTmpInsets.touchableInsets);
+ }
+ }
+ };
+
+ /**
+ * Concrete implementation of
+ * {@link AbstractInputMethodService.AbstractInputMethodImpl} that provides
+ * all of the standard behavior for an input method.
+ */
+ public class InputMethodImpl extends AbstractInputMethodImpl {
+ /**
+ * Take care of attaching the given window token provided by the system.
+ */
+ public void attachToken(IBinder token) {
+ if (mToken == null) {
+ mToken = token;
+ mWindow.setToken(token);
+ }
+ }
+
+ /**
+ * Handle a new input binding, calling
+ * {@link InputMethodService#onBindInput InputMethodService.onBindInput()}
+ * when done.
+ */
+ public void bindInput(InputBinding binding) {
+ mInputBinding = binding;
+ mInputConnection = binding.getConnection();
+ onBindInput();
+ }
+
+ /**
+ * Clear the current input binding.
+ */
+ public void unbindInput() {
+ mInputStarted = false;
+ mInputBinding = null;
+ mInputConnection = null;
+ }
+
+ public void startInput(EditorInfo attribute) {
+ doStartInput(attribute, false);
+ }
+
+ public void restartInput(EditorInfo attribute) {
+ doStartInput(attribute, false);
+ }
+
+ /**
+ * Handle a request by the system to hide the soft input area.
+ */
+ public void hideSoftInput() {
+ if (DEBUG) Log.v(TAG, "hideSoftInput()");
+ mShowInputRequested = false;
+ hideWindow();
+ }
+
+ /**
+ * Handle a request by the system to show the soft input area.
+ */
+ public void showSoftInput() {
+ if (DEBUG) Log.v(TAG, "showSoftInput()");
+ showWindow(true);
+ }
+ }
+
+ /**
+ * Concrete implementation of
+ * {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
+ * all of the standard behavior for an input method session.
+ */
+ public class InputMethodSessionImpl extends AbstractInputMethodSessionImpl {
+ public void finishInput() {
+ if (!isEnabled()) {
+ return;
+ }
+ onFinishInput();
+ mInputStarted = false;
+ }
+
+ /**
+ * Call {@link InputMethodService#onDisplayCompletions
+ * InputMethodService.onDisplayCompletions()}.
+ */
+ public void displayCompletions(CompletionInfo[] completions) {
+ if (!isEnabled()) {
+ return;
+ }
+ onDisplayCompletions(completions);
+ }
+
+ /**
+ * Call {@link InputMethodService#onUpdateExtractedText
+ * InputMethodService.onUpdateExtractedText()}.
+ */
+ public void updateExtractedText(int token, ExtractedText text) {
+ if (!isEnabled()) {
+ return;
+ }
+ onUpdateExtractedText(token, text);
+ }
+
+ /**
+ * Call {@link InputMethodService#onUpdateSelection
+ * InputMethodService.onUpdateSelection()}.
+ */
+ public void updateSelection(int oldSelStart, int oldSelEnd,
+ int newSelStart, int newSelEnd) {
+ if (!isEnabled()) {
+ return;
+ }
+ InputMethodService.this.onUpdateSelection(oldSelStart, oldSelEnd,
+ newSelStart, newSelEnd);
+ }
+
+ /**
+ * Call {@link InputMethodService#onUpdateCursor
+ * InputMethodService.onUpdateCursor()}.
+ */
+ public void updateCursor(Rect newCursor) {
+ if (!isEnabled()) {
+ return;
+ }
+ InputMethodService.this.onUpdateCursor(newCursor);
+ }
+
+ /**
+ * Call {@link InputMethodService#onAppPrivateCommand
+ * InputMethodService.onAppPrivateCommand()}.
+ */
+ public void appPrivateCommand(String action, Bundle data) {
+ if (!isEnabled()) {
+ return;
+ }
+ InputMethodService.this.onAppPrivateCommand(action, data);
+ }
+ }
+
+ /**
+ * Information about where interesting parts of the input method UI appear.
+ */
+ public static final class Insets {
+ /**
+ * This is the top part of the UI that is the main content. It is
+ * used to determine the basic space needed, to resize/pan the
+ * application behind. It is assumed that this inset does not
+ * change very much, since any change will cause a full resize/pan
+ * of the application behind. This value is relative to the top edge
+ * of the input method window.
+ */
+ int contentTopInsets;
+
+ /**
+ * This is the top part of the UI that is visibly covering the
+ * application behind it. This provides finer-grained control over
+ * visibility, allowing you to change it relatively frequently (such
+ * as hiding or showing candidates) without disrupting the underlying
+ * UI too much. For example, this will never resize the application
+ * UI, will only pan if needed to make the current focus visible, and
+ * will not aggressively move the pan position when this changes unless
+ * needed to make the focus visible. This value is relative to the top edge
+ * of the input method window.
+ */
+ int visibleTopInsets;
+
+ /**
+ * Option for {@link #touchableInsets}: the entire window frame
+ * can be touched.
+ */
+ public static final int TOUCHABLE_INSETS_FRAME
+ = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+
+ /**
+ * Option for {@link #touchableInsets}: the area inside of
+ * the content insets can be touched.
+ */
+ public static final int TOUCHABLE_INSETS_CONTENT
+ = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
+
+ /**
+ * Option for {@link #touchableInsets}: the area inside of
+ * the visible insets can be touched.
+ */
+ public static final int TOUCHABLE_INSETS_VISIBLE
+ = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
+
+ /**
+ * Determine which area of the window is touchable by the user. May
+ * be one of: {@link #TOUCHABLE_INSETS_FRAME},
+ * {@link #TOUCHABLE_INSETS_CONTENT}, or {@link #TOUCHABLE_INSETS_VISIBLE}.
+ */
+ public int touchableInsets;
+ }
+
+ @Override public void onCreate() {
+ super.onCreate();
+ mInflater = (LayoutInflater)getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ mWindow = new SoftInputWindow(this);
+ initViews();
+ }
+
+ void initViews() {
+ mWindowVisible = false;
+ mWindowCreated = false;
+ mShowInputRequested = false;
+ mShowCandidatesRequested = false;
+
+ mRootView = mInflater.inflate(
+ com.android.internal.R.layout.input_method, null);
+ mWindow.setContentView(mRootView);
+ mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer);
+
+ mExtractFrame = (FrameLayout)mRootView.findViewById(android.R.id.extractArea);
+ mExtractView = null;
+ mExtractEditText = null;
+ mFullscreenApplied = false;
+
+ mCandidatesFrame = (FrameLayout)mRootView.findViewById(android.R.id.candidatesArea);
+ mInputFrame = (FrameLayout)mRootView.findViewById(android.R.id.inputArea);
+ mInputView = null;
+ mIsInputViewShown = false;
+
+ mExtractFrame.setVisibility(View.GONE);
+ mCandidatesFrame.setVisibility(View.GONE);
+ mInputFrame.setVisibility(View.GONE);
+ }
+
+ @Override public void onDestroy() {
+ super.onDestroy();
+ mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
+ mInsetsComputer);
+ if (mWindowAdded) {
+ mWindow.dismiss();
+ }
+ }
+
+ /**
+ * Implement to return our standard {@link InputMethodImpl}. Subclasses
+ * can override to provide their own customized version.
+ */
+ public AbstractInputMethodImpl onCreateInputMethodInterface() {
+ return new InputMethodImpl();
+ }
+
+ /**
+ * Implement to return our standard {@link InputMethodSessionImpl}. Subclasses
+ * can override to provide their own customized version.
+ */
+ public AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface() {
+ return new InputMethodSessionImpl();
+ }
+
+ public LayoutInflater getLayoutInflater() {
+ return mInflater;
+ }
+
+ public Dialog getWindow() {
+ return mWindow;
+ }
+
+ /**
+ * Return the currently active InputBinding for the input method, or
+ * null if there is none.
+ */
+ public InputBinding getCurrentInputBinding() {
+ return mInputBinding;
+ }
+
+ /**
+ * Retrieve the currently active InputConnection that is bound to
+ * the input method, or null if there is none.
+ */
+ public InputConnection getCurrentInputConnection() {
+ return mInputConnection;
+ }
+
+ public boolean getCurrentInputStarted() {
+ return mInputStarted;
+ }
+
+ public EditorInfo getCurrentInputInfo() {
+ return mInputInfo;
+ }
+
+ /**
+ * Re-evaluate whether the input method should be running in fullscreen
+ * mode, and update its UI if this has changed since the last time it
+ * was evaluated. This will call {@link #onEvaluateFullscreenMode()} to
+ * determine whether it should currently run in fullscreen mode. You
+ * can use {@link #isFullscreenMode()} to determine if the input method
+ * is currently running in fullscreen mode.
+ */
+ public void updateFullscreenMode() {
+ boolean isFullscreen = onEvaluateFullscreenMode();
+ if (mIsFullscreen != isFullscreen || !mFullscreenApplied) {
+ mIsFullscreen = isFullscreen;
+ mFullscreenApplied = true;
+ mWindow.getWindow().setBackgroundDrawable(
+ onCreateBackgroundDrawable());
+ mExtractFrame.setVisibility(isFullscreen ? View.VISIBLE : View.GONE);
+ if (isFullscreen) {
+ if (mExtractView == null) {
+ View v = onCreateExtractTextView();
+ if (v != null) {
+ setExtractView(v);
+ }
+ }
+ startExtractingText();
+ mWindow.getWindow().setLayout(FILL_PARENT, FILL_PARENT);
+ } else {
+ mWindow.getWindow().setLayout(WRAP_CONTENT, WRAP_CONTENT);
+ }
+ }
+ }
+
+ /**
+ * Return whether the input method is <em>currently</em> running in
+ * fullscreen mode. This is the mode that was last determined and
+ * applied by {@link #updateFullscreenMode()}.
+ */
+ public boolean isFullscreenMode() {
+ return mIsFullscreen;
+ }
+
+ /**
+ * Override this to control when the input method should run in
+ * fullscreen mode. The default implementation runs in fullsceen only
+ * when the screen is in landscape mode and the input view is being
+ * shown ({@link #onEvaluateInputViewShown} returns true). If you change what
+ * this returns, you will need to call {@link #updateFullscreenMode()}
+ * yourself whenever the returned value may have changed to have it
+ * re-evaluated and applied.
+ */
+ public boolean onEvaluateFullscreenMode() {
+ Configuration config = getResources().getConfiguration();
+ return config.orientation == Configuration.ORIENTATION_LANDSCAPE
+ && onEvaluateInputViewShown();
+ }
+
+ /**
+ * Compute the interesting insets into your UI. The default implementation
+ * uses the top of the candidates frame for the visible insets, and the
+ * top of the input frame for the content insets. The default touchable
+ * insets are {@link Insets#TOUCHABLE_INSETS_VISIBLE}.
+ *
+ * <p>Note that this method is not called when in fullscreen mode, since
+ * in that case the application is left as-is behind the input method and
+ * not impacted by anything in its UI.
+ *
+ * @param outInsets Fill in with the current UI insets.
+ */
+ public void onComputeInsets(Insets outInsets) {
+ int[] loc = mTmpLocation;
+ if (mInputFrame.getVisibility() == View.VISIBLE) {
+ mInputFrame.getLocationInWindow(loc);
+ outInsets.contentTopInsets = loc[1];
+ }
+ if (mCandidatesFrame.getVisibility() == View.VISIBLE) {
+ mCandidatesFrame.getLocationInWindow(loc);
+ outInsets.visibleTopInsets = loc[1];
+ } else {
+ outInsets.visibleTopInsets = loc[1];
+ }
+ outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_VISIBLE;
+ }
+
+ /**
+ * Re-evaluate whether the soft input area should currently be shown, and
+ * update its UI if this has changed since the last time it
+ * was evaluated. This will call {@link #onEvaluateInputViewShown()} to
+ * determine whether the input view should currently be shown. You
+ * can use {@link #isInputViewShown()} to determine if the input view
+ * is currently shown.
+ */
+ public void updateInputViewShown() {
+ boolean isShown = onEvaluateInputViewShown();
+ if (mIsInputViewShown != isShown && mWindowVisible) {
+ mIsInputViewShown = isShown;
+ mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE);
+ if (mInputView == null) {
+ View v = onCreateInputView();
+ if (v != null) {
+ setInputView(v);
+ }
+ }
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Override this to control when the soft input area should be shown to
+ * the user. The default implementation only shows the input view when
+ * there is no hard keyboard or the keyboard is hidden. If you change what
+ * this returns, you will need to call {@link #updateInputViewShown()}
+ * yourself whenever the returned value may have changed to have it
+ * re-evalauted and applied.
+ */
+ public boolean onEvaluateInputViewShown() {
+ Configuration config = getResources().getConfiguration();
+ return config.keyboard == Configuration.KEYBOARD_NOKEYS
+ || config.hardKeyboardHidden == Configuration.KEYBOARDHIDDEN_YES;
+ }
+
+ /**
+ * Controls the visibility of the candidates display area. By default
+ * it is hidden.
+ */
+ public void setCandidatesViewShown(boolean shown) {
+ if (mShowCandidatesRequested != shown) {
+ mCandidatesFrame.setVisibility(shown ? View.VISIBLE : View.INVISIBLE);
+ if (!mShowInputRequested) {
+ // If we are being asked to show the candidates view while the app
+ // has not asked for the input view to be shown, then we need
+ // to update whether the window is shown.
+ if (shown) {
+ showWindow(false);
+ } else {
+ hideWindow();
+ }
+ }
+ mShowCandidatesRequested = shown;
+ }
+ }
+
+ public void setStatusIcon(int iconResId) {
+ mStatusIcon = iconResId;
+ if (mInputConnection != null && mWindowVisible) {
+ mInputConnection.showStatusIcon(getPackageName(), iconResId);
+ }
+ }
+
+ /**
+ * Force switch to a new input method, as identified by <var>id</var>. This
+ * input method will be destroyed, and the requested one started on the
+ * current input field.
+ *
+ * @param id Unique identifier of the new input method ot start.
+ */
+ public void switchInputMethod(String id) {
+ ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE))
+ .setInputMethod(mToken, id);
+ }
+
+ public void setExtractView(View view) {
+ mExtractFrame.removeAllViews();
+ mExtractFrame.addView(view, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ mExtractView = view;
+ if (view != null) {
+ mExtractEditText = (ExtractEditText)view.findViewById(
+ com.android.internal.R.id.inputExtractEditText);
+ startExtractingText();
+ } else {
+ mExtractEditText = null;
+ }
+ }
+
+ /**
+ * Replaces the current candidates view with a new one. You only need to
+ * call this when dynamically changing the view; normally, you should
+ * implement {@link #onCreateCandidatesView()} and create your view when
+ * first needed by the input method.
+ */
+ public void setCandidatesView(View view) {
+ mCandidatesFrame.removeAllViews();
+ mCandidatesFrame.addView(view, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ }
+
+ /**
+ * Replaces the current input view with a new one. You only need to
+ * call this when dynamically changing the view; normally, you should
+ * implement {@link #onCreateInputView()} and create your view when
+ * first needed by the input method.
+ */
+ public void setInputView(View view) {
+ mInputFrame.removeAllViews();
+ mInputFrame.addView(view, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ mInputView = view;
+ }
+
+ /**
+ * Called by the framework to create a Drawable for the background of
+ * the input method window. May return null for no background. The default
+ * implementation returns a non-null standard background only when in
+ * fullscreen mode.
+ */
+ public Drawable onCreateBackgroundDrawable() {
+ if (isFullscreenMode()) {
+ return getResources().getDrawable(
+ com.android.internal.R.drawable.input_method_fullscreen_background);
+ }
+ return null;
+ }
+
+ /**
+ * Called by the framework to create the layout for showing extacted text.
+ * Only called when in fullscreen mode. The returned view hierarchy must
+ * have an {@link ExtractEditText} whose ID is
+ * {@link android.R.id#inputExtractEditText}.
+ */
+ public View onCreateExtractTextView() {
+ return mInflater.inflate(
+ com.android.internal.R.layout.input_method_extract_view, null);
+ }
+
+ /**
+ * Create and return the view hierarchy used to show candidates. This will
+ * be called once, when the candidates are first displayed. You can return
+ * null to have no candidates view; the default implementation returns null.
+ *
+ * <p>To control when the candidates view is displayed, use
+ * {@link #setCandidatesViewShown(boolean)}.
+ * To change the candidates view after the first one is created by this
+ * function, use {@link #setCandidatesView(View)}.
+ */
+ public View onCreateCandidatesView() {
+ return null;
+ }
+
+ /**
+ * Create and return the view hierarchy used for the input area (such as
+ * a soft keyboard). This will be called once, when the input area is
+ * first displayed. You can return null to have no input area; the default
+ * implementation returns null.
+ *
+ * <p>To control when the input view is displayed, implement
+ * {@link #onEvaluateInputViewShown()}.
+ * To change the input view after the first one is created by this
+ * function, use {@link #setInputView(View)}.
+ */
+ public View onCreateInputView() {
+ return null;
+ }
+
+ /**
+ * Called when an input session is starting or restarting.
+ *
+ * @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 onStartInputView(EditorInfo info, boolean restarting) {
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ boolean visible = mWindowVisible;
+ boolean showingInput = mShowInputRequested;
+ boolean showingCandidates = mShowCandidatesRequested;
+ initViews();
+ if (visible) {
+ if (showingCandidates) {
+ setCandidatesViewShown(true);
+ }
+ showWindow(showingInput);
+ }
+ }
+
+ public void showWindow(boolean showInput) {
+ if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput
+ + " mShowInputRequested=" + mShowInputRequested
+ + " mWindowAdded=" + mWindowAdded
+ + " mWindowCreated=" + mWindowCreated
+ + " mWindowVisible=" + mWindowVisible
+ + " mInputStarted=" + mInputStarted);
+ boolean doShowInput = false;
+ boolean wasVisible = mWindowVisible;
+ mWindowVisible = true;
+ if (!mShowInputRequested) {
+ doShowInput = true;
+ mShowInputRequested = true;
+ } else {
+ showInput = true;
+ }
+
+ if (doShowInput) {
+ if (DEBUG) Log.v(TAG, "showWindow: updating UI");
+ updateFullscreenMode();
+ updateInputViewShown();
+ }
+
+ if (!mWindowAdded || !mWindowCreated) {
+ mWindowAdded = true;
+ mWindowCreated = true;
+ 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");
+ onStartInputView(mInputInfo, false);
+ }
+ startExtractingText();
+ }
+
+ if (!wasVisible) {
+ if (DEBUG) Log.v(TAG, "showWindow: showing!");
+ mWindow.show();
+ if (mInputConnection != null) {
+ mInputConnection.showStatusIcon(getPackageName(), mStatusIcon);
+ }
+ }
+ }
+
+ public void hideWindow() {
+ if (mWindowVisible) {
+ mWindow.hide();
+ mWindowVisible = false;
+ if (mInputConnection != null) {
+ mInputConnection.hideStatusIcon();
+ }
+ }
+ }
+
+ public void onBindInput() {
+ }
+
+ public void onStartInput(EditorInfo attribute, boolean restarting) {
+ }
+
+ void doStartInput(EditorInfo attribute, boolean restarting) {
+ mInputStarted = true;
+ mInputInfo = attribute;
+ onStartInput(attribute, restarting);
+ if (mWindowVisible) {
+ if (mWindowCreated) {
+ onStartInputView(mInputInfo, restarting);
+ }
+ startExtractingText();
+ }
+ }
+
+ public void onFinishInput() {
+ }
+
+ /**
+ * Called when the application has reported auto-completion candidates that
+ * it would like to have the input method displayed. Typically these are
+ * only used when an input method is running in full-screen mode, since
+ * otherwise the user can see and interact with the pop-up window of
+ * completions shown by the application.
+ *
+ * <p>The default implementation here does nothing.
+ */
+ public void onDisplayCompletions(CompletionInfo[] completions) {
+ }
+
+ /**
+ * Called when the application has reported new extracted text to be shown
+ * due to changes in its current text state. The default implementation
+ * here places the new text in the extract edit text, when the input
+ * method is running in fullscreen mode.
+ */
+ public void onUpdateExtractedText(int token, ExtractedText text) {
+ if (mExtractedToken != token) {
+ return;
+ }
+ if (mExtractEditText != null && text != null) {
+ mExtractedText = text;
+ mExtractEditText.setExtractedText(text);
+ }
+ }
+
+ /**
+ * Called when the application has reported a new selection region of
+ * the text. This is called whether or not the input method has requested
+ * extracted text updates, although if so it will not receive this call
+ * if the extracted text has changed as well.
+ *
+ * <p>The default implementation takes care of updating the cursor in
+ * the extract text, if it is being shown.
+ */
+ public void onUpdateSelection(int oldSelStart, int oldSelEnd,
+ int newSelStart, int newSelEnd) {
+ if (mExtractEditText != null && mExtractedText != null) {
+ final int off = mExtractedText.startOffset;
+ mExtractEditText.setSelection(newSelStart-off, newSelEnd-off);
+ }
+ }
+
+ /**
+ * Called when the application has reported a new location of its text
+ * cursor. This is only called if explicitly requested by the input method.
+ * The default implementation does nothing.
+ */
+ public void onUpdateCursor(Rect newCursor) {
+ }
+
+ /**
+ * 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.
+ */
+ public void dismissSoftInput() {
+ ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE))
+ .hideSoftInputFromInputMethod(mToken);
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (mWindowVisible && event.getKeyCode() == KeyEvent.KEYCODE_BACK
+ && event.getRepeatCount() == 0) {
+ dismissSoftInput();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
+ return false;
+ }
+
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ public boolean onTrackballEvent(MotionEvent event) {
+ return false;
+ }
+
+ public void onAppPrivateCommand(String action, Bundle data) {
+ }
+
+ void startExtractingText() {
+ if (mExtractEditText != null && getCurrentInputStarted()
+ && isFullscreenMode()) {
+ mExtractedToken++;
+ ExtractedTextRequest req = new ExtractedTextRequest();
+ req.token = mExtractedToken;
+ req.hintMaxLines = 10;
+ req.hintMaxChars = 10000;
+ mExtractedText = mInputConnection.getExtractedText(req,
+ InputConnection.EXTRACTED_TEXT_MONITOR);
+ if (mExtractedText != null) {
+ mExtractEditText.setExtractedText(mExtractedText);
+ }
+ mExtractEditText.setInputType(getCurrentInputInfo().inputType);
+ mExtractEditText.setHint(mInputInfo.hintText);
+ }
+ }
+}
diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java
new file mode 100755
index 0000000..75a2911
--- /dev/null
+++ b/core/java/android/inputmethodservice/Keyboard.java
@@ -0,0 +1,756 @@
+/*
+ * Copyright (C) 2008-2009 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 android.inputmethodservice;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.Display;
+import android.view.WindowManager;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+
+/**
+ * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
+ * consists of rows of keys.
+ * <p>The layout file for a keyboard contains XML that looks like the following snippet:</p>
+ * <pre>
+ * &lt;Keyboard
+ * android:keyWidth="%10p"
+ * android:keyHeight="50px"
+ * android:horizontalGap="2px"
+ * android:verticalGap="2px" &gt;
+ * &lt;Row android:keyWidth="32px" &gt;
+ * &lt;Key android:keyLabel="A" /&gt;
+ * ...
+ * &lt;/Row&gt;
+ * ...
+ * &lt;/Keyboard&gt;
+ * </pre>
+ * @attr ref android.R.styleable#Keyboard_keyWidth
+ * @attr ref android.R.styleable#Keyboard_keyHeight
+ * @attr ref android.R.styleable#Keyboard_horizontalGap
+ * @attr ref android.R.styleable#Keyboard_verticalGap
+ */
+public class Keyboard {
+
+ static final String TAG = "Keyboard";
+
+ // Keyboard XML Tags
+ private static final String TAG_KEYBOARD = "Keyboard";
+ private static final String TAG_ROW = "Row";
+ private static final String TAG_KEY = "Key";
+
+ public static final int EDGE_LEFT = 0x01;
+ public static final int EDGE_RIGHT = 0x02;
+ public static final int EDGE_TOP = 0x04;
+ public static final int EDGE_BOTTOM = 0x08;
+
+ public static final int KEYCODE_SHIFT = -1;
+ public static final int KEYCODE_MODE_CHANGE = -2;
+ public static final int KEYCODE_CANCEL = -3;
+ public static final int KEYCODE_DONE = -4;
+ public static final int KEYCODE_DELETE = -5;
+ public static final int KEYCODE_ALT = -6;
+
+ /** Keyboard label **/
+ private CharSequence mLabel;
+
+ /** Horizontal gap default for all rows */
+ private int mDefaultHorizontalGap;
+
+ /** Default key width */
+ private int mDefaultWidth;
+
+ /** Default key height */
+ private int mDefaultHeight;
+
+ /** Default gap between rows */
+ private int mDefaultVerticalGap;
+
+ /** Is the keyboard in the shifted state */
+ private boolean mShifted;
+
+ /** Key instance for the shift key, if present */
+ private Key mShiftKey;
+
+ /** Key index for the shift key, if present */
+ private int mShiftKeyIndex = -1;
+
+ /** Current key width, while loading the keyboard */
+ private int mKeyWidth;
+
+ /** Current key height, while loading the keyboard */
+ private int mKeyHeight;
+
+ /** Total height of the keyboard, including the padding and keys */
+ private int mTotalHeight;
+
+ /**
+ * Total width of the keyboard, including left side gaps and keys, but not any gaps on the
+ * right side.
+ */
+ private int mTotalWidth;
+
+ /** List of keys in this keyboard */
+ private List<Key> mKeys;
+
+ /** List of modifier keys such as Shift & Alt, if any */
+ private List<Key> mModifierKeys;
+
+ /** Width of the screen available to fit the keyboard */
+ private int mDisplayWidth;
+
+ /** Height of the screen */
+ private int mDisplayHeight;
+
+ /** Keyboard mode, or zero, if none. */
+ private int mKeyboardMode;
+
+ /**
+ * 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}
+ * defines.
+ * @attr ref android.R.styleable#Keyboard_keyWidth
+ * @attr ref android.R.styleable#Keyboard_keyHeight
+ * @attr ref android.R.styleable#Keyboard_horizontalGap
+ * @attr ref android.R.styleable#Keyboard_verticalGap
+ * @attr ref android.R.styleable#Keyboard_Row_rowEdgeFlags
+ * @attr ref android.R.styleable#Keyboard_Row_keyboardMode
+ */
+ public static class Row {
+ /** Default width of a key in this row. */
+ public int defaultWidth;
+ /** Default height of a key in this row. */
+ public int defaultHeight;
+ /** Default horizontal gap between keys in this row. */
+ public int defaultHorizontalGap;
+ /** Vertical gap following this row. */
+ public int verticalGap;
+ /**
+ * Edge flags for this row of keys. Possible values that can be assigned are
+ * {@link Keyboard#EDGE_TOP EDGE_TOP} and {@link Keyboard#EDGE_BOTTOM EDGE_BOTTOM}
+ */
+ public int rowEdgeFlags;
+
+ /** The keyboard mode for this row */
+ public int mode;
+
+ private Keyboard parent;
+
+ public Row(Keyboard parent) {
+ this.parent = parent;
+ }
+
+ public Row(Resources res, Keyboard parent, XmlResourceParser parser) {
+ this.parent = parent;
+ TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
+ com.android.internal.R.styleable.Keyboard);
+ defaultWidth = getDimensionOrFraction(a,
+ com.android.internal.R.styleable.Keyboard_keyWidth,
+ parent.mDisplayWidth, parent.mDefaultWidth);
+ defaultHeight = getDimensionOrFraction(a,
+ com.android.internal.R.styleable.Keyboard_keyHeight,
+ parent.mDisplayWidth, 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);
+ a.recycle();
+ a = res.obtainAttributes(Xml.asAttributeSet(parser),
+ com.android.internal.R.styleable.Keyboard_Row);
+ rowEdgeFlags = a.getInt(com.android.internal.R.styleable.Keyboard_Row_rowEdgeFlags, 0);
+ mode = a.getResourceId(com.android.internal.R.styleable.Keyboard_Row_keyboardMode,
+ 0);
+ }
+ }
+
+ /**
+ * Class for describing the position and characteristics of a single key in the keyboard.
+ *
+ * @attr ref android.R.styleable#Keyboard_keyWidth
+ * @attr ref android.R.styleable#Keyboard_keyHeight
+ * @attr ref android.R.styleable#Keyboard_horizontalGap
+ * @attr ref android.R.styleable#Keyboard_Key_codes
+ * @attr ref android.R.styleable#Keyboard_Key_keyIcon
+ * @attr ref android.R.styleable#Keyboard_Key_keyLabel
+ * @attr ref android.R.styleable#Keyboard_Key_iconPreview
+ * @attr ref android.R.styleable#Keyboard_Key_isSticky
+ * @attr ref android.R.styleable#Keyboard_Key_isRepeatable
+ * @attr ref android.R.styleable#Keyboard_Key_isModifier
+ * @attr ref android.R.styleable#Keyboard_Key_popupKeyboard
+ * @attr ref android.R.styleable#Keyboard_Key_popupCharacters
+ * @attr ref android.R.styleable#Keyboard_Key_keyOutputText
+ * @attr ref android.R.styleable#Keyboard_Key_keyEdgeFlags
+ */
+ public static class Key {
+ /**
+ * All the key codes (unicode or custom code) that this key could generate, zero'th
+ * being the most important.
+ */
+ public int[] codes;
+
+ /** Label to display */
+ public CharSequence label;
+
+ /** Icon to display instead of a label. Icon takes precedence over a label */
+ public Drawable icon;
+ /** Preview version of the icon, for the preview popup */
+ public Drawable iconPreview;
+ /** Width of the key, not including the gap */
+ public int width;
+ /** Height of the key, not including the gap */
+ public int height;
+ /** The horizontal gap before this key */
+ public int gap;
+ /** Whether this key is sticky, i.e., a toggle key */
+ public boolean sticky;
+ /** X coordinate of the key in the keyboard layout */
+ public int x;
+ /** Y coordinate of the key in the keyboard layout */
+ public int y;
+ /** The current pressed state of this key */
+ public boolean pressed;
+ /** If this is a sticky key, is it on? */
+ public boolean on;
+ /** Text to output when pressed. This can be multiple characters, like ".com" */
+ public CharSequence text;
+ /** Popup characters */
+ public CharSequence popupCharacters;
+
+ /**
+ * Flags that specify the anchoring to edges of the keyboard for detecting touch events
+ * that are just out of the boundary of the key. This is a bit mask of
+ * {@link Keyboard#EDGE_LEFT}, {@link Keyboard#EDGE_RIGHT}, {@link Keyboard#EDGE_TOP} and
+ * {@link Keyboard#EDGE_BOTTOM}.
+ */
+ public int edgeFlags;
+ /** Whether this is a modifier key, such as Shift or Alt */
+ public boolean modifier;
+ /** The keyboard that this key belongs to */
+ private Keyboard keyboard;
+ /**
+ * If this key pops up a mini keyboard, this is the resource id for the XML layout for that
+ * keyboard.
+ */
+ public int popupResId;
+ /** Whether this key repeats itself when held down */
+ public boolean repeatable;
+
+
+ private final static int[] KEY_STATE_NORMAL_ON = {
+ android.R.attr.state_checkable,
+ android.R.attr.state_checked
+ };
+
+ private final static int[] KEY_STATE_PRESSED_ON = {
+ android.R.attr.state_pressed,
+ android.R.attr.state_checkable,
+ android.R.attr.state_checked
+ };
+
+ private final static int[] KEY_STATE_NORMAL_OFF = {
+ android.R.attr.state_checkable
+ };
+
+ private final static int[] KEY_STATE_PRESSED_OFF = {
+ android.R.attr.state_pressed,
+ android.R.attr.state_checkable
+ };
+
+ private final static int[] KEY_STATE_NORMAL = {
+ };
+
+ private final static int[] KEY_STATE_PRESSED = {
+ android.R.attr.state_pressed
+ };
+
+ /** Create an empty key with no attributes. */
+ public Key(Row parent) {
+ keyboard = parent.parent;
+ }
+
+ /** Create a key with the given top-left coordinate and extract its attributes from
+ * the XML parser.
+ * @param res resources associated with the caller's context
+ * @param parent the row that this key belongs to. The row must already be attached to
+ * a {@link Keyboard}.
+ * @param x the x coordinate of the top-left
+ * @param y the y coordinate of the top-left
+ * @param parser the XML parser containing the attributes for this key
+ */
+ public Key(Resources res, Row parent, int x, int y, XmlResourceParser parser) {
+ this(parent);
+
+ this.x = x;
+ this.y = y;
+
+ TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
+ com.android.internal.R.styleable.Keyboard);
+
+ width = getDimensionOrFraction(a,
+ com.android.internal.R.styleable.Keyboard_keyWidth,
+ keyboard.mDisplayWidth, parent.defaultWidth);
+ height = getDimensionOrFraction(a,
+ com.android.internal.R.styleable.Keyboard_keyHeight,
+ keyboard.mDisplayHeight, parent.defaultHeight);
+ gap = getDimensionOrFraction(a,
+ com.android.internal.R.styleable.Keyboard_horizontalGap,
+ keyboard.mDisplayWidth, parent.defaultHorizontalGap);
+ a.recycle();
+ a = res.obtainAttributes(Xml.asAttributeSet(parser),
+ com.android.internal.R.styleable.Keyboard_Key);
+ this.x += gap;
+ TypedValue codesValue = new TypedValue();
+ a.getValue(com.android.internal.R.styleable.Keyboard_Key_codes,
+ codesValue);
+ if (codesValue.type == TypedValue.TYPE_INT_DEC
+ || codesValue.type == TypedValue.TYPE_INT_HEX) {
+ codes = new int[] { codesValue.data };
+ } else if (codesValue.type == TypedValue.TYPE_STRING) {
+ codes = parseCSV(codesValue.string.toString());
+ }
+
+ iconPreview = a.getDrawable(com.android.internal.R.styleable.Keyboard_Key_iconPreview);
+ if (iconPreview != null) {
+ iconPreview.setBounds(0, 0, iconPreview.getIntrinsicWidth(),
+ iconPreview.getIntrinsicHeight());
+ }
+ popupCharacters = a.getText(
+ com.android.internal.R.styleable.Keyboard_Key_popupCharacters);
+ popupResId = a.getResourceId(
+ com.android.internal.R.styleable.Keyboard_Key_popupKeyboard, 0);
+ repeatable = a.getBoolean(
+ com.android.internal.R.styleable.Keyboard_Key_isRepeatable, false);
+ modifier = a.getBoolean(
+ com.android.internal.R.styleable.Keyboard_Key_isModifier, false);
+ sticky = a.getBoolean(
+ com.android.internal.R.styleable.Keyboard_Key_isSticky, false);
+ edgeFlags = a.getInt(com.android.internal.R.styleable.Keyboard_Key_keyEdgeFlags, 0);
+ edgeFlags |= parent.rowEdgeFlags;
+
+ icon = a.getDrawable(
+ com.android.internal.R.styleable.Keyboard_Key_keyIcon);
+ if (icon != null) {
+ icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+ }
+ label = a.getText(com.android.internal.R.styleable.Keyboard_Key_keyLabel);
+ text = a.getText(com.android.internal.R.styleable.Keyboard_Key_keyOutputText);
+
+ if (codes == null && !TextUtils.isEmpty(label)) {
+ codes = new int[] { label.charAt(0) };
+ }
+ a.recycle();
+ }
+
+ /**
+ * Informs the key that it has been pressed, in case it needs to change its appearance or
+ * state.
+ * @see #onReleased(boolean)
+ */
+ public void onPressed() {
+ pressed = !pressed;
+ }
+
+ /**
+ * Changes the pressed state of the key. If it is a sticky key, it will also change the
+ * toggled state of the key if the finger was release inside.
+ * @param inside whether the finger was released inside the key
+ * @see #onPressed()
+ */
+ public void onReleased(boolean inside) {
+ pressed = !pressed;
+ if (sticky) {
+ on = !on;
+ }
+ }
+
+ int[] parseCSV(String value) {
+ int count = 0;
+ int lastIndex = 0;
+ if (value.length() > 0) {
+ count++;
+ while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
+ count++;
+ }
+ }
+ int[] values = new int[count];
+ count = 0;
+ StringTokenizer st = new StringTokenizer(value, ",");
+ while (st.hasMoreTokens()) {
+ try {
+ values[count++] = Integer.parseInt(st.nextToken());
+ } catch (NumberFormatException nfe) {
+ Log.e(TAG, "Error parsing keycodes " + value);
+ }
+ }
+ return values;
+ }
+
+ /**
+ * Detects if a point falls inside this key.
+ * @param x the x-coordinate of the point
+ * @param y the y-coordinate of the point
+ * @return whether or not the point falls inside the key. If the key is attached to an edge,
+ * it will assume that all points between the key and the edge are considered to be inside
+ * the key.
+ */
+ public boolean isInside(int x, int y) {
+ boolean leftEdge = (edgeFlags & EDGE_LEFT) > 0;
+ boolean rightEdge = (edgeFlags & EDGE_RIGHT) > 0;
+ boolean topEdge = (edgeFlags & EDGE_TOP) > 0;
+ boolean bottomEdge = (edgeFlags & EDGE_BOTTOM) > 0;
+ if ((x >= this.x || (leftEdge && x <= this.x + this.width))
+ && (x < this.x + this.width || (rightEdge && x >= this.x))
+ && (y >= this.y || (topEdge && y <= this.y + this.height))
+ && (y < this.y + this.height || (bottomEdge && y >= this.y))) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Returns the square of the distance between the center of the key and the given point.
+ * @param x the x-coordinate of the point
+ * @param y the y-coordinate of the point
+ * @return the square of the distance of the point from the center of the key
+ */
+ public int squaredDistanceFrom(int x, int y) {
+ float xDist = Math.abs((this.x + this.x + width) / 2f - x);
+ float yDist = Math.abs((this.y + this.y + height) / 2f - y);
+ return (int) (xDist * xDist + yDist * yDist);
+ }
+
+ /**
+ * Returns the drawable state for the key, based on the current state and type of the key.
+ * @return the drawable state of the key.
+ * @see android.graphics.drawable.StateListDrawable#setState(int[])
+ */
+ public int[] getCurrentDrawableState() {
+ int[] states = KEY_STATE_NORMAL;
+
+ if (on) {
+ if (pressed) {
+ states = KEY_STATE_PRESSED_ON;
+ } else {
+ states = KEY_STATE_NORMAL_ON;
+ }
+ } else {
+ if (sticky) {
+ if (pressed) {
+ states = KEY_STATE_PRESSED_OFF;
+ } else {
+ states = KEY_STATE_NORMAL_OFF;
+ }
+ } else {
+ if (pressed) {
+ states = KEY_STATE_PRESSED;
+ }
+ }
+ }
+ return states;
+ }
+ }
+
+ /**
+ * Creates a keyboard from the given xml key layout file.
+ * @param context the application or service context
+ * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
+ */
+ public Keyboard(Context context, int xmlLayoutResId) {
+ this(context, xmlLayoutResId, 0);
+ }
+
+ /**
+ * Creates a keyboard from the given xml key layout file. Weeds out rows
+ * that have a keyboard mode defined but don't match the specified mode.
+ * @param context the application or service context
+ * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
+ * @param modeId keyboard mode identifier
+ */
+ public Keyboard(Context context, int xmlLayoutResId, int modeId) {
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ final Display display = wm.getDefaultDisplay();
+ mDisplayWidth = display.getWidth();
+ mDisplayHeight = display.getHeight();
+ mDefaultHorizontalGap = 0;
+ mDefaultWidth = mDisplayWidth / 10;
+ mDefaultVerticalGap = 0;
+ mDefaultHeight = mDefaultWidth;
+ mKeys = new ArrayList<Key>();
+ mModifierKeys = new ArrayList<Key>();
+ mKeyboardMode = modeId;
+ loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
+ }
+
+ /**
+ * <p>Creates a blank keyboard from the given resource file and populates it with the specified
+ * characters in left-to-right, top-to-bottom fashion, using the specified number of columns.
+ * </p>
+ * <p>If the specified number of columns is -1, then the keyboard will fit as many keys as
+ * possible in each row.</p>
+ * @param context the application or service context
+ * @param layoutTemplateResId the layout template file, containing no keys.
+ * @param characters the list of characters to display on the keyboard. One key will be created
+ * for each character.
+ * @param columns the number of columns of keys to display. If this number is greater than the
+ * number of keys that can fit in a row, it will be ignored. If this number is -1, the
+ * keyboard will fit as many keys as possible in each row.
+ */
+ public Keyboard(Context context, int layoutTemplateResId,
+ CharSequence characters, int columns, int horizontalPadding) {
+ this(context, layoutTemplateResId);
+ int x = 0;
+ int y = 0;
+ int column = 0;
+ mTotalWidth = 0;
+
+ Row row = new Row(this);
+ row.defaultHeight = mDefaultHeight;
+ row.defaultWidth = mDefaultWidth;
+ 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);
+ if (column >= maxColumns
+ || x + mDefaultWidth + horizontalPadding > mDisplayWidth) {
+ x = 0;
+ y += mDefaultVerticalGap + mDefaultHeight;
+ column = 0;
+ }
+ final Key key = new Key(row);
+ key.x = x;
+ key.y = y;
+ key.width = mDefaultWidth;
+ key.height = mDefaultHeight;
+ key.gap = mDefaultHorizontalGap;
+ key.label = String.valueOf(c);
+ key.codes = new int[] { c };
+ column++;
+ x += key.width + key.gap;
+ mKeys.add(key);
+ if (x > mTotalWidth) {
+ mTotalWidth = x;
+ }
+ }
+ mTotalHeight = y + mDefaultHeight;
+ }
+
+ public List<Key> getKeys() {
+ return mKeys;
+ }
+
+ public List<Key> getModifierKeys() {
+ return mModifierKeys;
+ }
+
+ protected int getHorizontalGap() {
+ return mDefaultHorizontalGap;
+ }
+
+ protected void setHorizontalGap(int gap) {
+ mDefaultHorizontalGap = gap;
+ }
+
+ protected int getVerticalGap() {
+ return mDefaultVerticalGap;
+ }
+
+ protected void setVerticalGap(int gap) {
+ mDefaultVerticalGap = gap;
+ }
+
+ protected int getKeyHeight() {
+ return mDefaultHeight;
+ }
+
+ protected void setKeyHeight(int height) {
+ mDefaultHeight = height;
+ }
+
+ protected int getKeyWidth() {
+ return mDefaultWidth;
+ }
+
+ protected void setKeyWidth(int width) {
+ mDefaultWidth = width;
+ }
+
+ /**
+ * Returns the total height of the keyboard
+ * @return the total height of the keyboard
+ */
+ public int getHeight() {
+ return mTotalHeight;
+ }
+
+ public int getMinWidth() {
+ return mTotalWidth;
+ }
+
+ public boolean setShifted(boolean shiftState) {
+ if (mShiftKey != null) {
+ mShiftKey.on = shiftState;
+ }
+ if (mShifted != shiftState) {
+ mShifted = shiftState;
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isShifted() {
+ return mShifted;
+ }
+
+ public int getShiftKeyIndex() {
+ return mShiftKeyIndex;
+ }
+
+ protected Row createRowFromXml(Resources res, XmlResourceParser parser) {
+ return new Row(res, this, parser);
+ }
+
+ protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
+ XmlResourceParser parser) {
+ return new Key(res, parent, x, y, parser);
+ }
+
+ private void loadKeyboard(Context context, XmlResourceParser parser) {
+ boolean inKey = false;
+ boolean inRow = false;
+ boolean leftMostKey = false;
+ int row = 0;
+ int x = 0;
+ int y = 0;
+ Key key = null;
+ Row currentRow = null;
+ Resources res = context.getResources();
+ boolean skipRow = false;
+
+ try {
+ int event;
+ while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
+ if (event == XmlResourceParser.START_TAG) {
+ String tag = parser.getName();
+ if (TAG_ROW.equals(tag)) {
+ inRow = true;
+ x = 0;
+ currentRow = createRowFromXml(res, parser);
+ skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode;
+ if (skipRow) {
+ skipToEndOfRow(parser);
+ inRow = false;
+ }
+ } else if (TAG_KEY.equals(tag)) {
+ inKey = true;
+ key = createKeyFromXml(res, currentRow, x, y, parser);
+ mKeys.add(key);
+ if (key.codes[0] == KEYCODE_SHIFT) {
+ mShiftKey = key;
+ mShiftKeyIndex = mKeys.size()-1;
+ mModifierKeys.add(key);
+ } else if (key.codes[0] == KEYCODE_ALT) {
+ mModifierKeys.add(key);
+ }
+ } else if (TAG_KEYBOARD.equals(tag)) {
+ parseKeyboardAttributes(res, parser);
+ }
+ } else if (event == XmlResourceParser.END_TAG) {
+ if (inKey) {
+ inKey = false;
+ x += key.gap + key.width;
+ if (x > mTotalWidth) {
+ mTotalWidth = x;
+ }
+ } else if (inRow) {
+ inRow = false;
+ y += currentRow.verticalGap;
+ y += currentRow.defaultHeight;
+ row++;
+ } else {
+ // TODO: error or extend?
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Parse error:" + e);
+ e.printStackTrace();
+ }
+ mTotalHeight = y - mDefaultVerticalGap;
+ }
+
+ private void skipToEndOfRow(XmlResourceParser parser)
+ throws XmlPullParserException, IOException {
+ int event;
+ while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
+ if (event == XmlResourceParser.END_TAG
+ && parser.getName().equals(TAG_ROW)) {
+ break;
+ }
+ }
+ }
+
+ private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) {
+ TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
+ com.android.internal.R.styleable.Keyboard);
+
+ mDefaultWidth = getDimensionOrFraction(a,
+ com.android.internal.R.styleable.Keyboard_keyWidth,
+ mDisplayWidth, mDisplayWidth / 10);
+ mDefaultHeight = getDimensionOrFraction(a,
+ com.android.internal.R.styleable.Keyboard_keyHeight,
+ mDisplayHeight, 50);
+ mDefaultHorizontalGap = getDimensionOrFraction(a,
+ com.android.internal.R.styleable.Keyboard_horizontalGap,
+ mDisplayWidth, 0);
+ mDefaultVerticalGap = getDimensionOrFraction(a,
+ com.android.internal.R.styleable.Keyboard_verticalGap,
+ mDisplayHeight, 0);
+ a.recycle();
+ }
+
+ static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) {
+ TypedValue value = a.peekValue(index);
+ if (value == null) return defValue;
+ if (value.type == TypedValue.TYPE_DIMENSION) {
+ return a.getDimensionPixelOffset(index, defValue);
+ } else if (value.type == TypedValue.TYPE_FRACTION) {
+ return (int) a.getFraction(index, base, base, defValue);
+ }
+ return defValue;
+ }
+}
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
new file mode 100755
index 0000000..56473da
--- /dev/null
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -0,0 +1,1049 @@
+/*
+ * Copyright (C) 2008-2009 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 android.inputmethodservice;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Paint.Align;
+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.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.ViewGroup.LayoutParams;
+import android.widget.Button;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A view that renders a virtual {@link Keyboard}. It handles rendering of keys and
+ * detecting key presses and touch movements.
+ *
+ * @attr ref android.R.styleable#KeyboardView_keyBackground
+ * @attr ref android.R.styleable#KeyboardView_keyPreviewLayout
+ * @attr ref android.R.styleable#KeyboardView_keyPreviewOffset
+ * @attr ref android.R.styleable#KeyboardView_labelTextSize
+ * @attr ref android.R.styleable#KeyboardView_keyTextSize
+ * @attr ref android.R.styleable#KeyboardView_keyTextColor
+ * @attr ref android.R.styleable#KeyboardView_verticalCorrection
+ * @attr ref android.R.styleable#KeyboardView_popupLayout
+ */
+public class KeyboardView extends View implements View.OnClickListener {
+
+ /**
+ * Listener for virtual keyboard events.
+ */
+ public interface OnKeyboardActionListener {
+ /**
+ * Send a key press to the listener.
+ * @param primaryCode this is the key that was pressed
+ * @param keyCodes the codes for all the possible alternative keys
+ * with the primary code being the first. If the primary key code is
+ * a single character such as an alphabet or number or symbol, the alternatives
+ * will include other characters that may be on the same key or adjacent keys.
+ * These codes are useful to correct for accidental presses of a key adjacent to
+ * the intended key.
+ */
+ void onKey(int primaryCode, int[] keyCodes);
+
+ /**
+ * Called when the user quickly moves the finger from right to left.
+ */
+ void swipeLeft();
+
+ /**
+ * Called when the user quickly moves the finger from left to right.
+ */
+ void swipeRight();
+
+ /**
+ * Called when the user quickly moves the finger from up to down.
+ */
+ void swipeDown();
+
+ /**
+ * Called when the user quickly moves the finger from down to up.
+ */
+ void swipeUp();
+ }
+
+ private static final boolean DEBUG = false;
+ private static final int NOT_A_KEY = -1;
+ private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
+ private static final int[] LONG_PRESSABLE_STATE_SET = { R.attr.state_long_pressable };
+
+ private Keyboard mKeyboard;
+ private int mCurrentKeyIndex = NOT_A_KEY;
+ private int mLabelTextSize;
+ private int mKeyTextSize;
+ private int mKeyTextColor;
+
+ private TextView mPreviewText;
+ private PopupWindow mPreviewPopup;
+ private int mPreviewTextSizeLarge;
+ private int mPreviewOffset;
+ private int mPreviewHeight;
+ private int[] mOffsetInWindow;
+
+ private PopupWindow mPopupKeyboard;
+ private View mMiniKeyboardContainer;
+ private KeyboardView mMiniKeyboard;
+ private boolean mMiniKeyboardOnScreen;
+ private View mPopupParent;
+ private int mMiniKeyboardOffsetX;
+ private int mMiniKeyboardOffsetY;
+ private Map<Key,View> mMiniKeyboardCache;
+ private int[] mWindowOffset;
+
+ /** Listener for {@link OnKeyboardActionListener}. */
+ private OnKeyboardActionListener mKeyboardActionListener;
+
+ private static final int MSG_REMOVE_PREVIEW = 1;
+ private static final int MSG_REPEAT = 2;
+
+ private int mVerticalCorrection;
+ private int mProximityThreshold;
+
+ private boolean mPreviewCentered = false;
+ private boolean mShowPreview = true;
+ private boolean mShowTouchPoints = false;
+ private int mPopupPreviewX;
+ private int mPopupPreviewY;
+
+ private int mLastX;
+ private int mLastY;
+ private int mStartX;
+ private int mStartY;
+
+ private boolean mVibrateOn;
+ private boolean mSoundOn;
+ private boolean mProximityCorrectOn;
+
+ private Paint mPaint;
+ private Rect mPadding;
+
+ private long mDownTime;
+ private long mLastMoveTime;
+ private int mLastKey;
+ private int mLastCodeX;
+ private int mLastCodeY;
+ private int mCurrentKey = NOT_A_KEY;
+ private long mLastKeyTime;
+ private long mCurrentKeyTime;
+ private int[] mKeyIndices = new int[12];
+ private GestureDetector mGestureDetector;
+ private int mPopupX;
+ private int mPopupY;
+ private int mRepeatKeyIndex = NOT_A_KEY;
+ private int mPopupLayout;
+ private boolean mAbortKey;
+
+ private Drawable mKeyBackground;
+
+ private static final String PREF_VIBRATE_ON = "vibrate_on";
+ private static final String PREF_SOUND_ON = "sound_on";
+ private static final String PREF_PROXIMITY_CORRECTION = "hit_correction";
+
+ private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
+ private static final int REPEAT_START_DELAY = 400;
+
+ private Vibrator mVibrator;
+ private long[] mVibratePattern = new long[] {1, 20};
+
+ private static int MAX_NEARBY_KEYS = 12;
+ private int[] mDistances = new int[MAX_NEARBY_KEYS];
+
+ // For multi-tap
+ private int mLastSentIndex;
+ private int mTapCount;
+ private long mLastTapTime;
+ private boolean mInMultiTap;
+ private static final int MULTITAP_INTERVAL = 800; // milliseconds
+ private StringBuilder mPreviewLabel = new StringBuilder(1);
+
+ Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_REMOVE_PREVIEW:
+ mPreviewText.setVisibility(INVISIBLE);
+ break;
+ case MSG_REPEAT:
+ if (repeatKey()) {
+ Message repeat = Message.obtain(this, MSG_REPEAT);
+ sendMessageDelayed(repeat, REPEAT_INTERVAL);
+ }
+ break;
+ }
+
+ }
+ };
+
+ public KeyboardView(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.keyboardViewStyle);
+ }
+
+ public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a =
+ context.obtainStyledAttributes(
+ attrs, android.R.styleable.KeyboardView, defStyle, 0);
+
+ LayoutInflater inflate =
+ (LayoutInflater) context
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ int previewLayout = 0;
+ int keyTextSize = 0;
+
+ int n = a.getIndexCount();
+
+ for (int i = 0; i < n; i++) {
+ int attr = a.getIndex(i);
+
+ switch (attr) {
+ case com.android.internal.R.styleable.KeyboardView_keyBackground:
+ mKeyBackground = a.getDrawable(attr);
+ break;
+ case com.android.internal.R.styleable.KeyboardView_verticalCorrection:
+ mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
+ break;
+ case com.android.internal.R.styleable.KeyboardView_keyPreviewLayout:
+ previewLayout = a.getResourceId(attr, 0);
+ break;
+ case com.android.internal.R.styleable.KeyboardView_keyPreviewOffset:
+ mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
+ break;
+ case com.android.internal.R.styleable.KeyboardView_keyPreviewHeight:
+ mPreviewHeight = a.getDimensionPixelSize(attr, 80);
+ break;
+ case com.android.internal.R.styleable.KeyboardView_keyTextSize:
+ mKeyTextSize = a.getDimensionPixelSize(attr, 18);
+ break;
+ case com.android.internal.R.styleable.KeyboardView_keyTextColor:
+ mKeyTextColor = a.getColor(attr, 0xFF000000);
+ break;
+ case com.android.internal.R.styleable.KeyboardView_labelTextSize:
+ mLabelTextSize = a.getDimensionPixelSize(attr, 14);
+ break;
+ case com.android.internal.R.styleable.KeyboardView_popupLayout:
+ mPopupLayout = a.getResourceId(attr, 0);
+ break;
+ }
+ }
+
+ // Get the settings preferences
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ mVibrateOn = sp.getBoolean(PREF_VIBRATE_ON, mVibrateOn);
+ mSoundOn = sp.getBoolean(PREF_SOUND_ON, mSoundOn);
+ mProximityCorrectOn = sp.getBoolean(PREF_PROXIMITY_CORRECTION, true);
+
+ mPreviewPopup = new PopupWindow(context);
+ if (previewLayout != 0) {
+ mPreviewText = (TextView) inflate.inflate(previewLayout, null);
+ mPreviewTextSizeLarge = (int) mPreviewText.getTextSize();
+ mPreviewPopup.setContentView(mPreviewText);
+ mPreviewPopup.setBackgroundDrawable(null);
+ } else {
+ mShowPreview = false;
+ }
+
+ mPreviewPopup.setTouchable(false);
+
+ mPopupKeyboard = new PopupWindow(context);
+ mPopupKeyboard.setBackgroundDrawable(null);
+ //mPopupKeyboard.setClippingEnabled(false);
+
+ mPopupParent = this;
+ //mPredicting = true;
+
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setTextSize(keyTextSize);
+ mPaint.setTextAlign(Align.CENTER);
+
+ mPadding = new Rect(0, 0, 0, 0);
+ mMiniKeyboardCache = new HashMap<Key,View>();
+ mKeyBackground.getPadding(mPadding);
+
+ resetMultiTap();
+ initGestureDetector();
+ }
+
+ private void initGestureDetector() {
+ mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onFling(MotionEvent me1, MotionEvent me2,
+ float velocityX, float velocityY) {
+ if (velocityX > 400 && Math.abs(velocityY) < 400) {
+ swipeRight();
+ return true;
+ } else if (velocityX < -400 && Math.abs(velocityY) < 400) {
+ swipeLeft();
+ return true;
+ } else if (velocityY < -400 && Math.abs(velocityX) < 400) {
+ swipeUp();
+ return true;
+ } else if (velocityY > 400 && Math.abs(velocityX) < 400) {
+ swipeDown();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent me) {
+ openPopupIfRequired(me);
+ }
+ });
+ }
+
+ public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
+ mKeyboardActionListener = listener;
+ }
+
+ /**
+ * Returns the {@link OnKeyboardActionListener} object.
+ * @return the listener attached to this keyboard
+ */
+ protected OnKeyboardActionListener getOnKeyboardActionListener() {
+ return mKeyboardActionListener;
+ }
+
+ /**
+ * Attaches a keyboard to this view. The keyboard can be switched at any time and the
+ * view will re-layout itself to accommodate the keyboard.
+ * @see Keyboard
+ * @see #getKeyboard()
+ * @param keyboard the keyboard to display in this view
+ */
+ public void setKeyboard(Keyboard keyboard) {
+ mKeyboard = keyboard;
+ requestLayout();
+ invalidate();
+ computeProximityThreshold(keyboard);
+ }
+
+ /**
+ * Returns the current keyboard being displayed by this view.
+ * @return the currently attached keyboard
+ * @see #setKeyboard(Keyboard)
+ */
+ public Keyboard getKeyboard() {
+ return mKeyboard;
+ }
+
+ /**
+ * Sets the state of the shift key of the keyboard, if any.
+ * @param shifted whether or not to enable the state of the shift key
+ * @return true if the shift key state changed, false if there was no change
+ * @see KeyboardView#isShifted()
+ */
+ public boolean setShifted(boolean shifted) {
+ if (mKeyboard != null) {
+ if (mKeyboard.setShifted(shifted)) {
+ // The whole keyboard probably needs to be redrawn
+ invalidate();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the state of the shift key of the keyboard, if any.
+ * @return true if the shift is in a pressed state, false otherwise. If there is
+ * no shift key on the keyboard or there is no keyboard attached, it returns false.
+ * @see KeyboardView#setShifted(boolean)
+ */
+ public boolean isShifted() {
+ if (mKeyboard != null) {
+ return mKeyboard.isShifted();
+ }
+ return false;
+ }
+
+ /**
+ * Enables or disables the key feedback popup. This is a popup that shows a magnified
+ * version of the depressed key. By default the preview is enabled.
+ * @param previewEnabled whether or not to enable the key feedback popup
+ * @see #isPreviewEnabled()
+ */
+ public void setPreviewEnabled(boolean previewEnabled) {
+ mShowPreview = previewEnabled;
+ }
+
+ /**
+ * Returns the enabled state of the key feedback popup.
+ * @return whether or not the key feedback popup is enabled
+ * @see #setPreviewEnabled(boolean)
+ */
+ public boolean isPreviewEnabled() {
+ return mShowPreview;
+ }
+
+ public void setVerticalCorrection(int verticalOffset) {
+
+ }
+ public void setPopupParent(View v) {
+ mPopupParent = v;
+ }
+
+ public void setPopupOffset(int x, int y) {
+ mMiniKeyboardOffsetX = x;
+ mMiniKeyboardOffsetY = y;
+ if (mPreviewPopup.isShowing()) {
+ mPreviewPopup.dismiss();
+ }
+ }
+
+ /**
+ * Popup keyboard close button clicked.
+ * @hide
+ */
+ public void onClick(View v) {
+ dismissPopupKeyboard();
+ }
+
+ private CharSequence adjustCase(CharSequence label) {
+ if (mKeyboard.isShifted() && label != null && label.length() == 1
+ && Character.isLowerCase(label.charAt(0))) {
+ label = label.toString().toUpperCase();
+ }
+ return label;
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Round up a little
+ if (mKeyboard == null) {
+ setMeasuredDimension(mPaddingLeft + mPaddingRight, mPaddingTop + mPaddingBottom);
+ } else {
+ int width = mKeyboard.getMinWidth() + mPaddingLeft + mPaddingRight;
+ if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
+ width = MeasureSpec.getSize(widthMeasureSpec);
+ }
+ setMeasuredDimension(width, mKeyboard.getHeight() + mPaddingTop + mPaddingBottom);
+ }
+ }
+
+ /**
+ * Compute the average distance between adjacent keys (horizontally and vertically)
+ * and square it to get the proximity threshold. We use a square here and in computing
+ * the touch distance from a key's center to avoid taking a square root.
+ * @param keyboard
+ */
+ private void computeProximityThreshold(Keyboard keyboard) {
+ if (keyboard == null) return;
+ List<Key> keys = keyboard.getKeys();
+ if (keys == null) return;
+ int length = keys.size();
+ int dimensionSum = 0;
+ for (int i = 0; i < length; i++) {
+ Key key = keys.get(i);
+ dimensionSum += key.width + key.gap + key.height;
+ }
+ if (dimensionSum < 0 || length == 0) return;
+ mProximityThreshold = dimensionSum / (length * 2);
+ mProximityThreshold *= mProximityThreshold; // Square it
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mKeyboard == null) return;
+
+ final Paint paint = mPaint;
+ //final int descent = (int) paint.descent();
+ final Drawable keyBackground = mKeyBackground;
+ final Rect padding = mPadding;
+ final int kbdPaddingLeft = mPaddingLeft;
+ final int kbdPaddingTop = mPaddingTop;
+ List<Key> keys = mKeyboard.getKeys();
+ //canvas.translate(0, mKeyboardPaddingTop);
+ paint.setAlpha(255);
+ paint.setColor(mKeyTextColor);
+
+ final int keyCount = keys.size();
+ for (int i = 0; i < keyCount; i++) {
+ final Key key = keys.get(i);
+ int[] drawableState = key.getCurrentDrawableState();
+ keyBackground.setState(drawableState);
+
+ // Switch the character to uppercase if shift is pressed
+ String label = key.label == null? null : adjustCase(key.label).toString();
+
+ final Rect bounds = keyBackground.getBounds();
+ if (key.width != bounds.right ||
+ key.height != bounds.bottom) {
+ keyBackground.setBounds(0, 0, key.width, key.height);
+ }
+ canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
+ keyBackground.draw(canvas);
+
+ if (label != null) {
+ // For characters, use large font. For labels like "Done", use small font.
+ if (label.length() > 1 && key.codes.length < 2) {
+ paint.setTextSize(mLabelTextSize);
+ paint.setFakeBoldText(true);
+ } else {
+ paint.setTextSize(mKeyTextSize);
+ paint.setFakeBoldText(false);
+ }
+ // Draw a drop shadow for the text
+ paint.setShadowLayer(3f, 0, 0, 0xCC000000);
+ // Draw the text
+ canvas.drawText(label,
+ (key.width - padding.left - padding.right) / 2
+ + padding.left,
+ (key.height - padding.top - padding.bottom) / 2
+ + (paint.getTextSize() - paint.descent()) / 2 + padding.top,
+ paint);
+ // Turn off drop shadow
+ paint.setShadowLayer(0, 0, 0, 0);
+ } else if (key.icon != null) {
+ final int drawableX = (key.width - padding.left - padding.right
+ - key.icon.getIntrinsicWidth()) / 2 + padding.left;
+ final int drawableY = (key.height - padding.top - padding.bottom
+ - key.icon.getIntrinsicHeight()) / 2 + padding.top;
+ canvas.translate(drawableX, drawableY);
+ key.icon.setBounds(0, 0,
+ key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
+ key.icon.draw(canvas);
+ canvas.translate(-drawableX, -drawableY);
+ }
+ canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
+ }
+
+ // Overlay a dark rectangle to dim the keyboard
+ if (mMiniKeyboardOnScreen) {
+ paint.setColor(0xA0000000);
+ canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
+ }
+
+ if (DEBUG && mShowTouchPoints) {
+ paint.setAlpha(128);
+ paint.setColor(0xFFFF0000);
+ canvas.drawCircle(mStartX, mStartY, 3, paint);
+ canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint);
+ paint.setColor(0xFF0000FF);
+ canvas.drawCircle(mLastX, mLastY, 3, paint);
+ paint.setColor(0xFF00FF00);
+ canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
+ }
+ }
+
+ private void playKeyClick() {
+ if (mSoundOn) {
+ playSoundEffect(0);
+ }
+ }
+
+ private void vibrate() {
+ if (!mVibrateOn) {
+ return;
+ }
+ if (mVibrator == null) {
+ mVibrator = new Vibrator();
+ }
+ mVibrator.vibrate(mVibratePattern, -1);
+ }
+
+ private int getKeyIndices(int x, int y, int[] allKeys) {
+ final List<Key> keys = mKeyboard.getKeys();
+ 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();
+ for (int i = 0; i < keyCount; i++) {
+ final Key key = keys.get(i);
+ int dist = 0;
+ boolean isInside = key.isInside(x,y);
+ if (((mProximityCorrectOn
+ && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)
+ || isInside)
+ && key.codes[0] > 32) {
+ // Find insertion point
+ final int nCodes = key.codes.length;
+ if (dist < closestKeyDist) {
+ closestKeyDist = dist;
+ closestKey = i;
+ }
+
+ if (allKeys == null) continue;
+
+ for (int j = 0; j < mDistances.length; j++) {
+ if (mDistances[j] > dist) {
+ // Make space for nCodes codes
+ System.arraycopy(mDistances, j, mDistances, j + nCodes,
+ mDistances.length - j - nCodes);
+ System.arraycopy(allKeys, j, allKeys, j + nCodes,
+ 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;
+ }
+ }
+ }
+
+ if (isInside) {
+ primaryIndex = i;
+ }
+ }
+ if (primaryIndex == NOT_A_KEY) {
+ primaryIndex = closestKey;
+ }
+ return primaryIndex;
+ }
+
+ private void detectAndSendKey(int x, int y, long eventTime) {
+ int index = mCurrentKey;
+ if (index != NOT_A_KEY) {
+ vibrate();
+ final Key key = mKeyboard.getKeys().get(index);
+ if (key.text != null) {
+ for (int i = 0; i < key.text.length(); i++) {
+ mKeyboardActionListener.onKey(key.text.charAt(i), key.codes);
+ }
+ } else {
+ int code = key.codes[0];
+ //TextEntryState.keyPressedAt(key, x, y);
+ int[] codes = new int[MAX_NEARBY_KEYS];
+ Arrays.fill(codes, NOT_A_KEY);
+ getKeyIndices(x, y, codes);
+ // Multi-tap
+ if (mInMultiTap) {
+ if (mTapCount != -1) {
+ mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE);
+ } else {
+ mTapCount = 0;
+ }
+ code = key.codes[mTapCount];
+ }
+ mKeyboardActionListener.onKey(code, codes);
+ }
+ mLastSentIndex = index;
+ mLastTapTime = eventTime;
+ }
+ }
+
+ /**
+ * Handle multi-tap keys by producing the key label for the current multi-tap state.
+ */
+ private CharSequence getPreviewText(Key key) {
+ if (mInMultiTap) {
+ // Multi-tap
+ mPreviewLabel.setLength(0);
+ mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
+ return adjustCase(mPreviewLabel);
+ } else {
+ return adjustCase(key.label);
+ }
+ }
+
+ private void showPreview(int keyIndex) {
+ int oldKeyIndex = mCurrentKeyIndex;
+ final PopupWindow previewPopup = mPreviewPopup;
+
+ mCurrentKeyIndex = keyIndex;
+ // Release the old key and press the new key
+ final List<Key> keys = mKeyboard.getKeys();
+ if (oldKeyIndex != mCurrentKeyIndex) {
+ if (oldKeyIndex != NOT_A_KEY && keys.size() > oldKeyIndex) {
+ keys.get(oldKeyIndex).onReleased(mCurrentKeyIndex == NOT_A_KEY);
+ invalidateKey(oldKeyIndex);
+ }
+ if (mCurrentKeyIndex != NOT_A_KEY && keys.size() > mCurrentKeyIndex) {
+ keys.get(mCurrentKeyIndex).onPressed();
+ invalidateKey(mCurrentKeyIndex);
+ }
+ }
+ // If key changed and preview is on ...
+ if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
+ if (previewPopup.isShowing()) {
+ if (keyIndex == NOT_A_KEY) {
+ mHandler.sendMessageDelayed(mHandler
+ .obtainMessage(MSG_REMOVE_PREVIEW), 60);
+ }
+ }
+ 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(mPopupPreviewX + mOffsetInWindow[0],
+ mPopupPreviewY + mOffsetInWindow[1],
+ popupWidth, popupHeight);
+ } else {
+ previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
+ mPopupPreviewX + mOffsetInWindow[0],
+ mPopupPreviewY + mOffsetInWindow[1]);
+ }
+ mPreviewText.setVisibility(VISIBLE);
+ }
+ }
+ }
+
+ private void invalidateKey(int keyIndex) {
+ if (keyIndex < 0 || keyIndex >= mKeyboard.getKeys().size()) {
+ return;
+ }
+ final Key key = mKeyboard.getKeys().get(keyIndex);
+ invalidate(key.x + mPaddingLeft, key.y + mPaddingTop,
+ key.x + key.width + mPaddingLeft, key.y + key.height + mPaddingTop);
+ }
+
+ private boolean openPopupIfRequired(MotionEvent me) {
+ // Check if we have a popup layout specified first.
+ if (mPopupLayout == 0) {
+ return false;
+ }
+ if (mCurrentKey < 0 || mCurrentKey >= mKeyboard.getKeys().size()) {
+ return false;
+ }
+
+ Key popupKey = mKeyboard.getKeys().get(mCurrentKey);
+ boolean result = onLongPress(popupKey);
+ if (result) {
+ mAbortKey = true;
+ showPreview(NOT_A_KEY);
+ }
+ return result;
+ }
+
+ /**
+ * Called when a key is long pressed. By default this will open any popup keyboard associated
+ * with this key through the attributes popupLayout and popupCharacters.
+ * @param popupKey the key that was long pressed
+ * @return true if the long press is handled, false otherwise. Subclasses should call the
+ * method on the base class if the subclass doesn't wish to handle the call.
+ */
+ protected boolean onLongPress(Key popupKey) {
+ int popupKeyboardId = popupKey.popupResId;
+
+ if (popupKeyboardId != 0) {
+ mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);
+ if (mMiniKeyboardContainer == null) {
+ LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);
+ mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
+ com.android.internal.R.id.keyboardView);
+ View closeButton = mMiniKeyboardContainer.findViewById(
+ com.android.internal.R.id.button_close);
+ if (closeButton != null) closeButton.setOnClickListener(this);
+ mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
+ public void onKey(int primaryCode, int[] keyCodes) {
+ mKeyboardActionListener.onKey(primaryCode, keyCodes);
+ dismissPopupKeyboard();
+ }
+
+ public void swipeLeft() { }
+ public void swipeRight() { }
+ public void swipeUp() { }
+ public void swipeDown() { }
+ });
+ //mInputView.setSuggest(mSuggest);
+ Keyboard keyboard;
+ if (popupKey.popupCharacters != null) {
+ keyboard = new Keyboard(getContext(), popupKeyboardId,
+ popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
+ } else {
+ keyboard = new Keyboard(getContext(), popupKeyboardId);
+ }
+ mMiniKeyboard.setKeyboard(keyboard);
+ mMiniKeyboard.setPopupParent(this);
+ mMiniKeyboardContainer.measure(
+ MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
+
+ mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);
+ } else {
+ mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
+ com.android.internal.R.id.keyboardView);
+ }
+ if (mWindowOffset == null) {
+ mWindowOffset = new int[2];
+ getLocationInWindow(mWindowOffset);
+ }
+ mPopupX = popupKey.x + mPaddingLeft;
+ mPopupY = popupKey.y + mPaddingTop;
+ mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
+ mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
+ final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mWindowOffset[0];
+ final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mWindowOffset[1];
+ mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
+ mMiniKeyboard.setShifted(isShifted());
+ mPopupKeyboard.setContentView(mMiniKeyboardContainer);
+ mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
+ mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
+ mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
+ mMiniKeyboardOnScreen = true;
+ //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
+ invalidate();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent me) {
+ int touchX = (int) me.getX() - mPaddingLeft;
+ int touchY = (int) me.getY() + mVerticalCorrection - mPaddingTop;
+ int action = me.getAction();
+ long eventTime = me.getEventTime();
+ int keyIndex = getKeyIndices(touchX, touchY, null);
+
+ if (mGestureDetector.onTouchEvent(me)) {
+ showPreview(NOT_A_KEY);
+ mHandler.removeMessages(MSG_REPEAT);
+ return true;
+ }
+
+ // Needs to be called after the gesture detector gets a turn, as it may have
+ // displayed the mini keyboard
+ if (mMiniKeyboardOnScreen) {
+ return true;
+ }
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mAbortKey = false;
+ mStartX = touchX;
+ mStartY = touchY;
+ mLastCodeX = touchX;
+ mLastCodeY = touchY;
+ mLastKeyTime = 0;
+ mCurrentKeyTime = 0;
+ mLastKey = NOT_A_KEY;
+ mCurrentKey = keyIndex;
+ mDownTime = me.getEventTime();
+ mLastMoveTime = mDownTime;
+ checkMultiTap(eventTime, keyIndex);
+ if (mCurrentKey >= 0 && mKeyboard.getKeys().get(mCurrentKey).repeatable) {
+ mRepeatKeyIndex = mCurrentKey;
+ repeatKey();
+ Message msg = mHandler.obtainMessage(MSG_REPEAT);
+ mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
+ }
+ showPreview(keyIndex);
+ playKeyClick();
+ vibrate();
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (keyIndex != NOT_A_KEY) {
+ if (mCurrentKey == NOT_A_KEY) {
+ mCurrentKey = keyIndex;
+ mCurrentKeyTime = eventTime - mDownTime;
+ } else {
+ if (keyIndex == mCurrentKey) {
+ mCurrentKeyTime += eventTime - mLastMoveTime;
+ } else {
+ resetMultiTap();
+ mLastKey = mCurrentKey;
+ mLastCodeX = mLastX;
+ mLastCodeY = mLastY;
+ mLastKeyTime =
+ mCurrentKeyTime + eventTime - mLastMoveTime;
+ mCurrentKey = keyIndex;
+ mCurrentKeyTime = 0;
+ }
+ }
+ if (keyIndex != mRepeatKeyIndex) {
+ mHandler.removeMessages(MSG_REPEAT);
+ mRepeatKeyIndex = NOT_A_KEY;
+ }
+ }
+ showPreview(keyIndex);
+ break;
+
+ case MotionEvent.ACTION_UP:
+ mHandler.removeMessages(MSG_REPEAT);
+ if (keyIndex == mCurrentKey) {
+ mCurrentKeyTime += eventTime - mLastMoveTime;
+ } else {
+ resetMultiTap();
+ mLastKey = mCurrentKey;
+ mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
+ mCurrentKey = keyIndex;
+ mCurrentKeyTime = 0;
+ }
+ if (mCurrentKeyTime < mLastKeyTime && mLastKey != NOT_A_KEY) {
+ mCurrentKey = mLastKey;
+ touchX = mLastCodeX;
+ touchY = mLastCodeY;
+ }
+ 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);
+ }
+ mRepeatKeyIndex = NOT_A_KEY;
+ break;
+ }
+ mLastX = touchX;
+ mLastY = touchY;
+ return true;
+ }
+
+ private boolean repeatKey() {
+ Key key = mKeyboard.getKeys().get(mRepeatKeyIndex);
+ detectAndSendKey(key.x, key.y, mLastTapTime);
+ return true;
+ }
+
+ protected void swipeRight() {
+ mKeyboardActionListener.swipeRight();
+ }
+
+ protected void swipeLeft() {
+ mKeyboardActionListener.swipeLeft();
+ }
+
+ protected void swipeUp() {
+ mKeyboardActionListener.swipeUp();
+ }
+
+ protected void swipeDown() {
+ mKeyboardActionListener.swipeDown();
+ }
+
+ public void closing() {
+ if (mPreviewPopup.isShowing()) {
+ mPreviewPopup.dismiss();
+ }
+ dismissPopupKeyboard();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ closing();
+ }
+
+ private void dismissPopupKeyboard() {
+ if (mPopupKeyboard.isShowing()) {
+ mPopupKeyboard.dismiss();
+ mMiniKeyboardOnScreen = false;
+ invalidate();
+ }
+ }
+
+ public boolean handleBack() {
+ if (mPopupKeyboard.isShowing()) {
+ dismissPopupKeyboard();
+ return true;
+ }
+ return false;
+ }
+
+ private void resetMultiTap() {
+ mLastSentIndex = NOT_A_KEY;
+ mTapCount = 0;
+ mLastTapTime = -1;
+ mInMultiTap = false;
+ }
+
+ private void checkMultiTap(long eventTime, int keyIndex) {
+ if (keyIndex == NOT_A_KEY) return;
+ Key key = mKeyboard.getKeys().get(keyIndex);
+ if (key.codes.length > 1) {
+ mInMultiTap = true;
+ if (eventTime < mLastTapTime + MULTITAP_INTERVAL
+ && keyIndex == mLastSentIndex) {
+ mTapCount = (mTapCount + 1) % key.codes.length;
+ return;
+ } else {
+ mTapCount = -1;
+ return;
+ }
+ }
+ if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
+ resetMultiTap();
+ }
+ }
+}
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
new file mode 100644
index 0000000..9ff1665
--- /dev/null
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -0,0 +1,151 @@
+/*
+ * 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.inputmethodservice;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.IBinder;
+import android.view.Gravity;
+import android.view.WindowManager;
+
+/**
+ * A SoftInputWindow is a Dialog that is intended to be used for a top-level input
+ * method window. It will be displayed along the edge of the screen, moving
+ * the application user interface away from it so that the focused item is
+ * always visible.
+ */
+class SoftInputWindow extends Dialog {
+
+ /**
+ * Create a DockWindow that uses the default style.
+ *
+ * @param context The Context the DockWindow is to run it. In particular, it
+ * uses the window manager and theme in this context to present its
+ * UI.
+ */
+ public SoftInputWindow(Context context) {
+ super(context, com.android.internal.R.style.Theme_InputMethod);
+ initDockWindow();
+ }
+
+ public void setToken(IBinder token) {
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+ lp.token = token;
+ getWindow().setAttributes(lp);
+ }
+
+ /**
+ * Create a DockWindow that uses a custom style.
+ *
+ * @param context The Context in which the DockWindow should run. In
+ * particular, it 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
+ * 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.
+ */
+ public SoftInputWindow(Context context, int theme) {
+ super(context, theme);
+ initDockWindow();
+ }
+
+ /**
+ * Get the size of the DockWindow.
+ *
+ * @return If the DockWindow sticks to the top or bottom of the screen, the
+ * return value is the height of the DockWindow, and its width is
+ * equal to the width of the screen; If the DockWindow sticks to the
+ * left or right of the screen, the return value is the width of the
+ * DockWindow, and its height is equal to the height of the screen.
+ */
+ public int getSize() {
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+
+ if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) {
+ return lp.height;
+ } else {
+ return lp.width;
+ }
+ }
+
+ /**
+ * Set the size of the DockWindow.
+ *
+ * @param size If the DockWindow sticks to the top or bottom of the screen,
+ * <var>size</var> is the height of the DockWindow, and its width is
+ * equal to the width of the screen; If the DockWindow sticks to the
+ * left or right of the screen, <var>size</var> is the width of the
+ * DockWindow, and its height is equal to the height of the screen.
+ */
+ public void setSize(int size) {
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+
+ if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) {
+ lp.width = -1;
+ lp.height = size;
+ } else {
+ lp.width = size;
+ lp.height = -1;
+ }
+ getWindow().setAttributes(lp);
+ }
+
+ /**
+ * Set which boundary of the screen the DockWindow sticks to.
+ *
+ * @param gravity The boundary of the screen to stick. See {#link
+ * android.view.Gravity.LEFT}, {#link android.view.Gravity.TOP},
+ * {#link android.view.Gravity.BOTTOM}, {#link
+ * android.view.Gravity.RIGHT}.
+ */
+ public void setGravity(int gravity) {
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+
+ boolean oldIsVertical = (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM);
+
+ lp.gravity = gravity;
+
+ boolean newIsVertical = (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM);
+
+ if (oldIsVertical != newIsVertical) {
+ int tmp = lp.width;
+ lp.width = lp.height;
+ lp.height = tmp;
+ getWindow().setAttributes(lp);
+ }
+ }
+
+ private void initDockWindow() {
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+
+ lp.type = WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+ lp.setTitle("InputMethod");
+
+ lp.gravity = Gravity.BOTTOM;
+ lp.width = -1;
+
+ getWindow().setAttributes(lp);
+ getWindow().setFlags(
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
+ WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ }
+}
diff --git a/core/java/android/inputmethodservice/package.html b/core/java/android/inputmethodservice/package.html
new file mode 100644
index 0000000..164349b
--- /dev/null
+++ b/core/java/android/inputmethodservice/package.html
@@ -0,0 +1,8 @@
+<html>
+<body>
+Base classes for writing input methods. These APIs are not for use by
+normal applications, they are a framework specifically for writing input
+method components. Implementations will typically derive from
+{@link android.inputmethodservice.InputMethodService}.
+</body>
+</html>
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index ae74e6f..1d939e1 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -30,7 +30,6 @@ import com.android.internal.telephony.TelephonyIntents;
import android.net.NetworkInfo.DetailedState;
import android.telephony.TelephonyManager;
import android.util.Log;
-import android.util.Config;
import android.text.TextUtils;
import java.util.List;
@@ -71,7 +70,9 @@ public class MobileDataStateTracker extends NetworkStateTracker {
* @param target a message handler for getting callbacks about state changes
*/
public MobileDataStateTracker(Context context, Handler target) {
- super(context, target, ConnectivityManager.TYPE_MOBILE);
+ super(context, target, ConnectivityManager.TYPE_MOBILE,
+ TelephonyManager.getDefault().getNetworkType(), "MOBILE",
+ TelephonyManager.getDefault().getNetworkTypeName());
mPhoneService = null;
mDnsServers = new ArrayList<String>();
}
@@ -80,9 +81,10 @@ public class MobileDataStateTracker extends NetworkStateTracker {
* Begin monitoring mobile data connectivity.
*/
public void startMonitoring() {
-
- IntentFilter filter = new IntentFilter(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+ IntentFilter filter =
+ new IntentFilter(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
+ filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
Intent intent = mContext.registerReceiver(new MobileDataStateReceiver(), filter);
if (intent != null)
@@ -146,6 +148,9 @@ public class MobileDataStateTracker extends NetworkStateTracker {
reason == null ? "" : "(" + reason + ")");
setDetailedState(DetailedState.FAILED, reason, apnName);
}
+ TelephonyManager tm = TelephonyManager.getDefault();
+ setRoamingStatus(tm.isNetworkRoaming());
+ setSubtype(tm.getNetworkType(), tm.getNetworkTypeName());
}
}
@@ -223,6 +228,15 @@ public class MobileDataStateTracker extends NetworkStateTracker {
}
/**
+ * {@inheritDoc}
+ * The mobile data network subtype indicates what generation network technology is in effect,
+ * e.g., GPRS, EDGE, UMTS, etc.
+ */
+ public int getNetworkSubtype() {
+ return TelephonyManager.getDefault().getNetworkType();
+ }
+
+ /**
* Return the system properties name associated with the tcp buffer sizes
* for this network.
*/
@@ -358,8 +372,8 @@ public class MobileDataStateTracker extends NetworkStateTracker {
}
/**
- * Tells the phone sub-system that the caller is finished is
- * finished using the named feature. The only supported feature at
+ * Tells the phone sub-system that the caller is finished
+ * using the named feature. The only supported feature at
* this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
* to specify that it wants to send and/or receive MMS data.
* @param feature the name of the feature that is no longer needed
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index f776abf..8c82212 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -99,24 +99,38 @@ public class NetworkInfo implements Parcelable {
}
private int mNetworkType;
+ private int mSubtype;
+ private String mTypeName;
+ private String mSubtypeName;
private State mState;
private DetailedState mDetailedState;
private String mReason;
private String mExtraInfo;
private boolean mIsFailover;
+ private boolean mIsRoaming;
/**
* Indicates whether network connectivity is possible:
*/
private boolean mIsAvailable;
- public NetworkInfo(int type) {
+ /**
+ * TODO This is going away as soon as API council review happens.
+ * @param type network type
+ */
+ public NetworkInfo(int type) {}
+
+ NetworkInfo(int type, int subtype, String typeName, String subtypeName) {
if (!ConnectivityManager.isNetworkTypeValid(type)) {
throw new IllegalArgumentException("Invalid network type: " + type);
}
- this.mNetworkType = type;
+ mNetworkType = type;
+ mSubtype = subtype;
+ mTypeName = typeName;
+ mSubtypeName = subtypeName;
setDetailedState(DetailedState.IDLE, null, null);
mState = State.UNKNOWN;
mIsAvailable = true;
+ mIsRoaming = false;
}
/**
@@ -129,6 +143,41 @@ public class NetworkInfo implements Parcelable {
}
/**
+ * Return a network-type-specific integer describing the subtype
+ * of the network.
+ * @return the network subtype
+ *
+ * @hide pending API council review
+ */
+ public int getSubtype() {
+ return mSubtype;
+ }
+
+ void setSubtype(int subtype, String subtypeName) {
+ mSubtype = subtype;
+ mSubtypeName = subtypeName;
+ }
+
+ /**
+ * Return a human-readable name describe the type of the network,
+ * for example "WIFI" or "MOBILE".
+ * @return the name of the network type
+ */
+ public String getTypeName() {
+ return mTypeName;
+ }
+
+ /**
+ * Return a human-readable name describing the subtype of the network.
+ * @return the name of the network subtype
+ *
+ * @hide pending API council review
+ */
+ public String getSubtypeName() {
+ return mSubtypeName;
+ }
+
+ /**
* Indicates whether network connectivity exists or is in the process
* of being established. This is good for applications that need to
* do anything related to the network other than read or write data.
@@ -170,7 +219,7 @@ public class NetworkInfo implements Parcelable {
* Sets if the network is available, ie, if the connectivity is possible.
* @param isAvailable the new availability value.
*
- * {@hide}
+ * @hide
*/
public void setIsAvailable(boolean isAvailable) {
mIsAvailable = isAvailable;
@@ -187,12 +236,33 @@ public class NetworkInfo implements Parcelable {
return mIsFailover;
}
- /** {@hide} */
+ /**
+ * Set the failover boolean.
+ * @param isFailover {@code true} to mark the current connection attempt
+ * as a failover.
+ * @hide
+ */
public void setFailover(boolean isFailover) {
mIsFailover = isFailover;
}
/**
+ * Indicates whether the device is currently roaming on this network.
+ * When {@code true}, it suggests that use of data on this network
+ * may incur extra costs.
+ * @return {@code true} if roaming is in effect, {@code false} otherwise.
+ *
+ * @hide pending API council
+ */
+ public boolean isRoaming() {
+ return mIsRoaming;
+ }
+
+ void setRoaming(boolean isRoaming) {
+ mIsRoaming = isRoaming;
+ }
+
+ /**
* Reports the current coarse-grained state of the network.
* @return the coarse-grained state
*/
@@ -215,8 +285,6 @@ public class NetworkInfo implements Parcelable {
* if one was supplied. May be {@code null}.
* @param extraInfo an optional {@code String} providing addditional network state
* information passed up from the lower networking layers.
- *
- * {@hide}
*/
void setDetailedState(DetailedState detailedState, String reason, String extraInfo) {
this.mDetailedState = detailedState;
@@ -247,52 +315,59 @@ public class NetworkInfo implements Parcelable {
@Override
public String toString() {
StringBuilder builder = new StringBuilder("NetworkInfo: ");
- builder.append("type: ").append(getTypeName()).append(", state: ").append(mState).
- append("/").append(mDetailedState).
+ builder.append("type: ").append(getTypeName()).append("[").append(getSubtypeName()).
+ append("], state: ").append(mState).append("/").append(mDetailedState).
append(", reason: ").append(mReason == null ? "(unspecified)" : mReason).
append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo).
+ append(", roaming: ").append(mIsRoaming).
append(", failover: ").append(mIsFailover).
append(", isAvailable: ").append(mIsAvailable);
return builder.toString();
}
- public String getTypeName() {
- switch (mNetworkType) {
- case ConnectivityManager.TYPE_WIFI:
- return "WIFI";
- case ConnectivityManager.TYPE_MOBILE:
- return "MOBILE";
- default:
- return "<invalid>";
- }
- }
-
- /** Implement the Parcelable interface {@hide} */
+ /**
+ * Implement the Parcelable interface
+ * @hide
+ */
public int describeContents() {
return 0;
}
- /** Implement the Parcelable interface {@hide} */
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mNetworkType);
+ dest.writeInt(mSubtype);
+ dest.writeString(mTypeName);
+ dest.writeString(mSubtypeName);
dest.writeString(mState.name());
dest.writeString(mDetailedState.name());
dest.writeInt(mIsFailover ? 1 : 0);
dest.writeInt(mIsAvailable ? 1 : 0);
+ dest.writeInt(mIsRoaming ? 1 : 0);
dest.writeString(mReason);
dest.writeString(mExtraInfo);
}
- /** Implement the Parcelable interface {@hide} */
+ /**
+ * Implement the Parcelable interface.
+ * @hide
+ */
public static final Creator<NetworkInfo> CREATOR =
new Creator<NetworkInfo>() {
public NetworkInfo createFromParcel(Parcel in) {
int netType = in.readInt();
- NetworkInfo netInfo = new NetworkInfo(netType);
+ int subtype = in.readInt();
+ String typeName = in.readString();
+ String subtypeName = in.readString();
+ NetworkInfo netInfo = new NetworkInfo(netType, subtype, typeName, subtypeName);
netInfo.mState = State.valueOf(in.readString());
netInfo.mDetailedState = DetailedState.valueOf(in.readString());
netInfo.mIsFailover = in.readInt() != 0;
netInfo.mIsAvailable = in.readInt() != 0;
+ netInfo.mIsRoaming = in.readInt() != 0;
netInfo.mReason = in.readString();
netInfo.mExtraInfo = in.readString();
return netInfo;
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 4e1efa6..37087ac 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -22,7 +22,6 @@ import java.io.IOException;
import android.os.Handler;
import android.os.Message;
import android.os.SystemProperties;
-import android.os.PowerManager;
import android.content.Context;
import android.text.TextUtils;
import android.util.Config;
@@ -41,6 +40,7 @@ public abstract class NetworkStateTracker extends Handler {
protected NetworkInfo mNetworkInfo;
protected Context mContext;
protected Handler mTarget;
+ private boolean mTeardownRequested;
private static boolean DBG = Config.LOGV;
private static final String TAG = "NetworkStateTracker";
@@ -54,12 +54,20 @@ public abstract class NetworkStateTracker extends Handler {
*/
public static final int EVENT_NOTIFICATION_CHANGED = 3;
public static final int EVENT_CONFIGURATION_CHANGED = 4;
-
- public NetworkStateTracker(Context context, Handler target, int networkType) {
+ public static final int EVENT_ROAMING_CHANGED = 5;
+ public static final int EVENT_NETWORK_SUBTYPE_CHANGED = 6;
+
+ public NetworkStateTracker(Context context,
+ Handler target,
+ int networkType,
+ int subType,
+ String typeName,
+ String subtypeName) {
super();
mContext = context;
mTarget = target;
- this.mNetworkInfo = new NetworkInfo(networkType);
+ mTeardownRequested = false;
+ this.mNetworkInfo = new NetworkInfo(networkType, subType, typeName, subtypeName);
}
public NetworkInfo getNetworkInfo() {
@@ -222,6 +230,14 @@ public abstract class NetworkStateTracker extends Handler {
mNetworkInfo.setDetailedState(state, null, null);
}
+ public void setTeardownRequested(boolean isRequested) {
+ mTeardownRequested = isRequested;
+ }
+
+ public boolean isTeardownRequested() {
+ return mTeardownRequested;
+ }
+
/**
* Send a notification that the results of a scan for network access
* points has completed, and results are available.
@@ -231,6 +247,32 @@ public abstract class NetworkStateTracker extends Handler {
msg.sendToTarget();
}
+ /**
+ * Record the roaming status of the device, and if it is a change from the previous
+ * status, send a notification to any listeners.
+ * @param isRoaming {@code true} if the device is now roaming, {@code false}
+ * if it is no longer roaming.
+ */
+ protected void setRoamingStatus(boolean isRoaming) {
+ if (isRoaming != mNetworkInfo.isRoaming()) {
+ mNetworkInfo.setRoaming(isRoaming);
+ Message msg = mTarget.obtainMessage(EVENT_ROAMING_CHANGED, mNetworkInfo);
+ msg.sendToTarget();
+ }
+ }
+
+ protected void setSubtype(int subtype, String subtypeName) {
+ if (mNetworkInfo.isConnected()) {
+ int oldSubtype = mNetworkInfo.getSubtype();
+ if (subtype != oldSubtype) {
+ mNetworkInfo.setSubtype(subtype, subtypeName);
+ Message msg = mTarget.obtainMessage(
+ EVENT_NETWORK_SUBTYPE_CHANGED, oldSubtype, 0, mNetworkInfo);
+ msg.sendToTarget();
+ }
+ }
+ }
+
public abstract void startMonitoring();
/**
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 129248a..1153648 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -67,6 +67,14 @@ public class NetworkUtils {
public native static boolean stopDhcp(String interfaceName);
/**
+ * Release the current DHCP lease.
+ * @param interfaceName the name of the interface for which the lease should
+ * be released
+ * @return {@code true} for success, {@code false} for failure
+ */
+ public native static boolean releaseDhcpLease(String interfaceName);
+
+ /**
* Return the last DHCP-related error message that was recorded.
* <p/>NOTE: This string is not localized, but currently it is only
* used in logging.
diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java
index 86e1d5b..9f07c0a 100644
--- a/core/java/android/net/Proxy.java
+++ b/core/java/android/net/Proxy.java
@@ -43,9 +43,9 @@ final public class Proxy {
static final public String getHost(Context ctx) {
ContentResolver contentResolver = ctx.getContentResolver();
Assert.assertNotNull(contentResolver);
- String host = Settings.System.getString(
+ String host = Settings.Secure.getString(
contentResolver,
- Settings.System.HTTP_PROXY);
+ Settings.Secure.HTTP_PROXY);
if (host != null) {
int i = host.indexOf(':');
if (i == -1) {
@@ -67,9 +67,9 @@ final public class Proxy {
static final public int getPort(Context ctx) {
ContentResolver contentResolver = ctx.getContentResolver();
Assert.assertNotNull(contentResolver);
- String host = Settings.System.getString(
+ String host = Settings.Secure.getString(
contentResolver,
- Settings.System.HTTP_PROXY);
+ Settings.Secure.HTTP_PROXY);
if (host != null) {
int i = host.indexOf(':');
if (i == -1) {
diff --git a/core/java/android/net/http/Connection.java b/core/java/android/net/http/Connection.java
index 2c82582..563634f 100644
--- a/core/java/android/net/http/Connection.java
+++ b/core/java/android/net/http/Connection.java
@@ -375,6 +375,11 @@ abstract class Connection {
if (HttpLog.LOGV) HttpLog.v("Failed to open connection");
error = EventHandler.ERROR_LOOKUP;
exception = e;
+ } catch (IllegalArgumentException e) {
+ if (HttpLog.LOGV) HttpLog.v("Illegal argument exception");
+ error = EventHandler.ERROR_CONNECT;
+ req.mFailCount = RETRY_REQUEST_LIMIT;
+ exception = e;
} catch (SSLConnectionClosedByUserException e) {
// hack: if we have an SSL connection failure,
// we don't want to reconnect
diff --git a/core/java/android/net/http/Headers.java b/core/java/android/net/http/Headers.java
index 5d85ba4..b0923d1 100644
--- a/core/java/android/net/http/Headers.java
+++ b/core/java/android/net/http/Headers.java
@@ -30,7 +30,7 @@ import org.apache.http.util.CharArrayBuffer;
/**
* Manages received headers
- *
+ *
* {@hide}
*/
public final class Headers {
@@ -42,16 +42,16 @@ public final class Headers {
*/
public final static int CONN_CLOSE = 1;
/**
- * indicate HTTP 1.1 connection keep alive
+ * indicate HTTP 1.1 connection keep alive
*/
public final static int CONN_KEEP_ALIVE = 2;
-
+
// initial values.
public final static int NO_CONN_TYPE = 0;
public final static long NO_TRANSFER_ENCODING = 0;
public final static long NO_CONTENT_LENGTH = -1;
- // header string
+ // header strings
public final static String TRANSFER_ENCODING = "transfer-encoding";
public final static String CONTENT_LEN = "content-length";
public final static String CONTENT_TYPE = "content-type";
@@ -93,25 +93,61 @@ public final class Headers {
private final static int HASH_PRAGMA = -980228804;
private final static int HASH_REFRESH = 1085444827;
+ // keep any headers that require direct access in a presized
+ // string array
+ private final static int IDX_TRANSFER_ENCODING = 0;
+ private final static int IDX_CONTENT_LEN = 1;
+ private final static int IDX_CONTENT_TYPE = 2;
+ private final static int IDX_CONTENT_ENCODING = 3;
+ private final static int IDX_CONN_DIRECTIVE = 4;
+ private final static int IDX_LOCATION = 5;
+ private final static int IDX_PROXY_CONNECTION = 6;
+ private final static int IDX_WWW_AUTHENTICATE = 7;
+ private final static int IDX_PROXY_AUTHENTICATE = 8;
+ private final static int IDX_CONTENT_DISPOSITION = 9;
+ private final static int IDX_ACCEPT_RANGES = 10;
+ private final static int IDX_EXPIRES = 11;
+ private final static int IDX_CACHE_CONTROL = 12;
+ private final static int IDX_LAST_MODIFIED = 13;
+ private final static int IDX_ETAG = 14;
+ private final static int IDX_SET_COOKIE = 15;
+ private final static int IDX_PRAGMA = 16;
+ private final static int IDX_REFRESH = 17;
+
+ private final static int HEADER_COUNT = 18;
+
+ /* parsed values */
private long transferEncoding;
private long contentLength; // Content length of the incoming data
private int connectionType;
-
- private String contentType;
- private String contentEncoding;
- private String location;
- private String wwwAuthenticate;
- private String proxyAuthenticate;
- private String contentDisposition;
- private String acceptRanges;
- private String expires;
- private String cacheControl;
- private String lastModified;
- private String etag;
- private String pragma;
- private String refresh;
private ArrayList<String> cookies = new ArrayList<String>(2);
+ private String[] mHeaders = new String[HEADER_COUNT];
+ private final static String[] sHeaderNames = {
+ TRANSFER_ENCODING,
+ CONTENT_LEN,
+ CONTENT_TYPE,
+ CONTENT_ENCODING,
+ CONN_DIRECTIVE,
+ LOCATION,
+ PROXY_CONNECTION,
+ WWW_AUTHENTICATE,
+ PROXY_AUTHENTICATE,
+ CONTENT_DISPOSITION,
+ ACCEPT_RANGES,
+ EXPIRES,
+ CACHE_CONTROL,
+ LAST_MODIFIED,
+ ETAG,
+ SET_COOKIE,
+ PRAGMA,
+ REFRESH
+ };
+
+ // Catch-all for headers not explicitly handled
+ private ArrayList<String> mExtraHeaderNames = new ArrayList<String>(4);
+ private ArrayList<String> mExtraHeaderValues = new ArrayList<String>(4);
+
public Headers() {
transferEncoding = NO_TRANSFER_ENCODING;
contentLength = NO_CONTENT_LENGTH;
@@ -129,23 +165,22 @@ public final class Headers {
}
pos++;
+ String val = buffer.substringTrimmed(pos, buffer.length());
if (HttpLog.LOGV) {
- String val = buffer.substringTrimmed(pos, buffer.length());
HttpLog.v("hdr " + buffer.length() + " " + buffer);
}
switch (name.hashCode()) {
case HASH_TRANSFER_ENCODING:
if (name.equals(TRANSFER_ENCODING)) {
- // headers.transferEncoding =
+ mHeaders[IDX_TRANSFER_ENCODING] = val;
HeaderElement[] encodings = BasicHeaderValueParser.DEFAULT
- .parseElements(buffer, new ParserCursor(pos,
+ .parseElements(buffer, new ParserCursor(pos,
buffer.length()));
// The chunked encoding must be the last one applied RFC2616,
// 14.41
int len = encodings.length;
- if (HTTP.IDENTITY_CODING.equalsIgnoreCase(buffer
- .substringTrimmed(pos, buffer.length()))) {
+ if (HTTP.IDENTITY_CODING.equalsIgnoreCase(val)) {
transferEncoding = ContentLengthStrategy.IDENTITY;
} else if ((len > 0)
&& (HTTP.CHUNK_CODING
@@ -158,9 +193,9 @@ public final class Headers {
break;
case HASH_CONTENT_LEN:
if (name.equals(CONTENT_LEN)) {
+ mHeaders[IDX_CONTENT_LEN] = val;
try {
- contentLength = Long.parseLong(buffer.substringTrimmed(pos,
- buffer.length()));
+ contentLength = Long.parseLong(val);
} catch (NumberFormatException e) {
if (Config.LOGV) {
Log.v(LOGTAG, "Headers.headers(): error parsing"
@@ -171,88 +206,90 @@ public final class Headers {
break;
case HASH_CONTENT_TYPE:
if (name.equals(CONTENT_TYPE)) {
- contentType = buffer.substringTrimmed(pos, buffer.length());
+ mHeaders[IDX_CONTENT_TYPE] = val;
}
break;
case HASH_CONTENT_ENCODING:
if (name.equals(CONTENT_ENCODING)) {
- contentEncoding = buffer.substringTrimmed(pos, buffer.length());
+ mHeaders[IDX_CONTENT_ENCODING] = val;
}
break;
case HASH_CONN_DIRECTIVE:
if (name.equals(CONN_DIRECTIVE)) {
+ mHeaders[IDX_CONN_DIRECTIVE] = val;
setConnectionType(buffer, pos);
}
break;
case HASH_LOCATION:
if (name.equals(LOCATION)) {
- location = buffer.substringTrimmed(pos, buffer.length());
+ mHeaders[IDX_LOCATION] = val;
}
break;
case HASH_PROXY_CONNECTION:
if (name.equals(PROXY_CONNECTION)) {
+ mHeaders[IDX_PROXY_CONNECTION] = val;
setConnectionType(buffer, pos);
}
break;
case HASH_WWW_AUTHENTICATE:
if (name.equals(WWW_AUTHENTICATE)) {
- wwwAuthenticate = buffer.substringTrimmed(pos, buffer.length());
+ mHeaders[IDX_WWW_AUTHENTICATE] = val;
}
break;
case HASH_PROXY_AUTHENTICATE:
if (name.equals(PROXY_AUTHENTICATE)) {
- proxyAuthenticate = buffer.substringTrimmed(pos, buffer
- .length());
+ mHeaders[IDX_PROXY_AUTHENTICATE] = val;
}
break;
case HASH_CONTENT_DISPOSITION:
if (name.equals(CONTENT_DISPOSITION)) {
- contentDisposition = buffer.substringTrimmed(pos, buffer
- .length());
+ mHeaders[IDX_CONTENT_DISPOSITION] = val;
}
break;
case HASH_ACCEPT_RANGES:
if (name.equals(ACCEPT_RANGES)) {
- acceptRanges = buffer.substringTrimmed(pos, buffer.length());
+ mHeaders[IDX_ACCEPT_RANGES] = val;
}
break;
case HASH_EXPIRES:
if (name.equals(EXPIRES)) {
- expires = buffer.substringTrimmed(pos, buffer.length());
+ mHeaders[IDX_EXPIRES] = val;
}
break;
case HASH_CACHE_CONTROL:
if (name.equals(CACHE_CONTROL)) {
- cacheControl = buffer.substringTrimmed(pos, buffer.length());
+ mHeaders[IDX_CACHE_CONTROL] = val;
}
break;
case HASH_LAST_MODIFIED:
if (name.equals(LAST_MODIFIED)) {
- lastModified = buffer.substringTrimmed(pos, buffer.length());
+ mHeaders[IDX_LAST_MODIFIED] = val;
}
break;
case HASH_ETAG:
if (name.equals(ETAG)) {
- etag = buffer.substringTrimmed(pos, buffer.length());
+ mHeaders[IDX_ETAG] = val;
}
break;
case HASH_SET_COOKIE:
if (name.equals(SET_COOKIE)) {
- cookies.add(buffer.substringTrimmed(pos, buffer.length()));
+ mHeaders[IDX_SET_COOKIE] = val;
+ cookies.add(val);
}
break;
case HASH_PRAGMA:
if (name.equals(PRAGMA)) {
- pragma = buffer.substringTrimmed(pos, buffer.length());
+ mHeaders[IDX_PRAGMA] = val;
}
break;
case HASH_REFRESH:
if (name.equals(REFRESH)) {
- refresh = buffer.substringTrimmed(pos, buffer.length());
+ mHeaders[IDX_REFRESH] = val;
}
break;
default:
- // ignore
+ mExtraHeaderNames.add(name);
+ mExtraHeaderValues.add(val);
}
}
@@ -268,70 +305,60 @@ public final class Headers {
return connectionType;
}
- private void setConnectionType(CharArrayBuffer buffer, int pos) {
- if (CharArrayBuffers.containsIgnoreCaseTrimmed(
- buffer, pos, HTTP.CONN_CLOSE)) {
- connectionType = CONN_CLOSE;
- } else if (CharArrayBuffers.containsIgnoreCaseTrimmed(
- buffer, pos, HTTP.CONN_KEEP_ALIVE)) {
- connectionType = CONN_KEEP_ALIVE;
- }
- }
-
public String getContentType() {
- return this.contentType;
+ return mHeaders[IDX_CONTENT_TYPE];
}
public String getContentEncoding() {
- return this.contentEncoding;
+ return mHeaders[IDX_CONTENT_ENCODING];
}
public String getLocation() {
- return this.location;
+ return mHeaders[IDX_LOCATION];
}
public String getWwwAuthenticate() {
- return this.wwwAuthenticate;
+ return mHeaders[IDX_WWW_AUTHENTICATE];
}
public String getProxyAuthenticate() {
- return this.proxyAuthenticate;
+ return mHeaders[IDX_PROXY_AUTHENTICATE];
}
public String getContentDisposition() {
- return this.contentDisposition;
+ return mHeaders[IDX_CONTENT_DISPOSITION];
}
public String getAcceptRanges() {
- return this.acceptRanges;
+ return mHeaders[IDX_ACCEPT_RANGES];
}
public String getExpires() {
- return this.expires;
+ return mHeaders[IDX_EXPIRES];
}
public String getCacheControl() {
- return this.cacheControl;
+ return mHeaders[IDX_CACHE_CONTROL];
}
public String getLastModified() {
- return this.lastModified;
+ return mHeaders[IDX_LAST_MODIFIED];
}
public String getEtag() {
- return this.etag;
+ return mHeaders[IDX_ETAG];
}
public ArrayList<String> getSetCookie() {
return this.cookies;
}
-
+
public String getPragma() {
- return this.pragma;
+ return mHeaders[IDX_PRAGMA];
}
-
+
public String getRefresh() {
- return this.refresh;
+ return mHeaders[IDX_REFRESH];
}
public void setContentLength(long value) {
@@ -339,46 +366,82 @@ public final class Headers {
}
public void setContentType(String value) {
- this.contentType = value;
+ mHeaders[IDX_CONTENT_TYPE] = value;
}
public void setContentEncoding(String value) {
- this.contentEncoding = value;
+ mHeaders[IDX_CONTENT_ENCODING] = value;
}
public void setLocation(String value) {
- this.location = value;
+ mHeaders[IDX_LOCATION] = value;
}
public void setWwwAuthenticate(String value) {
- this.wwwAuthenticate = value;
+ mHeaders[IDX_WWW_AUTHENTICATE] = value;
}
public void setProxyAuthenticate(String value) {
- this.proxyAuthenticate = value;
+ mHeaders[IDX_PROXY_AUTHENTICATE] = value;
}
public void setContentDisposition(String value) {
- this.contentDisposition = value;
+ mHeaders[IDX_CONTENT_DISPOSITION] = value;
}
public void setAcceptRanges(String value) {
- this.acceptRanges = value;
+ mHeaders[IDX_ACCEPT_RANGES] = value;
}
public void setExpires(String value) {
- this.expires = value;
+ mHeaders[IDX_EXPIRES] = value;
}
public void setCacheControl(String value) {
- this.cacheControl = value;
+ mHeaders[IDX_CACHE_CONTROL] = value;
}
public void setLastModified(String value) {
- this.lastModified = value;
+ mHeaders[IDX_LAST_MODIFIED] = value;
}
public void setEtag(String value) {
- this.etag = value;
+ mHeaders[IDX_ETAG] = value;
+ }
+
+ public interface HeaderCallback {
+ public void header(String name, String value);
+ }
+
+ /**
+ * Reports all non-null headers to the callback
+ */
+ public void getHeaders(HeaderCallback hcb) {
+ for (int i = 0; i < HEADER_COUNT; i++) {
+ String h = mHeaders[i];
+ if (h != null) {
+ hcb.header(sHeaderNames[i], h);
+ }
+ }
+ int extraLen = mExtraHeaderNames.size();
+ for (int i = 0; i < extraLen; i++) {
+ if (Config.LOGV) {
+ HttpLog.v("Headers.getHeaders() extra: " + i + " " +
+ mExtraHeaderNames.get(i) + " " + mExtraHeaderValues.get(i));
+ }
+ hcb.header(mExtraHeaderNames.get(i),
+ mExtraHeaderValues.get(i));
+ }
+
+ }
+
+ private void setConnectionType(CharArrayBuffer buffer, int pos) {
+ if (CharArrayBuffers.containsIgnoreCaseTrimmed(
+ buffer, pos, HTTP.CONN_CLOSE)) {
+ connectionType = CONN_CLOSE;
+ } else if (CharArrayBuffers.containsIgnoreCaseTrimmed(
+ buffer, pos, HTTP.CONN_KEEP_ALIVE)) {
+ connectionType = CONN_KEEP_ALIVE;
+ }
}
}
diff --git a/core/java/android/net/http/Request.java b/core/java/android/net/http/Request.java
index bcbecf0..df4fff0 100644
--- a/core/java/android/net/http/Request.java
+++ b/core/java/android/net/http/Request.java
@@ -16,6 +16,7 @@
package android.net.http;
+import java.io.EOFException;
import java.io.InputStream;
import java.io.IOException;
import java.util.Iterator;
@@ -279,6 +280,11 @@ class Request {
count = 0;
}
}
+ } catch (EOFException e) {
+ /* InflaterInputStream throws an EOFException when the
+ server truncates gzipped content. Handle this case
+ as we do truncated non-gzipped content: no error */
+ if (HttpLog.LOGV) HttpLog.v( "readResponse() handling " + e);
} catch(IOException e) {
// don't throw if we have a non-OK status code
if (statusCode == HttpStatus.SC_OK) {
diff --git a/core/java/android/net/http/RequestHandle.java b/core/java/android/net/http/RequestHandle.java
index 5d81250..65e6117 100644
--- a/core/java/android/net/http/RequestHandle.java
+++ b/core/java/android/net/http/RequestHandle.java
@@ -113,7 +113,7 @@ public class RequestHandle {
* @param statusCode HTTP status code returned from original request
* @param cacheHeaders Cache header for redirect URL
* @return true if setup succeeds, false otherwise (redirect loop
- * count exceeded)
+ * count exceeded, body provider unable to rewind on 307 redirect)
*/
public boolean setupRedirect(String redirectTo, int statusCode,
Map<String, String> cacheHeaders) {
@@ -164,8 +164,22 @@ public class RequestHandle {
}
mMethod = "GET";
}
- mHeaders.remove("Content-Type");
- mBodyProvider = null;
+ /* Only repost content on a 307. If 307, reset the body
+ provider so we can replay the body */
+ if (statusCode == 307) {
+ try {
+ if (mBodyProvider != null) mBodyProvider.reset();
+ } catch (java.io.IOException ex) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("setupAuthResponse() failed to reset body provider");
+ }
+ return false;
+ }
+
+ } else {
+ mHeaders.remove("Content-Type");
+ mBodyProvider = null;
+ }
// Update the cache headers for this URL
mHeaders.putAll(cacheHeaders);
diff --git a/core/java/android/net/http/RequestQueue.java b/core/java/android/net/http/RequestQueue.java
index d592995..66d5722 100644
--- a/core/java/android/net/http/RequestQueue.java
+++ b/core/java/android/net/http/RequestQueue.java
@@ -596,7 +596,7 @@ public class RequestQueue implements RequestFeeder {
}
protected synchronized void queueRequest(Request request, boolean head) {
- HttpHost host = request.mHost;
+ HttpHost host = request.mProxyHost == null ? request.mHost : request.mProxyHost;
LinkedList<Request> reqList;
if (mPending.containsKey(host)) {
reqList = mPending.get(host);
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
new file mode 100644
index 0000000..ee4e897
--- /dev/null
+++ b/core/java/android/os/AsyncTask.java
@@ -0,0 +1,454 @@
+/*
+ * 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.os;
+
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * <p>AsyncTask enables proper and easy use of the UI thread. This class allows to
+ * perform background operations and publish results on the UI thread without
+ * having to manipulate threads and/or handlers.</p>
+ *
+ * <p>An asynchronous task is defined by a computation that runs on a background thread and
+ * whose result is published on the UI thread. An asynchronous task is defined by 3 generic
+ * types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>,
+ * and 4 steps, called <code>begin</code>, <code>doInBackground</code>,
+ * <code>processProgress<code> and <code>end</code>.</p>
+ *
+ * <h2>Usage</h2>
+ * <p>AsyncTask must be subclassed to be used. The subclass will override at least
+ * one method ({@link #doInBackground}), and most often will override a
+ * second one ({@link #onPostExecute}.)</p>
+ *
+ * <p>Here is an example of subclassing:</p>
+ * <pre class="prettyprint">
+ * private class DownloadFilesTask extends AsyncTask&lt;URL, Integer, Long&gt; {
+ * protected Long doInBackground(URL... urls) {
+ * int count = urls.length;
+ * long totalSize = 0;
+ * for (int i = 0; i < count; i++) {
+ * totalSize += Downloader.downloadFile(urls[i]);
+ * publishProgress((int) ((i / (float) count) * 100));
+ * }
+ * return totalSize;
+ * }
+ *
+ * protected void onProgressUpdate(Integer... progress) {
+ * setProgressPercent(progress[0]);
+ * }
+ *
+ * protected void onPostExecute(Long result) {
+ * showDialog("Downloaded " + result + " bytes");
+ * }
+ * }
+ * </pre>
+ *
+ * <p>Once created, a task is executed very simply:</p>
+ * <pre class="prettyprint">
+ * new DownloadFilesTask().execute(url1, url2, url3);
+ * </pre>
+ *
+ * <h2>AsyncTask's generic types</h2>
+ * <p>The three types used by an asynchronous task are the following:</p>
+ * <ol>
+ * <li><code>Params</code>, the type of the parameters sent to the task upon
+ * execution.</li>
+ * <li><code>Progress</code>, the type of the progress units published during
+ * the background computation.</li>
+ * <li><code>Result</code>, the type of the result of the background
+ * computation.</li>
+ * </ol>
+ * <p>Not all types are always used by am asynchronous task. To mark a type as unused,
+ * simply use the type {@link Void}:</p>
+ * <pre>
+ * private class MyTask extends AsyncTask<Void, Void, Void) { ... }
+ * </pre>
+ *
+ * <h2>The 4 steps</h2>
+ * <p>When an asynchronous task is executed, the task goes through 4 steps:</p>
+ * <ol>
+ * <li>{@link #onPreExecute()}, invoked on the UI thread immediately after the task
+ * is executed. This step is normally used to setup the task, for instance by
+ * showing a progress bar in the user interface.</li>
+ * <li>{@link #doInBackground}, invoked on the background thread
+ * immediately after {@link #onPreExecute()} finishes executing. This step is used
+ * to perform background computation that can take a long time. The parameters
+ * of the asynchronous task are passed to this step. The result of the computation must
+ * be returned by this step and will be passed back to the last step. This step
+ * can also use {@link #publishProgress} to publish one or more units
+ * of progress. These values are published on the UI thread, in the
+ * {@link #onProgressUpdate} step.</li>
+ * <li>{@link #onProgressUpdate}, invoked on the UI thread after a
+ * call to {@link #publishProgress}. The timing of the execution is
+ * undefined. This method is used to display any form of progress in the user
+ * interface while the background computation is still executing. For instance,
+ * it can be used to animate a progress bar or show logs in a text field.</li>
+ * <li>{@link #onPostExecute}, invoked on the UI thread after the background
+ * computation finishes. The result of the background computation is passed to
+ * this step as a parameter.</li>
+ * </ol>
+ *
+ * <h2>Threading rules</h2>
+ * <p>There are a few threading rules that must be followed for this class to
+ * work properly:</p>
+ * <ul>
+ * <li>The task instance must be created on the UI thread.</li>
+ * <li>{@link #execute} must be invoked on the UI thread.</li>
+ * <li>Do not call {@link #onPreExecute()}, {@link #onPostExecute},
+ * {@link #doInBackground}, {@link #onProgressUpdate} manually.</li>
+ * <li>The task can be executed only once (an exception will be thrown if
+ * a second execution is attempted.)</li>
+ * </ul>
+ */
+public abstract class AsyncTask<Params, Progress, Result> {
+ private static final String LOG_TAG = "AsyncTask";
+
+ private static final int CORE_POOL_SIZE = 1;
+ private static final int MAXIMUM_POOL_SIZE = 10;
+ private static final int KEEP_ALIVE = 10;
+
+ private static final BlockingQueue<Runnable> sWorkQueue =
+ new LinkedBlockingQueue<Runnable>(MAXIMUM_POOL_SIZE);
+
+ private static final ThreadFactory sThreadFactory = new ThreadFactory() {
+ private final AtomicInteger mCount = new AtomicInteger(1);
+
+ public Thread newThread(Runnable r) {
+ return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
+ }
+ };
+
+ private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
+ MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
+
+ private static final int MESSAGE_POST_RESULT = 0x1;
+ private static final int MESSAGE_POST_PROGRESS = 0x2;
+ private static final int MESSAGE_POST_CANCEL = 0x3;
+
+ private static final InternalHandler sHandler = new InternalHandler();
+
+ private final WorkerRunnable<Params, Result> mWorker;
+ private final FutureTask<Result> mFuture;
+
+ private volatile Status mStatus = Status.PENDING;
+
+ /**
+ * Indicates the current status of the task. Each status will be set only once
+ * during the lifetime of a task.
+ */
+ public enum Status {
+ /**
+ * Indicates that the task has not been executed yet.
+ */
+ PENDING,
+ /**
+ * Indicates that the task is running.
+ */
+ RUNNING,
+ /**
+ * Indicates that {@link AsyncTask#onPostExecute} has finished.
+ */
+ FINISHED,
+ }
+
+ /**
+ * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
+ */
+ public AsyncTask() {
+ mWorker = new WorkerRunnable<Params, Result>() {
+ public Result call() throws Exception {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ return doInBackground(mParams);
+ }
+ };
+
+ mFuture = new FutureTask<Result>(mWorker) {
+ @Override
+ protected void done() {
+ Message message;
+ Result result = null;
+
+ try {
+ result = get();
+ } catch (InterruptedException e) {
+ android.util.Log.w(LOG_TAG, e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException("An error occured while executing doInBackground()",
+ e.getCause());
+ } catch (CancellationException e) {
+ message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
+ new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
+ message.sendToTarget();
+ return;
+ } catch (Throwable t) {
+ throw new RuntimeException("An error occured while executing "
+ + "doInBackground()", t);
+ }
+
+ message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
+ new AsyncTaskResult<Result>(AsyncTask.this, result));
+ message.sendToTarget();
+ }
+ };
+ }
+
+ /**
+ * Returns the current status of this task.
+ *
+ * @return The current status.
+ */
+ public final Status getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Override this method to perform a computation on a background thread. The
+ * specified parameters are the parameters passed to {@link #execute}
+ * by the caller of this task.
+ *
+ * This method can call {@link #publishProgress} to publish updates
+ * on the UI thread.
+ *
+ * @param params The parameters of the task.
+ *
+ * @return A result, defined by the subclass of this task.
+ *
+ * @see #onPreExecute()
+ * @see #onPostExecute
+ * @see #publishProgress
+ */
+ protected abstract Result doInBackground(Params... params);
+
+ /**
+ * Runs on the UI thread before {@link #doInBackground}.
+ *
+ * @see #onPostExecute
+ * @see #doInBackground
+ */
+ protected void onPreExecute() {
+ }
+
+ /**
+ * Runs on the UI thread after {@link #doInBackground}. The
+ * specified result is the value returned by {@link #doInBackground}
+ * or null if the task was cancelled or an exception occured.
+ *
+ * @param result The result of the operation computed by {@link #doInBackground}.
+ *
+ * @see #onPreExecute
+ * @see #doInBackground
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ protected void onPostExecute(Result result) {
+ }
+
+ /**
+ * Runs on the UI thread after {@link #publishProgress} is invoked.
+ * The specified values are the values passed to {@link #publishProgress}.
+ *
+ * @param values The values indicating progress.
+ *
+ * @see #publishProgress
+ * @see #doInBackground
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ protected void onProgressUpdate(Progress... values) {
+ }
+
+ /**
+ * Runs on the UI thread after {@link #cancel(boolean)} is invoked.
+ *
+ * @see #cancel(boolean)
+ * @see #isCancelled()
+ */
+ protected void onCancelled() {
+ }
+
+ /**
+ * Returns <tt>true</tt> if this task was cancelled before it completed
+ * normally.
+ *
+ * @return <tt>true</tt> if task was cancelled before it completed
+ *
+ * @see #cancel(boolean)
+ */
+ public final boolean isCancelled() {
+ return mFuture.isCancelled();
+ }
+
+ /**
+ * Attempts to cancel execution of this task. This attempt will
+ * fail if the task has already completed, already been cancelled,
+ * or could not be cancelled for some other reason. If successful,
+ * and this task has not started when <tt>cancel</tt> is called,
+ * this task should never run. If the task has already started,
+ * then the <tt>mayInterruptIfRunning</tt> parameter determines
+ * whether the thread executing this task should be interrupted in
+ * an attempt to stop the task.
+ *
+ * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
+ * task should be interrupted; otherwise, in-progress tasks are allowed
+ * to complete.
+ *
+ * @return <tt>false</tt> if the task could not be cancelled,
+ * typically because it has already completed normally;
+ * <tt>true</tt> otherwise
+ *
+ * @see #isCancelled()
+ * @see #onCancelled()
+ */
+ public final boolean cancel(boolean mayInterruptIfRunning) {
+ return mFuture.cancel(mayInterruptIfRunning);
+ }
+
+ /**
+ * Waits if necessary for the computation to complete, and then
+ * retrieves its result.
+ *
+ * @return The computed result.
+ *
+ * @throws CancellationException If the computation was cancelled.
+ * @throws ExecutionException If the computation threw an exception.
+ * @throws InterruptedException If the current thread was interrupted
+ * while waiting.
+ */
+ public final Result get() throws InterruptedException, ExecutionException {
+ return mFuture.get();
+ }
+
+ /**
+ * Waits if necessary for at most the given time for the computation
+ * to complete, and then retrieves its result.
+ *
+ * @param timeout Time to wait before cancelling the operation.
+ * @param unit The time unit for the timeout.
+ *
+ * @return The computed result.
+ *
+ * @throws CancellationException If the computation was cancelled.
+ * @throws ExecutionException If the computation threw an exception.
+ * @throws InterruptedException If the current thread was interrupted
+ * while waiting.
+ * @throws TimeoutException If the wait timed out.
+ */
+ public final Result get(long timeout, TimeUnit unit) throws InterruptedException,
+ ExecutionException, TimeoutException {
+ return mFuture.get(timeout, unit);
+ }
+
+ /**
+ * Executes the task with the specified parameters. The task returns
+ * itself (this) so that the caller can keep a reference to it.
+ *
+ * This method must be invoked on the UI thread.
+ *
+ * @param params The parameters of the task.
+ *
+ * @return This instance of AsyncTask.
+ *
+ * @throws IllegalStateException If {@link #getStatus()} returns either
+ * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
+ */
+ public final AsyncTask<Params, Progress, Result> execute(Params... params) {
+ if (mStatus != Status.PENDING) {
+ switch (mStatus) {
+ case RUNNING:
+ throw new IllegalStateException("Cannot execute task:"
+ + " the task is already running.");
+ case FINISHED:
+ throw new IllegalStateException("Cannot execute task:"
+ + " the task has already been executed "
+ + "(a task can be executed only once)");
+ }
+ }
+
+ mStatus = Status.RUNNING;
+
+ onPreExecute();
+
+ mWorker.mParams = params;
+ sExecutor.execute(mFuture);
+
+ return this;
+ }
+
+ /**
+ * This method can be invoked from {@link #doInBackground} to
+ * publish updates on the UI thread while the background computation is
+ * still running. Each call to this method will trigger the execution of
+ * {@link #onProgressUpdate} on the UI thread.
+ *
+ * @param values The progress values to update the UI with.
+ *
+ * @see #onProgressUpdate
+ * @see #doInBackground
+ */
+ protected final void publishProgress(Progress... values) {
+ sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
+ new AsyncTaskResult<Progress>(this, values)).sendToTarget();
+ }
+
+ private void finish(Result result) {
+ onPostExecute(result);
+ mStatus = Status.FINISHED;
+ }
+
+ private static class InternalHandler extends Handler {
+ @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
+ @Override
+ public void handleMessage(Message msg) {
+ AsyncTaskResult result = (AsyncTaskResult) msg.obj;
+ switch (msg.what) {
+ case MESSAGE_POST_RESULT:
+ // There is only one result
+ result.mTask.finish(result.mData[0]);
+ break;
+ case MESSAGE_POST_PROGRESS:
+ result.mTask.onProgressUpdate(result.mData);
+ break;
+ case MESSAGE_POST_CANCEL:
+ result.mTask.onCancelled();
+ break;
+ }
+ }
+ }
+
+ private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
+ Params[] mParams;
+ }
+
+ @SuppressWarnings({"RawUseOfParameterizedType"})
+ private static class AsyncTaskResult<Data> {
+ final AsyncTask mTask;
+ final Data[] mData;
+
+ AsyncTaskResult(AsyncTask task, Data... data) {
+ mTask = task;
+ mData = data;
+ }
+ }
+}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index bf47555..8f1a756 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -37,8 +37,10 @@ public class BatteryManager {
public static final int BATTERY_HEALTH_OVER_VOLTAGE = 5;
public static final int BATTERY_HEALTH_UNSPECIFIED_FAILURE = 6;
- // values of the "plugged" field in the ACTION_BATTERY_CHANGED intent
+ // values of the "plugged" field in the ACTION_BATTERY_CHANGED intent.
+ // These must be powers of 2.
+ /** Power source is an AC charger. */
public static final int BATTERY_PLUGGED_AC = 1;
+ /** Power source is a USB port. */
public static final int BATTERY_PLUGGED_USB = 2;
-
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
new file mode 100644
index 0000000..e065063
--- /dev/null
+++ b/core/java/android/os/BatteryStats.java
@@ -0,0 +1,518 @@
+package android.os;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Formatter;
+import java.util.Map;
+
+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.
+ */
+public abstract class BatteryStats {
+
+ /**
+ * A constant indicating a partial wake lock.
+ */
+ public static final int WAKE_TYPE_PARTIAL = 0;
+
+ /**
+ * A constant indicating a full wake lock.
+ */
+ public static final int WAKE_TYPE_FULL = 1;
+
+ /**
+ * A constant indicating a window wake lock.
+ */
+ public static final int WAKE_TYPE_WINDOW = 2;
+
+ /**
+ * Include all of the data in the stats, including previously saved data.
+ */
+ public static final int STATS_TOTAL = 0;
+
+ /**
+ * Include only the last run in the stats.
+ */
+ public static final int STATS_LAST = 1;
+
+ /**
+ * Include only the current run in the stats.
+ */
+ public static final int STATS_CURRENT = 2;
+
+ /**
+ * Include only the run since the last time the device was unplugged in the stats.
+ */
+ public static final int STATS_UNPLUGGED = 3;
+
+ private final StringBuilder mFormatBuilder = new StringBuilder(8);
+ private final Formatter mFormatter = new Formatter(mFormatBuilder);
+
+ /**
+ * State for keeping track of timing information.
+ */
+ public static abstract class Timer {
+
+ /**
+ * Returns the count associated with this Timer for the
+ * selected type of statistics.
+ *
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT
+ */
+ public abstract int getCount(int which);
+
+ /**
+ * Returns the total time in microseconds associated with this Timer for the
+ * selected type of statistics.
+ *
+ * @param now system uptime time 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);
+ }
+
+ /**
+ * The statistics associated with a particular uid.
+ */
+ public static abstract class Uid {
+
+ /**
+ * Returns a mapping containing wakelock statistics.
+ *
+ * @return a Map from Strings to Uid.Wakelock objects.
+ */
+ public abstract Map<String, ? extends Wakelock> getWakelockStats();
+
+ /**
+ * The statistics associated with a particular wake lock.
+ */
+ public static abstract class Wakelock {
+ public abstract Timer getWakeTime(int type);
+ }
+
+ /**
+ * Returns a mapping containing sensor statistics.
+ *
+ * @return a Map from Integer sensor ids to Uid.Sensor objects.
+ */
+ public abstract Map<Integer, ? extends Sensor> getSensorStats();
+
+ /**
+ * Returns a mapping containing process statistics.
+ *
+ * @return a Map from Strings to Uid.Proc objects.
+ */
+ public abstract Map<String, ? extends Proc> getProcessStats();
+
+ /**
+ * Returns a mapping containing package statistics.
+ *
+ * @return a Map from Strings to Uid.Pkg objects.
+ */
+ public abstract Map<String, ? extends Pkg> getPackageStats();
+
+ public static abstract class Sensor {
+ public abstract Timer getSensorTime();
+ }
+
+ /**
+ * The statistics associated with a particular process.
+ */
+ public static abstract class Proc {
+
+ /**
+ * Returns the total time (in 1/100 sec) spent executing in user code.
+ *
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ */
+ public abstract long getUserTime(int which);
+
+ /**
+ * Returns the total time (in 1/100 sec) spent executing in system code.
+ *
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ */
+ public abstract long getSystemTime(int which);
+
+ /**
+ * Returns the number of times the process has been started.
+ *
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ */
+ public abstract int getStarts(int which);
+ }
+
+ /**
+ * The statistics associated with a particular package.
+ */
+ public static abstract class Pkg {
+
+ /**
+ * Returns the number of times this package has done something that could wake up the
+ * device from sleep.
+ *
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ */
+ public abstract int getWakeups(int which);
+
+ /**
+ * Returns a mapping containing service statistics.
+ */
+ public abstract Map<String, ? extends Serv> getServiceStats();
+
+ /**
+ * The statistics associated with a particular service.
+ */
+ public abstract class Serv {
+
+ /**
+ * Returns the amount of time spent started.
+ *
+ * @param now elapsed realtime in microseconds.
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ * @return
+ */
+ public abstract long getStartTime(long now, int which);
+
+ /**
+ * Returns the total number of times startService() has been called.
+ *
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ */
+ public abstract int getStarts(int which);
+
+ /**
+ * Returns the total number times the service has been launched.
+ *
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ */
+ public abstract int getLaunches(int which);
+ }
+ }
+ }
+
+ /**
+ * Returns the number of times the device has been started.
+ */
+ public abstract int getStartCount();
+
+ /**
+ * Returns a SparseArray containing the statistics for each uid.
+ */
+ public abstract SparseArray<? extends Uid> getUidStats();
+
+ /**
+ * Returns the current battery uptime in microseconds.
+ *
+ * @param curTime the amount of elapsed realtime in microseconds.
+ */
+ public abstract long getBatteryUptime(long curTime);
+
+ /**
+ * Returns the current battery realtime in microseconds.
+ *
+ * @param curTime the amount of elapsed realtime in microseconds.
+ */
+ public abstract long getBatteryRealtime(long curTime);
+
+ /**
+ * Returns the total, last, or current battery uptime in microseconds.
+ *
+ * @param curTime the elapsed realtime in microseconds.
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ */
+ public abstract long computeBatteryUptime(long curTime, int which);
+
+ /**
+ * Returns the total, last, or current battery realtime in microseconds.
+ *
+ * @param curTime the current elapsed realtime in microseconds.
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ */
+ public abstract long computeBatteryRealtime(long curTime, int which);
+
+ /**
+ * Returns the total, last, or current uptime in micropeconds.
+ *
+ * @param curTime the current elapsed realtime in microseconds.
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ */
+ public abstract long computeUptime(long curTime, int which);
+
+ /**
+ * Returns the total, last, or current realtime in microseconds.
+ * *
+ * @param curTime the current elapsed realtime in microseconds.
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ */
+ public abstract long computeRealtime(long curTime, int which);
+
+ private final static void formatTime(StringBuilder out, long seconds) {
+ long days = seconds / (60 * 60 * 24);
+ if (days != 0) {
+ out.append(days);
+ out.append("d ");
+ }
+ long used = days * 60 * 60 * 24;
+
+ long hours = (seconds - used) / (60 * 60);
+ if (hours != 0 || used != 0) {
+ out.append(hours);
+ out.append("h ");
+ }
+ used += hours * 60 * 60;
+
+ long mins = (seconds-used) / 60;
+ if (mins != 0 || used != 0) {
+ out.append(mins);
+ out.append("m ");
+ }
+ used += mins * 60;
+
+ if (seconds != 0 || used != 0) {
+ out.append(seconds-used);
+ out.append("s ");
+ }
+ }
+
+ private final static String formatTime(long time) {
+ long sec = time / 100;
+ StringBuilder sb = new StringBuilder();
+ formatTime(sb, sec);
+ sb.append((time - (sec * 100)) * 10);
+ sb.append("ms ");
+ return sb.toString();
+ }
+
+ private final static String formatTimeMs(long time) {
+ long sec = time / 1000;
+ StringBuilder sb = new StringBuilder();
+ formatTime(sb, sec);
+ sb.append(time - (sec * 1000));
+ sb.append("ms ");
+ return sb.toString();
+ }
+
+ private final String formatRatioLocked(long num, long den) {
+ float perc = ((float)num) / ((float)den) * 100;
+ mFormatBuilder.setLength(0);
+ mFormatter.format("%.1f%%", perc);
+ return mFormatBuilder.toString();
+ }
+
+ /**
+ *
+ * @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 final String printWakeLock(StringBuilder sb, Timer timer, long now,
+ String name, int which, String linePrefix) {
+ if (timer != null) {
+ // Convert from microseconds to milliseconds with rounding
+ long totalTimeMillis = (timer.getTotalTime(now, which) + 500) / 1000;
+ int count = timer.getCount(which);
+ if (totalTimeMillis != 0) {
+ sb.append(linePrefix);
+ sb.append(formatTimeMs(totalTimeMillis));
+ sb.append(name);
+ sb.append(' ');
+ sb.append('(');
+ sb.append(count);
+ sb.append(" times)");
+ return ", ";
+ }
+ }
+ return linePrefix;
+ }
+
+ @SuppressWarnings("unused")
+ private final void dumpLocked(FileDescriptor fd, PrintWriter pw, String prefix, int which) {
+ long uSecTime = SystemClock.elapsedRealtime() * 1000;
+ final long uSecNow = getBatteryUptime(uSecTime);
+
+ 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:");
+ }
+ long batteryUptime = computeBatteryUptime(uSecNow, which);
+ long batteryRealtime = computeBatteryRealtime(getBatteryRealtime(uSecTime), which);
+ long elapsedRealtime = computeRealtime(uSecTime, which);
+ pw.println(prefix
+ + " On battery: " + formatTimeMs(batteryUptime) + "("
+ + formatRatioLocked(batteryUptime, batteryRealtime)
+ + ") uptime, "
+ + formatTimeMs(batteryRealtime) + "("
+ + formatRatioLocked(batteryRealtime, elapsedRealtime)
+ + ") realtime");
+ pw.println(prefix
+ + " Total: "
+ + formatTimeMs(computeUptime(SystemClock.uptimeMillis() * 1000, which))
+ + "uptime, "
+ + formatTimeMs(elapsedRealtime)
+ + "realtime");
+
+ pw.println(" ");
+
+ 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);
+ pw.println(prefix + " #" + uid + ":");
+ boolean uidActivity = false;
+
+ 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);
+ sb.append(prefix);
+ sb.append(" Wake lock ");
+ sb.append(ent.getKey());
+ linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_FULL), uSecNow,
+ "full", which, linePrefix);
+ linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), uSecNow,
+ "partial", which, linePrefix);
+ linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), uSecNow,
+ "window", which, linePrefix);
+ if (linePrefix.equals(": ")) {
+ sb.append(": (nothing executed)");
+ }
+ pw.println(sb.toString());
+ uidActivity = true;
+ }
+ }
+
+ 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();
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Sensor ");
+ sb.append(sensorNumber);
+
+ Timer timer = se.getSensorTime();
+ if (timer != null) {
+ // Convert from microseconds to milliseconds with rounding
+ long totalTime = (timer.getTotalTime(uSecNow, which) + 500) / 1000;
+ int count = timer.getCount(which);
+ if (totalTime != 0) {
+ sb.append(": ");
+ sb.append(formatTimeMs(totalTime));
+ sb.append(' ');
+ sb.append('(');
+ sb.append(count);
+ sb.append(" times)");
+ }
+ } else {
+ sb.append(": (none used)");
+ }
+
+ pw.println(sb.toString());
+ uidActivity = true;
+ }
+ }
+
+ 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;
+ long systemTime;
+ int starts;
+
+ userTime = ps.getUserTime(which);
+ systemTime = ps.getSystemTime(which);
+ starts = ps.getStarts(which);
+
+ if (userTime != 0 || systemTime != 0 || starts != 0) {
+ pw.println(prefix + " Proc " + ent.getKey() + ":");
+ pw.println(prefix + " CPU: " + formatTime(userTime) + "user + "
+ + formatTime(systemTime) + "kernel");
+ pw.println(prefix + " " + starts + " process starts");
+ uidActivity = true;
+ }
+ }
+ }
+
+ Map<String, ? extends BatteryStats.Uid.Pkg> packageStats = u.getPackageStats();
+ if (packageStats.size() > 0) {
+ for (Map.Entry<String, ? extends BatteryStats.Uid.Pkg> ent
+ : packageStats.entrySet()) {
+ pw.println(prefix + " Apk " + ent.getKey() + ":");
+ boolean apkActivity = false;
+ Uid.Pkg ps = ent.getValue();
+ int wakeups = ps.getWakeups(which);
+ if (wakeups != 0) {
+ pw.println(prefix + " " + wakeups + " wakeup alarms");
+ apkActivity = true;
+ }
+ Map<String, ? extends Uid.Pkg.Serv> serviceStats = ps.getServiceStats();
+ if (serviceStats.size() > 0) {
+ 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);
+ 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));
+ pw.println(prefix + " Starts: " + starts
+ + ", launches: " + launches);
+ apkActivity = true;
+ }
+ }
+ }
+ if (!apkActivity) {
+ pw.println(prefix + " (nothing executed)");
+ }
+ uidActivity = true;
+ }
+ }
+ if (!uidActivity) {
+ pw.println(prefix + " (nothing executed)");
+ }
+ }
+ }
+
+ /**
+ * 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.
+ */
+ @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);
+ }
+ }
+}
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index b9b2773..c3bb967 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -117,6 +117,10 @@ public final class Debug
* waitForDebugger() call if you want to start tracing immediately.
*/
public static void waitForDebugger() {
+ if (!VMDebug.isDebuggingEnabled()) {
+ //System.out.println("debugging not enabled, not waiting");
+ return;
+ }
if (isDebuggerConnected())
return;
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index b6f38d9..2a32e54 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -54,7 +54,7 @@ import java.lang.reflect.Modifier;
* <p>When a
* process is created for your application, its main thread is dedicated to
* running a message queue that takes care of managing the top-level
- * application objects (activities, intent receivers, etc) and any windows
+ * application objects (activities, broadcast receivers, etc) and any windows
* they create. You can create your own threads, and communicate back with
* the main application thread through a Handler. This is done by calling
* the same <em>post</em> or <em>sendMessage</em> methods as before, but from
@@ -71,20 +71,31 @@ public class Handler {
private static final String TAG = "Handler";
/**
+ * Callback interface you can use when instantiating a Handler to avoid
+ * having to implement your own subclass of Handler.
+ */
+ public interface Callback {
+ public boolean handleMessage(Message msg);
+ }
+
+ /**
* Subclasses must implement this to receive messages.
*/
- public void handleMessage(Message msg)
- {
+ public void handleMessage(Message msg) {
}
/**
* Handle system messages here.
*/
- public void dispatchMessage(Message msg)
- {
+ public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
+ if (mCallback != null) {
+ if (mCallback.handleMessage(msg)) {
+ return;
+ }
+ }
handleMessage(msg);
}
}
@@ -95,8 +106,31 @@ public class Handler {
*
* If there isn't one, this handler won't be able to receive messages.
*/
- public Handler()
- {
+ public Handler() {
+ if (FIND_POTENTIAL_LEAKS) {
+ final Class<? extends Handler> klass = getClass();
+ if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
+ (klass.getModifiers() & Modifier.STATIC) == 0) {
+ Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
+ klass.getCanonicalName());
+ }
+ }
+
+ mLooper = Looper.myLooper();
+ if (mLooper == null) {
+ throw new RuntimeException(
+ "Can't create handler inside thread that has not called Looper.prepare()");
+ }
+ mQueue = mLooper.mQueue;
+ mCallback = null;
+ }
+
+ /**
+ * Constructor associates this handler with the queue for the
+ * current thread and takes a callback interface in which you can handle
+ * messages.
+ */
+ public Handler(Callback callback) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
@@ -112,15 +146,26 @@ public class Handler {
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
+ mCallback = callback;
}
/**
* Use the provided queue instead of the default one.
*/
- public Handler(Looper looper)
- {
+ public Handler(Looper looper) {
+ mLooper = looper;
+ mQueue = looper.mQueue;
+ mCallback = null;
+ }
+
+ /**
+ * Use the provided queue instead of the default one and take a callback
+ * interface in which to handle messages.
+ */
+ public Handler(Looper looper, Callback callback) {
mLooper = looper;
mQueue = looper.mQueue;
+ mCallback = callback;
}
/**
@@ -544,5 +589,6 @@ public class Handler {
final MessageQueue mQueue;
final Looper mLooper;
+ final Callback mCallback;
IMessenger mMessenger;
}
diff --git a/core/java/android/os/ICheckinService.aidl b/core/java/android/os/ICheckinService.aidl
index aa43852..70ad28e 100644
--- a/core/java/android/os/ICheckinService.aidl
+++ b/core/java/android/os/ICheckinService.aidl
@@ -39,5 +39,6 @@ interface ICheckinService {
* Determine if the device is under parental control. Return null if
* we are unable to check the parental control status.
*/
- void getParentalControlState(IParentalControlCallback p);
+ void getParentalControlState(IParentalControlCallback p,
+ String requestingApp);
}
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 9d05917..abc1e2f 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -26,6 +26,6 @@ interface IPowerManager
void userActivity(long when, boolean noChangeLights);
void userActivityWithForce(long when, boolean noChangeLights, boolean force);
void setPokeLock(int pokey, IBinder lock, String tag);
- void setStayOnSetting(boolean val);
+ void setStayOnSetting(int val);
long getScreenOnTime();
}
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 80b68e2..9581893 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -130,7 +130,8 @@ public class Looper {
}
/**
- * Return the Looper object associated with the current thread.
+ * Return the Looper object associated with the current thread. Returns
+ * null if the calling thread is not associated with a Looper.
*/
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
@@ -151,7 +152,8 @@ public class Looper {
/**
* Return the {@link MessageQueue} object associated with the current
- * thread.
+ * thread. This must be called from a thread running a Looper, or a
+ * NullPointerException will be thrown.
*/
public static final MessageQueue myQueue() {
return myLooper().mQueue;
@@ -171,6 +173,16 @@ public class Looper {
mQueue.enqueueMessage(msg, 0);
}
+ /**
+ * Return the Thread associated with this Looper.
+ *
+ * @since CURRENT
+ * {@hide pending API Council approval}
+ */
+ public Thread getThread() {
+ return mThread;
+ }
+
public void dump(Printer pw, String prefix) {
pw.println(prefix + this);
pw.println(prefix + "mRun=" + mRun);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 2be7768..cd86fbe 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -19,6 +19,7 @@ package android.os;
import android.net.LocalSocketAddress;
import android.net.LocalSocket;
import android.util.Log;
+import dalvik.system.Zygote;
import java.io.BufferedWriter;
import java.io.DataInputStream;
@@ -221,13 +222,13 @@ public class Process {
public static final int start(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
- boolean enableDebugger,
+ int debugFlags,
String[] zygoteArgs)
{
if (supportsProcesses()) {
try {
return startViaZygote(processClass, niceName, uid, gid, gids,
- enableDebugger, zygoteArgs);
+ debugFlags, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
@@ -259,9 +260,9 @@ public class Process {
* {@hide}
*/
public static final int start(String processClass, int uid, int gid,
- int[] gids, boolean enableDebugger, String[] zygoteArgs) {
+ int[] gids, int debugFlags, String[] zygoteArgs) {
return start(processClass, "", uid, gid, gids,
- enableDebugger, zygoteArgs);
+ debugFlags, zygoteArgs);
}
private static void invokeStaticMain(String className) {
@@ -452,7 +453,7 @@ public class Process {
final String niceName,
final int uid, final int gid,
final int[] gids,
- boolean enableDebugger,
+ int debugFlags,
String[] extraArgs)
throws ZygoteStartFailedEx {
int pid;
@@ -465,9 +466,15 @@ public class Process {
argsForZygote.add("--runtime-init");
argsForZygote.add("--setuid=" + uid);
argsForZygote.add("--setgid=" + gid);
- if (enableDebugger) {
+ if ((debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) != 0) {
argsForZygote.add("--enable-debugger");
}
+ if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
+ argsForZygote.add("--enable-checkjni");
+ }
+ if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
+ argsForZygote.add("--enable-assert");
+ }
//TODO optionally enable debuger
//argsForZygote.add("--enable-debugger");
@@ -529,7 +536,12 @@ public class Process {
public static final native int myTid();
/**
- * Returns the UID assigned to a partlicular user name, or -1 if there is
+ * Returns the identifier of this process's user.
+ */
+ public static final native int myUid();
+
+ /**
+ * Returns the UID assigned to a particular user name, or -1 if there is
* none. If the given string consists of only numbers, it is converted
* directly to a uid.
*/
diff --git a/core/java/android/pim/EventRecurrence.java b/core/java/android/pim/EventRecurrence.java
index ad671f6..edf69ee 100644
--- a/core/java/android/pim/EventRecurrence.java
+++ b/core/java/android/pim/EventRecurrence.java
@@ -18,6 +18,7 @@ package android.pim;
import android.content.res.Resources;
import android.text.TextUtils;
+import android.text.format.Time;
import java.util.Calendar;
diff --git a/core/java/android/pim/RecurrenceSet.java b/core/java/android/pim/RecurrenceSet.java
index c02ff52..c6615da 100644
--- a/core/java/android/pim/RecurrenceSet.java
+++ b/core/java/android/pim/RecurrenceSet.java
@@ -21,6 +21,7 @@ import android.database.Cursor;
import android.os.Bundle;
import android.provider.Calendar;
import android.text.TextUtils;
+import android.text.format.Time;
import android.util.Config;
import android.util.Log;
@@ -145,7 +146,7 @@ public class RecurrenceSet {
long[] dates = new long[n];
for (int i = 0; i<n; ++i) {
// The timezone is updated to UTC if the time string specified 'Z'.
- time.parse2445(rawDates[i]);
+ time.parse(rawDates[i]);
dates[i] = time.toMillis(false /* use isDst */);
time.timezone = tz;
}
@@ -173,7 +174,7 @@ public class RecurrenceSet {
// NOTE: the timezone may be null, if this is a floating time.
String tzid = tzidParam == null ? null : tzidParam.value;
Time start = new Time(tzidParam == null ? Time.TIMEZONE_UTC : tzid);
- boolean inUtc = start.parse2445(dtstart);
+ boolean inUtc = start.parse(dtstart);
boolean allDay = start.allDay;
if (inUtc) {
@@ -350,7 +351,7 @@ public class RecurrenceSet {
? start.timezone : endTzidParameter.value;
Time end = new Time(endTzid);
- end.parse2445(dtendProperty.getValue());
+ end.parse(dtendProperty.getValue());
long durationMillis = end.toMillis(false /* use isDst */)
- start.toMillis(false /* use isDst */);
long durationSeconds = (durationMillis / 1000);
diff --git a/core/java/android/preference/CheckBoxPreference.java b/core/java/android/preference/CheckBoxPreference.java
index 4bebf87..1e9b7ae 100644
--- a/core/java/android/preference/CheckBoxPreference.java
+++ b/core/java/android/preference/CheckBoxPreference.java
@@ -27,7 +27,7 @@ import android.widget.Checkable;
import android.widget.TextView;
/**
- * The {@link CheckBoxPreference} is a preference that provides checkbox widget
+ * A {@link Preference} that provides checkbox widget
* functionality.
* <p>
* This preference will store a boolean into the SharedPreferences.
diff --git a/core/java/android/preference/DialogPreference.java b/core/java/android/preference/DialogPreference.java
index 3eb65e2..666efae 100644
--- a/core/java/android/preference/DialogPreference.java
+++ b/core/java/android/preference/DialogPreference.java
@@ -34,7 +34,7 @@ import android.view.View;
import android.widget.TextView;
/**
- * The {@link DialogPreference} class is a base class for preferences that are
+ * A base class for {@link Preference} objects that are
* dialog-based. These preferences will, when clicked, open a dialog showing the
* actual preference controls.
*
@@ -356,7 +356,7 @@ public abstract class DialogPreference extends Preference implements
getPreferenceManager().unregisterOnActivityDestroyListener(this);
mDialog = null;
- onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON1);
+ onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
}
/**
@@ -370,6 +370,15 @@ public abstract class DialogPreference extends Preference implements
}
/**
+ * Gets the dialog that is shown by this preference.
+ *
+ * @return The dialog, or null if a dialog is not being shown.
+ */
+ public Dialog getDialog() {
+ return mDialog;
+ }
+
+ /**
* {@inheritDoc}
*/
public void onActivityDestroy() {
diff --git a/core/java/android/preference/EditTextPreference.java b/core/java/android/preference/EditTextPreference.java
index be56003..a12704f 100644
--- a/core/java/android/preference/EditTextPreference.java
+++ b/core/java/android/preference/EditTextPreference.java
@@ -31,7 +31,7 @@ import android.widget.EditText;
import android.widget.LinearLayout;
/**
- * The {@link EditTextPreference} class is a preference that allows for string
+ * A {@link Preference} that allows for string
* input.
* <p>
* It is a subclass of {@link DialogPreference} and shows the {@link EditText}
diff --git a/core/java/android/preference/ListPreference.java b/core/java/android/preference/ListPreference.java
index 6c98ded..f842d75 100644
--- a/core/java/android/preference/ListPreference.java
+++ b/core/java/android/preference/ListPreference.java
@@ -26,7 +26,7 @@ import android.os.Parcelable;
import android.util.AttributeSet;
/**
- * The {@link ListPreference} is a preference that displays a list of entries as
+ * A {@link Preference} that displays a list of entries as
* a dialog.
* <p>
* This preference will store a string into the SharedPreferences. This string will be the value
@@ -192,8 +192,22 @@ public class ListPreference extends DialogPreference {
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mClickedDialogEntryIndex = which;
+
+ /*
+ * Clicking on an item simulates the positive button
+ * click, and dismisses the dialog.
+ */
+ ListPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
+ dialog.dismiss();
}
});
+
+ /*
+ * The typical interaction for list-based dialogs is to have
+ * click-on-an-item dismiss the dialog instead of the user having to
+ * press 'Ok'.
+ */
+ builder.setPositiveButton(null, null);
}
@Override
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 1db7525..3820f28 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -37,13 +37,13 @@ import android.widget.ListView;
import android.widget.TextView;
/**
- * The {@link Preference} class represents the basic preference UI building
- * block that is displayed by a {@link PreferenceActivity} in the form of a
+ * Represents the basic Preference UI building
+ * block displayed by a {@link PreferenceActivity} in the form of a
* {@link ListView}. This class provides the {@link View} to be displayed in
* the activity and associates with a {@link SharedPreferences} to
* store/retrieve the preference data.
* <p>
- * When specifying a preference hierarchy in XML, each tag name can point to a
+ * When specifying a preference hierarchy in XML, each element can point to a
* subclass of {@link Preference}, similar to the view hierarchy and layouts.
* <p>
* This class contains a {@code key} that will be used as the key into the
@@ -109,46 +109,46 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
private boolean mBaseMethodCalled;
/**
- * Interface definition for a callback to be invoked when this
- * {@link Preference Preference's} value has been changed by the user and is
+ * Interface definition for a callback to be invoked when the value of this
+ * {@link Preference} has been changed by the user and is
* about to be set and/or persisted. This gives the client a chance
* to prevent setting and/or persisting the value.
*/
public interface OnPreferenceChangeListener {
/**
- * Called when this preference has been changed by the user. This is
- * called before the preference's state is about to be updated and
+ * Called when a Preference has been changed by the user. This is
+ * called before the state of the Preference is about to be updated and
* before the state is persisted.
*
- * @param preference This preference.
- * @param newValue The new value of the preference.
- * @return Whether or not to update this preference's state with the new value.
+ * @param preference The changed Preference.
+ * @param newValue The new value of the Preference.
+ * @return True to update the state of the Preference with the new value.
*/
boolean onPreferenceChange(Preference preference, Object newValue);
}
/**
- * Interface definition for a callback to be invoked when a preference is
+ * Interface definition for a callback to be invoked when a {@link Preference} is
* clicked.
*/
public interface OnPreferenceClickListener {
/**
- * Called when a preference has been clicked.
+ * Called when a Preference has been clicked.
*
- * @param preference The preference that was clicked.
- * @return Whether the click was handled.
+ * @param preference The Preference that was clicked.
+ * @return True if the click was handled.
*/
boolean onPreferenceClick(Preference preference);
}
/**
* Interface definition for a callback to be invoked when this
- * {@link Preference} is changed or if this is a group, there is an
+ * {@link Preference} is changed or, if this is a group, there is an
* addition/removal of {@link Preference}(s). This is used internally.
*/
interface OnPreferenceChangeInternalListener {
/**
- * Called when this preference has changed.
+ * Called when this Preference has changed.
*
* @param preference This preference.
*/
@@ -157,18 +157,18 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
/**
* Called when this group has added/removed {@link Preference}(s).
*
- * @param preference This preference.
+ * @param preference This Preference.
*/
void onPreferenceHierarchyChange(Preference preference);
}
/**
* Perform inflation from XML and apply a class-specific base style. This
- * constructor of {@link Preference} allows subclasses to use their own base
- * style when they are inflating. For example, a {@link CheckBoxPreference}'s
- * constructor would call this version of the super class constructor and
- * supply {@code android.R.attr.checkBoxPreferenceStyle} for <var>defStyle</var>;
- * this allows the theme's checkbox preference style to modify all of the base
+ * constructor of Preference allows subclasses to use their own base
+ * style when they are inflating. For example, a {@link CheckBoxPreference}
+ * constructor calls this version of the super class constructor and
+ * supplies {@code android.R.attr.checkBoxPreferenceStyle} for <var>defStyle</var>.
+ * This allows the theme's checkbox preference style to modify all of the base
* preference attributes as well as the {@link CheckBoxPreference} class's
* attributes.
*
@@ -254,8 +254,8 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Constructor that is called when inflating a preference from XML. This is
- * called when a preference is being constructed from an XML file, supplying
+ * Constructor that is called when inflating a Preference from XML. This is
+ * called when a Preference is being constructed from an XML file, supplying
* attributes that were specified in the XML file. This version uses a
* default style of 0, so the only attribute values applied are those in the
* Context's Theme and the given AttributeSet.
@@ -274,15 +274,15 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
/**
* Constructor to create a Preference.
*
- * @param context The context to store preference values.
+ * @param context The Context in which to store Preference values.
*/
public Preference(Context context) {
this(context, null);
}
/**
- * Called when {@link Preference} is being inflated and the default value
- * attribute needs to be read. Since different preference types have
+ * Called when a Preference is being inflated and the default value
+ * attribute needs to be read. Since different Preference types have
* different value types, the subclass should get and return the default
* value which will be its value type.
* <p>
@@ -299,16 +299,16 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
/**
* Sets an {@link Intent} to be used for
- * {@link Context#startActivity(Intent)} when the preference is clicked.
+ * {@link Context#startActivity(Intent)} when this Preference is clicked.
*
- * @param intent The intent associated with the preference.
+ * @param intent The intent associated with this Preference.
*/
public void setIntent(Intent intent) {
mIntent = intent;
}
/**
- * Return the {@link Intent} associated with this preference.
+ * Return the {@link Intent} associated with this Preference.
*
* @return The {@link Intent} last set via {@link #setIntent(Intent)} or XML.
*/
@@ -318,12 +318,12 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
/**
* Sets the layout resource that is inflated as the {@link View} to be shown
- * for this preference. In most cases, the default layout is sufficient for
- * custom preferences and only the widget layout needs to be changed.
+ * for this Preference. In most cases, the default layout is sufficient for
+ * custom Preference objects and only the widget layout needs to be changed.
* <p>
* This layout should contain a {@link ViewGroup} with ID
* {@link android.R.id#widget_frame} to be the parent of the specific widget
- * for this preference. It should similarly contain
+ * for this Preference. It should similarly contain
* {@link android.R.id#title} and {@link android.R.id#summary}.
*
* @param layoutResId The layout resource ID to be inflated and returned as
@@ -340,7 +340,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Gets the layout resource that will be shown as the {@link View} for this preference.
+ * Gets the layout resource that will be shown as the {@link View} for this Preference.
*
* @return The layout resource ID.
*/
@@ -349,8 +349,8 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Sets The layout for the controllable widget portion of a preference. This
- * is inflated into the main layout. For example, a checkbox preference
+ * Sets The layout for the controllable widget portion of this Preference. This
+ * is inflated into the main layout. For example, a {@link CheckBoxPreference}
* would specify a custom layout (consisting of just the CheckBox) here,
* instead of creating its own main layout.
*
@@ -363,7 +363,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Gets the layout resource for the controllable widget portion of a preference.
+ * Gets the layout resource for the controllable widget portion of this Preference.
*
* @return The layout resource ID.
*/
@@ -374,11 +374,11 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
/**
* Gets the View that will be shown in the {@link PreferenceActivity}.
*
- * @param convertView The old view to reuse, if possible. Note: You should
- * check that this view is non-null and of an appropriate type
- * before using. If it is not possible to convert this view to
- * display the correct data, this method can create a new view.
- * @param parent The parent that this view will eventually be attached to.
+ * @param convertView The old View to reuse, if possible. Note: You should
+ * check that this View is non-null and of an appropriate type
+ * before using. If it is not possible to convert this View to
+ * display the correct data, this method can create a new View.
+ * @param parent The parent that this View will eventually be attached to.
* @return Returns the same Preference object, for chaining multiple calls
* into a single statement.
* @see #onCreateView(ViewGroup)
@@ -393,16 +393,16 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Creates the View to be shown for this preference in the
+ * Creates the View to be shown for this Preference in the
* {@link PreferenceActivity}. The default behavior is to inflate the main
- * layout of this preference (see {@link #setLayoutResource(int)}. If
+ * layout of this Preference (see {@link #setLayoutResource(int)}. If
* changing this behavior, please specify a {@link ViewGroup} with ID
* {@link android.R.id#widget_frame}.
* <p>
* Make sure to call through to the superclass's implementation.
*
- * @param parent The parent that this view will eventually be attached to.
- * @return The View that displays this preference.
+ * @param parent The parent that this View will eventually be attached to.
+ * @return The View that displays this Preference.
* @see #onBindView(View)
*/
protected View onCreateView(ViewGroup parent) {
@@ -420,14 +420,14 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Binds the created View to the data for the preference.
+ * Binds the created View to the data for this Preference.
* <p>
* This is a good place to grab references to custom Views in the layout and
* set properties on them.
* <p>
* Make sure to call through to the superclass's implementation.
*
- * @param view The View that shows this preference.
+ * @param view The View that shows this Preference.
* @see #onCreateView(ViewGroup)
*/
protected void onBindView(View view) {
@@ -453,7 +453,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
if (mShouldDisableView) {
- setEnabledStateOnViews(view, mEnabled);
+ setEnabledStateOnViews(view, isEnabled());
}
}
@@ -472,13 +472,13 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Sets the order of this {@link Preference} with respect to other
- * {@link Preference} on the same level. If this is not specified, the
+ * Sets the order of this Preference with respect to other
+ * Preference objects on the same level. If this is not specified, the
* default behavior is to sort alphabetically. The
* {@link PreferenceGroup#setOrderingAsAdded(boolean)} can be used to order
- * preferences based on the order they appear in the XML.
+ * Preference objects based on the order they appear in the XML.
*
- * @param order The order for this preference. A lower value will be shown
+ * @param order The order for this Preference. A lower value will be shown
* first. Use {@link #DEFAULT_ORDER} to sort alphabetically or
* allow ordering from XML.
* @see PreferenceGroup#setOrderingAsAdded(boolean)
@@ -494,9 +494,10 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Gets the order of this {@link Preference}.
+ * Gets the order of this Preference with respect to other Preference objects
+ * on the same level.
*
- * @return The order of this {@link Preference}.
+ * @return The order of this Preference.
* @see #setOrder(int)
*/
public int getOrder() {
@@ -504,11 +505,12 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Sets the title for the preference. This title will be placed into the ID
+ * Sets the title for this Preference with a CharSequence.
+ * This title will be placed into the ID
* {@link android.R.id#title} within the View created by
* {@link #onCreateView(ViewGroup)}.
*
- * @param title The title of the preference.
+ * @param title The title for this Preference.
*/
public void setTitle(CharSequence title) {
if (title == null && mTitle != null || title != null && !title.equals(mTitle)) {
@@ -518,6 +520,8 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
+ * Sets the title for this Preference with a resource ID.
+ *
* @see #setTitle(CharSequence)
* @param titleResId The title as a resource ID.
*/
@@ -526,7 +530,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Returns the title of the preference.
+ * Returns the title of this Preference.
*
* @return The title.
* @see #setTitle(CharSequence)
@@ -536,7 +540,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Returns the summary of the preference.
+ * Returns the summary of this Preference.
*
* @return The summary.
* @see #setSummary(CharSequence)
@@ -546,11 +550,9 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Sets the summary for the preference. This summary will be placed into the
- * ID {@link android.R.id#summary} within the View created by
- * {@link #onCreateView(ViewGroup)}.
+ * Sets the summary for this Preference with a CharSequence.
*
- * @param summary The summary of the preference.
+ * @param summary The summary for the preference.
*/
public void setSummary(CharSequence summary) {
if (summary == null && mSummary != null || summary != null && !summary.equals(mSummary)) {
@@ -560,6 +562,8 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
+ * Sets the summary for this Preference with a resource ID.
+ *
* @see #setSummary(CharSequence)
* @param summaryResId The summary as a resource.
*/
@@ -568,10 +572,10 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Sets whether this preference is enabled. If disabled, the preference will
+ * Sets whether this Preference is enabled. If disabled, it will
* not handle clicks.
*
- * @param enabled Whether the preference is enabled.
+ * @param enabled Set true to enable it.
*/
public void setEnabled(boolean enabled) {
if (mEnabled != enabled) {
@@ -585,18 +589,18 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Whether this {@link Preference} should be enabled in the list.
+ * Checks whether this Preference should be enabled in the list.
*
- * @return Whether this preference is enabled.
+ * @return True if this Preference is enabled, false otherwise.
*/
public boolean isEnabled() {
return mEnabled;
}
/**
- * Sets whether this preference is selectable.
+ * Sets whether this Preference is selectable.
*
- * @param selectable Whether the preference is selectable.
+ * @param selectable Set true to make it selectable.
*/
public void setSelectable(boolean selectable) {
if (mSelectable != selectable) {
@@ -606,23 +610,23 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Whether this {@link Preference} should be selectable in the list.
+ * Checks whether this Preference should be selectable in the list.
*
- * @return Whether this preference is selectable.
+ * @return True if it is selectable, false otherwise.
*/
public boolean isSelectable() {
return mSelectable;
}
/**
- * Sets whether this {@link Preference} should disable its view when it gets
+ * Sets whether this Preference should disable its view when it gets
* disabled.
* <p>
* For example, set this and {@link #setEnabled(boolean)} to false for
* preferences that are only displaying information and 1) should not be
* clickable 2) should not have the view set to the disabled state.
*
- * @param shouldDisableView Whether this preference should disable its view
+ * @param shouldDisableView Set true if this preference should disable its view
* when the preference is disabled.
*/
public void setShouldDisableView(boolean shouldDisableView) {
@@ -631,18 +635,19 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
+ * Checks whether this Preference should disable its view when it's action is disabled.
* @see #setShouldDisableView(boolean)
- * @return Whether this preference should disable its view when it is disabled.
+ * @return True if it should disable the view.
*/
public boolean getShouldDisableView() {
return mShouldDisableView;
}
/**
- * Returns a unique ID for this preference. This ID should be unique across all
- * preferences in a hierarchy.
+ * Returns a unique ID for this Preference. This ID should be unique across all
+ * Preference objects in a hierarchy.
*
- * @return A unique ID for this preference.
+ * @return A unique ID for this Preference.
*/
long getId() {
return mId;
@@ -658,7 +663,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Sets the key for the preference which is used as a key to the
+ * Sets the key for this Preference, which is used as a key to the
* {@link SharedPreferences}. This should be unique for the package.
*
* @param key The key for the preference.
@@ -673,7 +678,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Gets the key for the preference, which is also the key used for storing
+ * Gets the key for this Preference, which is also the key used for storing
* values into SharedPreferences.
*
* @return The key.
@@ -686,6 +691,8 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* Checks whether the key is present, and if it isn't throws an
* exception. This should be called by subclasses that store preferences in
* the {@link SharedPreferences}.
+ *
+ * @throws IllegalStateException If there is no key assigned.
*/
void requireKey() {
if (mKey == null) {
@@ -696,43 +703,43 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Returns whether this {@link Preference} has a valid key.
+ * Checks whether this Preference has a valid key.
*
- * @return Whether the key exists and is not a blank string.
+ * @return True if the key exists and is not a blank string, false otherwise.
*/
public boolean hasKey() {
return !TextUtils.isEmpty(mKey);
}
/**
- * Returns whether this {@link Preference} is persistent. If it is persistent, it stores its value(s) into
+ * Checks whether this Preference is persistent. If it is, it stores its value(s) into
* the persistent {@link SharedPreferences} storage.
*
- * @return Whether this is persistent.
+ * @return True if it is persistent.
*/
public boolean isPersistent() {
return mPersistent;
}
/**
- * Convenience method of whether at the given time this method is called,
- * the {@link Preference} should store/restore its value(s) into the
- * {@link SharedPreferences}. This, at minimum, checks whether the
- * {@link Preference} is persistent and it currently has a key. Before you
+ * Checks whether, at the given time this method is called,
+ * this Preference should store/restore its value(s) into the
+ * {@link SharedPreferences}. This, at minimum, checks whether this
+ * Preference is persistent and it currently has a key. Before you
* save/restore from the {@link SharedPreferences}, check this first.
*
- * @return Whether to persist the value.
+ * @return True if it should persist the value.
*/
protected boolean shouldPersist() {
return mPreferenceManager != null && isPersistent() && hasKey();
}
/**
- * Sets whether this {@link Preference} is persistent. If it is persistent,
+ * Sets whether this Preference is persistent. When persistent,
* it stores its value(s) into the persistent {@link SharedPreferences}
* storage.
*
- * @param persistent Whether it should store its value(s) into the {@link SharedPreferences}.
+ * @param persistent Set true if it should store its value(s) into the {@link SharedPreferences}.
*/
public void setPersistent(boolean persistent) {
mPersistent = persistent;
@@ -742,8 +749,8 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* Call this method after the user changes the preference, but before the
* internal state is set. This allows the client to ignore the user value.
*
- * @param newValue The new value of the preference.
- * @return Whether or not the user value should be set as the preference
+ * @param newValue The new value of this Preference.
+ * @return True if the user value should be set as the preference
* value (and persisted).
*/
protected boolean callChangeListener(Object newValue) {
@@ -751,7 +758,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Sets the callback to be invoked when this preference is changed by the
+ * Sets the callback to be invoked when this Preference is changed by the
* user (but before the internal state has been updated).
*
* @param onPreferenceChangeListener The callback to be invoked.
@@ -761,7 +768,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Gets the callback to be invoked when this preference is changed by the
+ * Returns the callback to be invoked when this Preference is changed by the
* user (but before the internal state has been updated).
*
* @return The callback to be invoked.
@@ -771,7 +778,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Sets the callback to be invoked when this preference is clicked.
+ * Sets the callback to be invoked when this Preference is clicked.
*
* @param onPreferenceClickListener The callback to be invoked.
*/
@@ -780,7 +787,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Gets the callback to be invoked when this preference is clicked.
+ * Returns the callback to be invoked when this Preference is clicked.
*
* @return The callback to be invoked.
*/
@@ -791,9 +798,9 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
/**
* Called when a click should be performed.
*
- * @param preferenceScreen Optional {@link PreferenceScreen} whose hierarchy click
+ * @param preferenceScreen A {@link PreferenceScreen} whose hierarchy click
* listener should be called in the proper order (between other
- * processing).
+ * processing). May be null.
*/
void performClick(PreferenceScreen preferenceScreen) {
@@ -824,18 +831,19 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Returns the context of this preference. Each preference in a preference hierarchy can be
- * from different context (for example, if multiple activities provide preferences into a single
- * {@link PreferenceActivity}). This context will be used to save the preference valus.
+ * Returns the {@link android.content.Context} of this Preference.
+ * Each Preference in a Preference hierarchy can be
+ * from different Context (for example, if multiple activities provide preferences into a single
+ * {@link PreferenceActivity}). This Context will be used to save the Preference values.
*
- * @return The context of this preference.
+ * @return The Context of this Preference.
*/
public Context getContext() {
return mContext;
}
/**
- * Returns the {@link SharedPreferences} where this preference can read its
+ * Returns the {@link SharedPreferences} where this Preference can read its
* value(s). Usually, it's easier to use one of the helper read methods:
* {@link #getPersistedBoolean(boolean)}, {@link #getPersistedFloat(float)},
* {@link #getPersistedInt(int)}, {@link #getPersistedLong(long)},
@@ -847,8 +855,8 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* {@link SharedPreferences}, this is intended behavior to improve
* performance.
*
- * @return The {@link SharedPreferences} where this preference reads its
- * value(s), or null if it isn't attached to a preference hierarchy.
+ * @return The {@link SharedPreferences} where this Preference reads its
+ * value(s), or null if it isn't attached to a Preference hierarchy.
* @see #getEditor()
*/
public SharedPreferences getSharedPreferences() {
@@ -860,7 +868,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Returns an {@link SharedPreferences.Editor} where this preference can
+ * Returns an {@link SharedPreferences.Editor} where this Preference can
* save its value(s). Usually it's easier to use one of the helper save
* methods: {@link #persistBoolean(boolean)}, {@link #persistFloat(float)},
* {@link #persistInt(int)}, {@link #persistLong(long)},
@@ -869,11 +877,11 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* true, it is this Preference's responsibility to commit.
* <p>
* In some cases, writes to this will not be committed right away and hence
- * not show up in the shared preferences, this is intended behavior to
+ * not show up in the SharedPreferences, this is intended behavior to
* improve performance.
*
* @return A {@link SharedPreferences.Editor} where this preference saves
- * its value(s), or null if it isn't attached to a preference
+ * its value(s), or null if it isn't attached to a Preference
* hierarchy.
* @see #shouldCommit()
* @see #getSharedPreferences()
@@ -903,9 +911,11 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Compares preferences based on order (if set), otherwise alphabetically on title.
- * <p>
- * {@inheritDoc}
+ * Compares Preference objects based on order (if set), otherwise alphabetically on the titles.
+ *
+ * @param another The Preference to compare to this one.
+ * @return 0 if the same; less than 0 if this Preference sorts ahead of <var>another</var>;
+ * greater than 0 if this Preference sorts after <var>another</var>.
*/
public int compareTo(Preference another) {
if (mOrder != DEFAULT_ORDER
@@ -942,7 +952,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Should be called this is a group a {@link Preference} has been
+ * Should be called when a Preference has been
* added/removed from this group, or the ordering should be
* re-evaluated.
*/
@@ -953,7 +963,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Gets the {@link PreferenceManager} that manages this preference's tree.
+ * Gets the {@link PreferenceManager} that manages this Preference object's tree.
*
* @return The {@link PreferenceManager}.
*/
@@ -962,10 +972,10 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Called when this preference has been attached to a preference hierarchy.
+ * Called when this Preference has been attached to a Preference hierarchy.
* Make sure to call the super implementation.
*
- * @param preferenceManager The preference manager of the hierarchy.
+ * @param preferenceManager The PreferenceManager of the hierarchy.
*/
protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
mPreferenceManager = preferenceManager;
@@ -976,9 +986,9 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Called when the preference hierarchy has been attached to the
+ * Called when the Preference hierarchy has been attached to the
* {@link PreferenceActivity}. This can also be called when this
- * {@link Preference} has been attached to a group that was already attached
+ * Preference has been attached to a group that was already attached
* to the {@link PreferenceActivity}.
*/
protected void onAttachedToActivity() {
@@ -1010,15 +1020,14 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Find a Preference in this hierarchy (the whole thing,
+ * Finds a Preference in this hierarchy (the whole thing,
* even above/below your {@link PreferenceScreen} screen break) with the given
* key.
* <p>
* This only functions after we have been attached to a hierarchy.
*
- * @param key The key of the {@link Preference} to find.
- * @return The {@link Preference} object of a preference
- * with the given key.
+ * @param key The key of the Preference to find.
+ * @return The Preference that uses the given key.
*/
protected Preference findPreferenceInHierarchy(String key) {
if (TextUtils.isEmpty(key) || mPreferenceManager == null) {
@@ -1029,13 +1038,13 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Adds a dependent Preference on this preference so we can notify it.
- * Usually, the dependent preference registers itself (it's good for it to
+ * Adds a dependent Preference on this Preference so we can notify it.
+ * Usually, the dependent Preference registers itself (it's good for it to
* know it depends on something), so please use
- * {@link Preference#setDependency(String)} on the dependent preference.
+ * {@link Preference#setDependency(String)} on the dependent Preference.
*
* @param dependent The dependent Preference that will be enabled/disabled
- * according to the state of this preference.
+ * according to the state of this Preference.
*/
private void registerDependent(Preference dependent) {
if (mDependents == null) {
@@ -1048,10 +1057,10 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Removes a dependent Preference on this preference.
+ * Removes a dependent Preference on this Preference.
*
* @param dependent The dependent Preference that will be enabled/disabled
- * according to the state of this preference.
+ * according to the state of this Preference.
* @return Returns the same Preference object, for chaining multiple calls
* into a single statement.
*/
@@ -1065,7 +1074,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* Notifies any listening dependents of a change that affects the
* dependency.
*
- * @param disableDependents Whether this {@link Preference} should disable
+ * @param disableDependents Whether this Preference should disable
* its dependents.
*/
public void notifyDependencyChange(boolean disableDependents) {
@@ -1084,15 +1093,15 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
/**
* Called when the dependency changes.
*
- * @param dependency The preference that this preference depends on.
- * @param disableDependent Whether to disable this preference.
+ * @param dependency The Preference that this Preference depends on.
+ * @param disableDependent Set true to disable this Preference.
*/
public void onDependencyChanged(Preference dependency, boolean disableDependent) {
setEnabled(!disableDependent);
}
/**
- * Should return whether this preference's dependents should currently be
+ * Checks whether this preference's dependents should currently be
* disabled.
*
* @return True if the dependents should be disabled, otherwise false.
@@ -1117,7 +1126,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Returns the key of the dependency on this preference.
+ * Returns the key of the dependency on this Preference.
*
* @return The key of the dependency.
* @see #setDependency(String)
@@ -1136,7 +1145,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Sets the default value for the preference, which will be set either if
+ * Sets the default value for this Preference, which will be set either if
* persistence is off or persistence is on and the preference is not found
* in the persistent storage.
*
@@ -1159,17 +1168,21 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Implement this to set the initial value of the Preference. If the
- * restoreValue flag is true, you should restore the value from the shared
- * preferences. If false, you should set (and possibly store to shared
- * preferences if {@link #shouldPersist()}) to defaultValue.
+ * Implement this to set the initial value of the Preference.
+ * <p>
+ * If <var>restorePersistedValue</var> is true, you should restore the
+ * Preference value from the {@link android.content.SharedPreferences}. If
+ * <var>restorePersistedValue</var> is false, you should set the Preference
+ * value to defaultValue that is given (and possibly store to SharedPreferences
+ * if {@link #shouldPersist()} is true).
* <p>
* This may not always be called. One example is if it should not persist
* but there is no default value given.
*
- * @param restorePersistedValue Whether to restore the persisted value
- * (true), or use the given default value (false).
- * @param defaultValue The default value. Only use if restoreValue is false.
+ * @param restorePersistedValue True to restore the persisted value;
+ * false to use the given <var>defaultValue</var>.
+ * @param defaultValue The default value for this Preference. Only use this
+ * if <var>restorePersistedValue</var> is false.
*/
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
}
@@ -1181,14 +1194,14 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Attempts to persist a String to the SharedPreferences.
+ * Attempts to persist a String to the {@link android.content.SharedPreferences}.
* <p>
- * This will check if the Preference is persistent, get an editor from
- * the preference manager, put the string, check if we should commit (and
+ * This will check if this Preference is persistent, get an editor from
+ * the {@link PreferenceManager}, put in the string, and check if we should commit (and
* commit if so).
*
* @param value The value to persist.
- * @return Whether the Preference is persistent. (This is not whether the
+ * @return True if the Preference is persistent. (This is not whether the
* value was persisted, since we may not necessarily commit if there
* will be a batch commit later.)
* @see #getPersistedString(String)
@@ -1210,15 +1223,15 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Attempts to get a persisted String from the SharedPreferences.
+ * Attempts to get a persisted String from the {@link android.content.SharedPreferences}.
* <p>
- * This will check if the Preference is persistent, get the shared
- * preferences from the preference manager, get the value.
+ * This will check if this Preference is persistent, get the SharedPreferences
+ * from the {@link PreferenceManager}, and get the value.
*
* @param defaultReturnValue The default value to return if either the
* Preference is not persistent or the Preference is not in the
* shared preferences.
- * @return The value from the shared preferences or the default return
+ * @return The value from the SharedPreferences or the default return
* value.
* @see #persistString(String)
*/
@@ -1231,10 +1244,10 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Attempts to persist an int to the SharedPreferences.
+ * Attempts to persist an int to the {@link android.content.SharedPreferences}.
*
* @param value The value to persist.
- * @return Whether the Preference is persistent. (This is not whether the
+ * @return True if the Preference is persistent. (This is not whether the
* value was persisted, since we may not necessarily commit if there
* will be a batch commit later.)
* @see #persistString(String)
@@ -1256,12 +1269,12 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Attempts to get a persisted int from the SharedPreferences.
+ * Attempts to get a persisted int from the {@link android.content.SharedPreferences}.
*
- * @param defaultReturnValue The default value to return if either the
- * Preference is not persistent or the Preference is not in the
- * shared preferences.
- * @return The value from the shared preferences or the default return
+ * @param defaultReturnValue The default value to return if either this
+ * Preference is not persistent or this Preference is not in the
+ * SharedPreferences.
+ * @return The value from the SharedPreferences or the default return
* value.
* @see #getPersistedString(String)
* @see #persistInt(int)
@@ -1275,10 +1288,10 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Attempts to persist a float to the SharedPreferences.
+ * Attempts to persist a float to the {@link android.content.SharedPreferences}.
*
* @param value The value to persist.
- * @return Whether the Preference is persistent. (This is not whether the
+ * @return True if this Preference is persistent. (This is not whether the
* value was persisted, since we may not necessarily commit if there
* will be a batch commit later.)
* @see #persistString(String)
@@ -1300,12 +1313,12 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Attempts to get a persisted float from the SharedPreferences.
+ * Attempts to get a persisted float from the {@link android.content.SharedPreferences}.
*
- * @param defaultReturnValue The default value to return if either the
- * Preference is not persistent or the Preference is not in the
- * shared preferences.
- * @return The value from the shared preferences or the default return
+ * @param defaultReturnValue The default value to return if either this
+ * Preference is not persistent or this Preference is not in the
+ * SharedPreferences.
+ * @return The value from the SharedPreferences or the default return
* value.
* @see #getPersistedString(String)
* @see #persistFloat(float)
@@ -1319,10 +1332,10 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Attempts to persist a long to the SharedPreferences.
+ * Attempts to persist a long to the {@link android.content.SharedPreferences}.
*
* @param value The value to persist.
- * @return Whether the Preference is persistent. (This is not whether the
+ * @return True if this Preference is persistent. (This is not whether the
* value was persisted, since we may not necessarily commit if there
* will be a batch commit later.)
* @see #persistString(String)
@@ -1344,12 +1357,12 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Attempts to get a persisted long from the SharedPreferences.
+ * Attempts to get a persisted long from the {@link android.content.SharedPreferences}.
*
- * @param defaultReturnValue The default value to return if either the
- * Preference is not persistent or the Preference is not in the
- * shared preferences.
- * @return The value from the shared preferences or the default return
+ * @param defaultReturnValue The default value to return if either this
+ * Preference is not persistent or this Preference is not in the
+ * SharedPreferences.
+ * @return The value from the SharedPreferences or the default return
* value.
* @see #getPersistedString(String)
* @see #persistLong(long)
@@ -1363,10 +1376,10 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Attempts to persist a boolean to the SharedPreferences.
+ * Attempts to persist a boolean to the {@link android.content.SharedPreferences}.
*
* @param value The value to persist.
- * @return Whether the Preference is persistent. (This is not whether the
+ * @return True if this Preference is persistent. (This is not whether the
* value was persisted, since we may not necessarily commit if there
* will be a batch commit later.)
* @see #persistString(String)
@@ -1388,12 +1401,12 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Attempts to get a persisted boolean from the SharedPreferences.
+ * Attempts to get a persisted boolean from the {@link android.content.SharedPreferences}.
*
- * @param defaultReturnValue The default value to return if either the
- * Preference is not persistent or the Preference is not in the
- * shared preferences.
- * @return The value from the shared preferences or the default return
+ * @param defaultReturnValue The default value to return if either this
+ * Preference is not persistent or this Preference is not in the
+ * SharedPreferences.
+ * @return The value from the SharedPreferences or the default return
* value.
* @see #getPersistedString(String)
* @see #persistBoolean(boolean)
@@ -1416,7 +1429,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Returns the text that will be used to filter this preference depending on
+ * Returns the text that will be used to filter this Preference depending on
* user input.
* <p>
* If overridding and calling through to the superclass, make sure to prepend
@@ -1442,9 +1455,9 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Store this preference hierarchy's frozen state into the given container.
+ * Store this Preference hierarchy's frozen state into the given container.
*
- * @param container The Bundle in which to save the preference's icicles.
+ * @param container The Bundle in which to save the instance of this Preference.
*
* @see #restoreHierarchyState
* @see #dispatchSaveInstanceState
@@ -1455,11 +1468,11 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Called by {@link #saveHierarchyState} to store the icicles for this preference and its children.
- * May be overridden to modify how freezing happens to a preference's children; for example, some
- * preferences may want to not store icicles for their children.
+ * Called by {@link #saveHierarchyState} to store the instance for this Preference and its children.
+ * May be overridden to modify how the save happens for children. For example, some
+ * Preference objects may want to not store an instance for their children.
*
- * @param container The Bundle in which to save the preference's icicles.
+ * @param container The Bundle in which to save the instance of this Preference.
*
* @see #dispatchRestoreInstanceState
* @see #saveHierarchyState
@@ -1480,13 +1493,13 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Hook allowing a preference to generate a representation of its internal
+ * Hook allowing a Preference to generate a representation of its internal
* state that can later be used to create a new instance with that same
* state. This state should only contain information that is not persistent
* or can be reconstructed later.
*
- * @return Returns a Parcelable object containing the preference's current
- * dynamic state, or null if there is nothing interesting to save.
+ * @return A Parcelable object containing the current dynamic state of
+ * this Preference, or null if there is nothing interesting to save.
* The default implementation returns null.
* @see #onRestoreInstanceState
* @see #saveHierarchyState
@@ -1498,9 +1511,9 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Restore this preference hierarchy's frozen state from the given container.
+ * Restore this Preference hierarchy's previously saved state from the given container.
*
- * @param container The Bundle which holds previously frozen icicles.
+ * @param container The Bundle that holds the previously saved state.
*
* @see #saveHierarchyState
* @see #dispatchRestoreInstanceState
@@ -1511,12 +1524,12 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Called by {@link #restoreHierarchyState} to retrieve the icicles for this
- * preference and its children. May be overridden to modify how restoreing
- * happens to a preference's children; for example, some preferences may
- * want to not store icicles for their children.
+ * Called by {@link #restoreHierarchyState} to retrieve the saved state for this
+ * Preference and its children. May be overridden to modify how restoring
+ * happens to the children of a Preference. For example, some Preference objects may
+ * not want to save state for their children.
*
- * @param container The Bundle which holds previously frozen icicles.
+ * @param container The Bundle that holds the previously saved state.
* @see #dispatchSaveInstanceState
* @see #restoreHierarchyState
* @see #onRestoreInstanceState
@@ -1536,11 +1549,11 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
/**
- * Hook allowing a preference to re-apply a representation of its internal
+ * Hook allowing a Preference to re-apply a representation of its internal
* state that had previously been generated by {@link #onSaveInstanceState}.
- * This function will never be called with a null icicle.
+ * This function will never be called with a null state.
*
- * @param state The frozen state that had previously been returned by
+ * @param state The saved state that had previously been returned by
* {@link #onSaveInstanceState}.
* @see #onSaveInstanceState
* @see #restoreHierarchyState
@@ -1553,6 +1566,9 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
}
+ /**
+ * A base class for managing the instance state of a {@link Preference}.
+ */
public static class BaseSavedState extends AbsSavedState {
public BaseSavedState(Parcel source) {
super(source);
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 98144ca..95970ea 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -27,7 +27,7 @@ import android.view.View;
import android.view.Window;
/**
- * The {@link PreferenceActivity} activity shows a hierarchy of preferences as
+ * Shows a hierarchy of {@link Preference} objects as
* lists, possibly spanning multiple screens. These preferences will
* automatically save to {@link SharedPreferences} as the user interacts with
* them. To retrieve an instance of {@link SharedPreferences} that the
@@ -108,7 +108,7 @@ public abstract class PreferenceActivity extends ListActivity implements
setContentView(com.android.internal.R.layout.preference_list_content);
mPreferenceManager = onCreatePreferenceManager();
- getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
+ getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
}
@Override
diff --git a/core/java/android/preference/PreferenceCategory.java b/core/java/android/preference/PreferenceCategory.java
index a1b6f09..237c5ce 100644
--- a/core/java/android/preference/PreferenceCategory.java
+++ b/core/java/android/preference/PreferenceCategory.java
@@ -22,7 +22,7 @@ import android.content.Context;
import android.util.AttributeSet;
/**
- * The {@link PreferenceCategory} class is used to group {@link Preference}s
+ * Used to group {@link Preference} objects
* and provide a disabled title above the group.
*/
public class PreferenceCategory extends PreferenceGroup {
diff --git a/core/java/android/preference/PreferenceGroup.java b/core/java/android/preference/PreferenceGroup.java
index 55b3753..4258b41 100644
--- a/core/java/android/preference/PreferenceGroup.java
+++ b/core/java/android/preference/PreferenceGroup.java
@@ -28,8 +28,8 @@ import android.os.Parcelable;
import android.util.AttributeSet;
/**
- * The {@link PreferenceGroup} class is a container for multiple
- * {@link Preference}s. It is a base class for {@link Preference} that are
+ * A container for multiple
+ * {@link Preference} objects. It is a base class for Preference objects that are
* parents, such as {@link PreferenceCategory} and {@link PreferenceScreen}.
*
* @attr ref android.R.styleable#PreferenceGroup_orderingFromXml
diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java
index 9963544..a7a3eef 100644
--- a/core/java/android/preference/PreferenceManager.java
+++ b/core/java/android/preference/PreferenceManager.java
@@ -35,7 +35,7 @@ import android.os.Bundle;
import android.util.Log;
/**
- * The {@link PreferenceManager} is used to help create preference hierarchies
+ * Used to help create {@link Preference} hierarchies
* from activities or XML.
* <p>
* In most cases, clients should use
@@ -643,17 +643,23 @@ public class PreferenceManager {
* event.
*/
void dispatchActivityDestroy() {
- List<OnActivityDestroyListener> list;
+ List<OnActivityDestroyListener> list = null;
synchronized (this) {
- if (mActivityDestroyListeners == null) return;
- list = new ArrayList<OnActivityDestroyListener>(mActivityDestroyListeners);
+ if (mActivityDestroyListeners != null) {
+ list = new ArrayList<OnActivityDestroyListener>(mActivityDestroyListeners);
+ }
}
- final int N = list.size();
- for (int i = 0; i < N; i++) {
- list.get(i).onActivityDestroy();
+ if (list != null) {
+ final int N = list.size();
+ for (int i = 0; i < N; i++) {
+ list.get(i).onActivityDestroy();
+ }
}
+
+ // Dismiss any PreferenceScreens still showing
+ dismissAllScreens();
}
/**
@@ -697,10 +703,13 @@ public class PreferenceManager {
* @param intent The new Intent.
*/
void dispatchNewIntent(Intent intent) {
+ dismissAllScreens();
+ }
+ private void dismissAllScreens() {
// Remove any of the previously shown preferences screens
ArrayList<DialogInterface> screensToDismiss;
-
+
synchronized (this) {
if (mPreferencesScreens == null) {
@@ -715,7 +724,7 @@ public class PreferenceManager {
screensToDismiss.get(i).dismiss();
}
}
-
+
/**
* Sets the callback to be invoked when a {@link Preference} in the
* hierarchy rooted at this {@link PreferenceManager} is clicked.
diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java
index e4ecb88..9929b96 100644
--- a/core/java/android/preference/PreferenceScreen.java
+++ b/core/java/android/preference/PreferenceScreen.java
@@ -30,11 +30,11 @@ import android.widget.ListAdapter;
import android.widget.ListView;
/**
- * The {@link PreferenceScreen} class represents a top-level {@link Preference} that
- * is the root of a {@link Preference} hierarchy. A {@link PreferenceActivity}
+ * Represents a top-level {@link Preference} that
+ * is the root of a Preference hierarchy. A {@link PreferenceActivity}
* points to an instance of this class to show the preferences. To instantiate
* this class, use {@link PreferenceManager#createPreferenceScreen(Context)}.
- * <p>
+ * <ul>
* This class can appear in two places:
* <li> When a {@link PreferenceActivity} points to this, it is used as the root
* and is not shown (only the contained preferences are shown).
@@ -45,24 +45,25 @@ import android.widget.ListView;
* {@link Preference#getIntent()}). The children of this {@link PreferenceScreen}
* are NOT shown in the screen that this {@link PreferenceScreen} is shown in.
* Instead, a separate screen will be shown when this preference is clicked.
+ * </ul>
+ * <p>Here's an example XML layout of a PreferenceScreen:</p>
+ * <pre>
+&lt;PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="first_preferencescreen"&gt;
+ &lt;CheckBoxPreference
+ android:key="wifi enabled"
+ android:title="WiFi" /&gt;
+ &lt;PreferenceScreen
+ android:key="second_preferencescreen"
+ android:title="WiFi settings"&gt;
+ &lt;CheckBoxPreference
+ android:key="prefer wifi"
+ android:title="Prefer WiFi" /&gt;
+ ... other preferences here ...
+ &lt;/PreferenceScreen&gt;
+&lt;/PreferenceScreen&gt; </pre>
* <p>
- * <code>
- &lt;PreferenceScreen
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:key="first_preferencescreen"&gt;
- &lt;CheckBoxPreference
- android:key="wifi enabled"
- android:title="WiFi" /&gt;
- &lt;PreferenceScreen
- android:key="second_preferencescreen"
- android:title="WiFi settings"&gt;
- &lt;CheckBoxPreference
- android:key="prefer wifi"
- android:title="Prefer WiFi" /&gt;
- ... other preferences here ...
- &lt;/PreferenceScreen&gt;
- &lt;/PreferenceScreen&gt;
- * </code>
* In this example, the "first_preferencescreen" will be used as the root of the
* hierarchy and given to a {@link PreferenceActivity}. The first screen will
* show preferences "WiFi" (which can be used to quickly enable/disable WiFi)
diff --git a/core/java/android/preference/RingtonePreference.java b/core/java/android/preference/RingtonePreference.java
index 97674ce..6beb06d 100644
--- a/core/java/android/preference/RingtonePreference.java
+++ b/core/java/android/preference/RingtonePreference.java
@@ -27,8 +27,8 @@ import android.util.AttributeSet;
import android.util.Log;
/**
- * The {@link RingtonePreference} allows the user to choose one from all of the
- * available ringtones. The chosen ringtone's URI will be persisted as a string.
+ * A {@link Preference} that allows the user to choose a ringtone from those on the device.
+ * The chosen ringtone's URI will be persisted as a string.
* <p>
* If the user chooses the "Default" item, the saved string will be one of
* {@link System#DEFAULT_RINGTONE_URI} or
diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java
index 5a0a089..6e215dc 100644
--- a/core/java/android/preference/VolumePreference.java
+++ b/core/java/android/preference/VolumePreference.java
@@ -16,7 +16,6 @@
package android.preference;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.ContentObserver;
@@ -35,33 +34,15 @@ import android.widget.SeekBar.OnSeekBarChangeListener;
/**
* @hide
*/
-public class VolumePreference extends SeekBarPreference implements OnSeekBarChangeListener,
- Runnable, PreferenceManager.OnActivityStopListener {
+public class VolumePreference extends SeekBarPreference implements
+ PreferenceManager.OnActivityStopListener {
private static final String TAG = "VolumePreference";
- private ContentResolver mContentResolver;
- private Handler mHandler = new Handler();
-
- private AudioManager mVolume;
private int mStreamType;
- private int mOriginalStreamVolume;
- private Ringtone mRingtone;
-
- private int mLastProgress;
- private SeekBar mSeekBar;
- private ContentObserver mVolumeObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
-
- if (mSeekBar != null) {
- mSeekBar.setProgress(System.getInt(mContentResolver,
- System.VOLUME_SETTINGS[mStreamType], 0));
- }
- }
- };
+ /** May be null if the dialog isn't visible. */
+ private SeekBarVolumizer mSeekBarVolumizer;
public VolumePreference(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -70,39 +51,32 @@ public class VolumePreference extends SeekBarPreference implements OnSeekBarChan
com.android.internal.R.styleable.VolumePreference, 0, 0);
mStreamType = a.getInt(android.R.styleable.VolumePreference_streamType, 0);
a.recycle();
-
- mContentResolver = context.getContentResolver();
-
- mVolume = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
+ public void setStreamType(int streamType) {
+ mStreamType = streamType;
+ }
+
@Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
- final SeekBar seekBar = mSeekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
- seekBar.setMax(mVolume.getStreamMaxVolume(mStreamType));
- mOriginalStreamVolume = mVolume.getStreamVolume(mStreamType);
- seekBar.setProgress(mOriginalStreamVolume);
- seekBar.setOnSeekBarChangeListener(this);
+ final SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
+ mSeekBarVolumizer = new SeekBarVolumizer(getContext(), seekBar, mStreamType);
- mContentResolver.registerContentObserver(System.getUriFor(System.VOLUME_SETTINGS[mStreamType]), false, mVolumeObserver);
-
getPreferenceManager().registerOnActivityStopListener(this);
- mRingtone = RingtoneManager.getRingtone(getContext(), Settings.System.DEFAULT_RINGTONE_URI);
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
- if (!positiveResult) {
- mVolume.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
+ if (!positiveResult && mSeekBarVolumizer != null) {
+ mSeekBarVolumizer.revertVolume();
}
cleanup();
}
-
public void onActivityStop() {
cleanup();
@@ -112,50 +86,134 @@ public class VolumePreference extends SeekBarPreference implements OnSeekBarChan
* Do clean up. This can be called multiple times!
*/
private void cleanup() {
- stopSample();
- if (mVolumeObserver != null) {
- mContentResolver.unregisterContentObserver(mVolumeObserver);
+ getPreferenceManager().unregisterOnActivityStopListener(this);
+
+ if (mSeekBarVolumizer != null) {
+ mSeekBarVolumizer.stop();
+ mSeekBarVolumizer = null;
+ }
+ }
+
+ protected void onSampleStarting(SeekBarVolumizer volumizer) {
+ if (mSeekBarVolumizer != null && volumizer != mSeekBarVolumizer) {
+ mSeekBarVolumizer.stopSample();
}
- getPreferenceManager().unregisterOnActivityStopListener(this);
- mSeekBar = null;
}
+
+ /**
+ * Turns a {@link SeekBar} into a volume control.
+ */
+ public class SeekBarVolumizer implements OnSeekBarChangeListener, Runnable {
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
- if (!fromTouch) {
- return;
+ private Context mContext;
+ private Handler mHandler = new Handler();
+
+ private AudioManager mAudioManager;
+ private int mStreamType;
+ private int mOriginalStreamVolume;
+ private Ringtone mRingtone;
+
+ private int mLastProgress;
+ private SeekBar mSeekBar;
+
+ private ContentObserver mVolumeObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+
+ if (mSeekBar != null) {
+ mSeekBar.setProgress(System.getInt(mContext.getContentResolver(),
+ System.VOLUME_SETTINGS[mStreamType], 0));
+ }
+ }
+ };
+
+ public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType) {
+ mContext = context;
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mStreamType = streamType;
+ mSeekBar = seekBar;
+
+ initSeekBar(seekBar);
}
- postSetVolume(progress);
- }
-
- private void postSetVolume(int progress) {
- // Do the volume changing separately to give responsive UI
- mLastProgress = progress;
- mHandler.removeCallbacks(this);
- mHandler.post(this);
- }
+ private void initSeekBar(SeekBar seekBar) {
+ seekBar.setMax(mAudioManager.getStreamMaxVolume(mStreamType));
+ mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
+ seekBar.setProgress(mOriginalStreamVolume);
+ seekBar.setOnSeekBarChangeListener(this);
+
+ mContext.getContentResolver().registerContentObserver(
+ System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
+ false, mVolumeObserver);
- public void onStartTrackingTouch(SeekBar seekBar) {
- }
+ mRingtone = RingtoneManager.getRingtone(mContext,
+ mStreamType == AudioManager.STREAM_NOTIFICATION
+ ? Settings.System.DEFAULT_NOTIFICATION_URI
+ : Settings.System.DEFAULT_RINGTONE_URI);
+ mRingtone.setStreamType(mStreamType);
+ }
+
+ public void stop() {
+ stopSample();
+ mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
+ mSeekBar.setOnSeekBarChangeListener(null);
+ }
+
+ public void revertVolume() {
+ mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
+ }
+
+ public void onProgressChanged(SeekBar seekBar, int progress,
+ boolean fromTouch) {
+ if (!fromTouch) {
+ return;
+ }
+
+ postSetVolume(progress);
+ }
- public void onStopTrackingTouch(SeekBar seekBar) {
- if (mRingtone != null && !mRingtone.isPlaying()) {
- sample();
+ private void postSetVolume(int progress) {
+ // Do the volume changing separately to give responsive UI
+ mLastProgress = progress;
+ mHandler.removeCallbacks(this);
+ mHandler.post(this);
+ }
+
+ public void onStartTrackingTouch(SeekBar seekBar) {
}
- }
- public void run() {
- mVolume.setStreamVolume(mStreamType, mLastProgress, 0);
- }
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ if (mRingtone != null && !mRingtone.isPlaying()) {
+ sample();
+ }
+ }
+
+ public void run() {
+ mAudioManager.setStreamVolume(mStreamType, mLastProgress, 0);
+ }
+
+ private void sample() {
- private void sample() {
- mRingtone.play();
- }
+ // Only play a preview sample when controlling the ringer stream
+ if (mStreamType != AudioManager.STREAM_RING
+ && mStreamType != AudioManager.STREAM_NOTIFICATION) {
+ return;
+ }
+
+ onSampleStarting(this);
+ mRingtone.play();
+ }
+
+ public void stopSample() {
+ if (mRingtone != null) {
+ mRingtone.stop();
+ }
+ }
- private void stopSample() {
- if (mRingtone != null) {
- mRingtone.stop();
+ public SeekBar getSeekBar() {
+ return mSeekBar;
}
+
}
-
}
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index b07f1b8..b137b34 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -38,11 +38,11 @@ import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
-import android.pim.DateUtils;
import android.pim.ICalendar;
import android.pim.RecurrenceSet;
-import android.pim.Time;
import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
import android.util.Config;
import android.util.Log;
@@ -429,18 +429,27 @@ public final class Calendar {
public static final String EXDATE = "exdate";
/**
- * The original event this event is an exception for
+ * The _sync_id of the original recurring event for which this event is
+ * an exception.
* <P>Type: TEXT</P>
*/
public static final String ORIGINAL_EVENT = "originalEvent";
/**
- * The time of the original instance time this event is an exception for
+ * The original instance time of the recurring event for which this
+ * event is an exception.
* <P>Type: INTEGER (long; millis since epoch)</P>
*/
public static final String ORIGINAL_INSTANCE_TIME = "originalInstanceTime";
/**
+ * The allDay status (true or false) of the original recurring event
+ * for which this event is an exception.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String ORIGINAL_ALL_DAY = "originalAllDay";
+
+ /**
* The last date this event repeats on, or NULL if it never ends
* <P>Type: INTEGER (long; millis since epoch)</P>
*/
@@ -543,7 +552,7 @@ public final class Calendar {
time.clear(tzidParam.value);
}
try {
- time.parse2445(dtstart);
+ time.parse(dtstart);
} catch (Exception e) {
if (Config.LOGD) {
Log.d(TAG, "Cannot parse dtstart " + dtstart, e);
@@ -564,7 +573,7 @@ public final class Calendar {
// TODO: make sure the timezones are the same for
// start, end.
try {
- time.parse2445(dtend);
+ time.parse(dtend);
} catch (Exception e) {
if (Config.LOGD) {
Log.d(TAG, "Cannot parse dtend " + dtend, e);
diff --git a/core/java/android/provider/Checkin.java b/core/java/android/provider/Checkin.java
index 5b79482..ef5eded 100644
--- a/core/java/android/provider/Checkin.java
+++ b/core/java/android/provider/Checkin.java
@@ -96,8 +96,6 @@ public final class Checkin {
SYSTEM_SERVICE_LOOPING,
SYSTEM_TOMBSTONE,
TEST,
- NETWORK_RX_MOBILE,
- NETWORK_TX_MOBILE,
}
}
diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java
index 91b1853..6d24ba8 100644
--- a/core/java/android/provider/Contacts.java
+++ b/core/java/android/provider/Contacts.java
@@ -16,6 +16,8 @@
package android.provider;
+import com.android.internal.R;
+
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -29,8 +31,6 @@ import android.text.TextUtils;
import android.util.Log;
import android.widget.ImageView;
-import com.android.internal.R;
-
import java.io.ByteArrayInputStream;
import java.io.InputStream;
@@ -1229,7 +1229,7 @@ public class Contacts {
try {
display = labels[type - 1];
} catch (ArrayIndexOutOfBoundsException e) {
- display = labels[People.Phones.TYPE_HOME - 1];
+ display = labels[Organizations.TYPE_WORK - 1];
}
} else {
if (!TextUtils.isEmpty(label)) {
@@ -1546,6 +1546,32 @@ public class Contacts {
public static final String PHONE_ISPRIMARY = "phone_isprimary";
/**
+ * The extra field for an optional second contact phone number.
+ * <P>Type: String</P>
+ */
+ public static final String SECONDARY_PHONE = "secondary_phone";
+
+ /**
+ * The extra field for an optional second contact phone number type.
+ * <P>Type: Either an integer value from {@link android.provider.Contacts.PhonesColumns PhonesColumns},
+ * or a string specifying a type and label.</P>
+ */
+ public static final String SECONDARY_PHONE_TYPE = "secondary_phone_type";
+
+ /**
+ * The extra field for an optional third contact phone number.
+ * <P>Type: String</P>
+ */
+ public static final String TERTIARY_PHONE = "tertiary_phone";
+
+ /**
+ * The extra field for an optional third contact phone number type.
+ * <P>Type: Either an integer value from {@link android.provider.Contacts.PhonesColumns PhonesColumns},
+ * or a string specifying a type and label.</P>
+ */
+ public static final String TERTIARY_PHONE_TYPE = "tertiary_phone_type";
+
+ /**
* The extra field for the contact email address.
* <P>Type: String</P>
*/
@@ -1565,6 +1591,32 @@ public class Contacts {
public static final String EMAIL_ISPRIMARY = "email_isprimary";
/**
+ * The extra field for an optional second contact email address.
+ * <P>Type: String</P>
+ */
+ public static final String SECONDARY_EMAIL = "secondary_email";
+
+ /**
+ * The extra field for an optional second contact email type.
+ * <P>Type: Either an integer value from {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns}
+ * or a string specifying a type and label.</P>
+ */
+ public static final String SECONDARY_EMAIL_TYPE = "secondary_email_type";
+
+ /**
+ * The extra field for an optional third contact email address.
+ * <P>Type: String</P>
+ */
+ public static final String TERTIARY_EMAIL = "tertiary_email";
+
+ /**
+ * The extra field for an optional third contact email type.
+ * <P>Type: Either an integer value from {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns}
+ * or a string specifying a type and label.</P>
+ */
+ public static final String TERTIARY_EMAIL_TYPE = "tertiary_email_type";
+
+ /**
* The extra field for the contact postal address.
* <P>Type: String</P>
*/
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 42e9d95..a5a30b9 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -29,6 +29,29 @@ import android.net.Uri;
// this API is hidden.
public final class Downloads implements BaseColumns {
private Downloads() {}
+
+ /**
+ * The permission to access the download manager
+ */
+ public static final String PERMISSION_ACCESS = "android.permission.ACCESS_DOWNLOAD_MANAGER";
+
+ /**
+ * The permission to access the download manager's advanced functions
+ */
+ public static final String PERMISSION_ACCESS_ADVANCED =
+ "android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED";
+
+ /**
+ * The permission to directly access the download manager's cache directory
+ */
+ public static final String PERMISSION_CACHE = "android.permission.ACCESS_CACHE_FILESYSTEM";
+
+ /**
+ * The permission to send broadcasts on download completion
+ */
+ public static final String PERMISSION_SEND_INTENTS =
+ "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
+
/**
* The content:// URI for the data table in the provider
*/
@@ -64,21 +87,11 @@ public final class Downloads implements BaseColumns {
public static final String URI = "uri";
/**
- * The name of the column containing the HTTP method to use for this
- * download. See the METHOD_* constants for a list of legal values.
- * <P>Type: INTEGER</P>
- * <P>Owner can Init/Read</P>
- */
- public static final String METHOD = "method";
-
- /**
- * The name of the column containing the entity to be sent with the
- * request of this download. Only use for methods that support sending
- * entities, i.e. POST.
+ * The name of the column containing application-specific data.
* <P>Type: TEXT</P>
- * <P>Owner can Init</P>
+ * <P>Owner can Init/Read/Write</P>
*/
- public static final String ENTITY = "entity";
+ public static final String APP_DATA = "entity";
/**
* The name of the column containing the flags that indicates whether
@@ -89,7 +102,7 @@ public final class Downloads implements BaseColumns {
* a byte-range request without an ETag, or when it can't determine
* whether a download fully completed).
* <P>Type: BOOLEAN</P>
- * <P>Owner can Init/Read</P>
+ * <P>Owner can Init</P>
*/
public static final String NO_INTEGRITY = "no_integrity";
@@ -98,7 +111,7 @@ public final class Downloads implements BaseColumns {
* application recommends. When possible, the download manager will attempt
* to use this filename, or a variation, as the actual name for the file.
* <P>Type: TEXT</P>
- * <P>Owner can Init/Read</P>
+ * <P>Owner can Init</P>
*/
public static final String FILENAME_HINT = "hint";
@@ -107,15 +120,13 @@ public final class Downloads implements BaseColumns {
* was actually stored.
* <P>Type: TEXT</P>
* <P>Owner can Read</P>
- * <P>UI can Read</P>
*/
- public static final String FILENAME = "_data";
+ public static final String _DATA = "_data";
/**
* The name of the column containing the MIME type of the downloaded data.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read</P>
- * <P>UI can Read</P>
*/
public static final String MIMETYPE = "mimetype";
@@ -123,51 +134,25 @@ public final class Downloads implements BaseColumns {
* The name of the column containing the flag that controls the destination
* of the download. See the DESTINATION_* constants for a list of legal values.
* <P>Type: INTEGER</P>
- * <P>Owner can Init/Read</P>
- * <P>UI can Read</P>
+ * <P>Owner can Init</P>
*/
public static final String DESTINATION = "destination";
/**
- * The name of the column containing the flags that controls whether
- * the download must be saved with the filename used for OTA updates.
- * Must be used with INTERNAL, and the initiating application must hold the
- * android.permission.DOWNLOAD_OTA_UPDATE permission.
- * <P>Type: BOOLEAN</P>
- * <P>Owner can Init/Read</P>
- * <P>UI can Read</P>
- */
- public static final String OTA_UPDATE = "otaupdate";
-
- /**
- * The name of the columns containing the flag that controls whether
- * files with private/inernal/system MIME types can be downloaded.
- * <P>Type: BOOLEAN</P>
- * <P>Owner can Init/Read</P>
- */
- public static final String NO_SYSTEM_FILES = "no_system";
-
- /**
* The name of the column containing the flags that controls whether the
* download is displayed by the UI. See the VISIBILITY_* constants for
* a list of legal values.
* <P>Type: INTEGER</P>
* <P>Owner can Init/Read/Write</P>
- * <P>UI can Read/Write (only for entries that are visible)</P>
*/
public static final String VISIBILITY = "visibility";
/**
- * The name of the column containing the command associated with the
- * download. After a download is initiated, this is the only column that
- * applications can modify. See the CONTROL_* constants for a list of legal
- * values. Note: doesn't do anything in 1.0. The API will be hooked up
- * in a future version, and is provided here as an indication of things
- * to come.
+ * The name of the column containing the current control state of the download.
+ * Applications can write to this to control (pause/resume) the download.
+ * the CONTROL_* constants for a list of legal values.
* <P>Type: INTEGER</P>
- * <P>Owner can Init/Read/Write</P>
- * <P>UI can Init/Read/Write</P>
- * @hide
+ * <P>Owner can Read</P>
*/
public static final String CONTROL = "control";
@@ -177,7 +162,6 @@ public final class Downloads implements BaseColumns {
* the STATUS_* constants for a list of legal values.
* <P>Type: INTEGER</P>
* <P>Owner can Read</P>
- * <P>UI can Read</P>
*/
public static final String STATUS = "status";
@@ -187,24 +171,15 @@ public final class Downloads implements BaseColumns {
* value.
* <P>Type: BIGINT</P>
* <P>Owner can Read</P>
- * <P>UI can Read</P>
*/
public static final String LAST_MODIFICATION = "lastmod";
/**
- * The name of the column containing the number of consecutive connections
- * that have failed.
- * <P>Type: INTEGER</P>
- */
- public static final String FAILED_CONNECTIONS = "numfailed";
-
- /**
* The name of the column containing the package name of the application
* that initiating the download. The download manager will send
* notifications to a component in this package when the download completes.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read</P>
- * <P>UI can Read</P>
*/
public static final String NOTIFICATION_PACKAGE = "notificationpackage";
@@ -215,13 +190,14 @@ public final class Downloads implements BaseColumns {
* Intent.setClassName(String,String).
* <P>Type: TEXT</P>
* <P>Owner can Init/Read</P>
- * <P>UI can Read</P>
*/
public static final String NOTIFICATION_CLASS = "notificationclass";
/**
* If extras are specified when requesting a download they will be provided in the intent that
* is sent to the specified class and package when a download has finished.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
*/
public static final String NOTIFICATION_EXTRAS = "notificationextras";
@@ -255,7 +231,6 @@ public final class Downloads implements BaseColumns {
* downloaded.
* <P>Type: INTEGER</P>
* <P>Owner can Read</P>
- * <P>UI can Read</P>
*/
public static final String TOTAL_BYTES = "total_bytes";
@@ -264,33 +239,18 @@ public final class Downloads implements BaseColumns {
* has been downloaded so far.
* <P>Type: INTEGER</P>
* <P>Owner can Read</P>
- * <P>UI can Read</P>
*/
public static final String CURRENT_BYTES = "current_bytes";
/**
- * The name of the column containing the entity tag for the response.
- * <P>Type: TEXT</P>
- * @hide
- */
- public static final String ETAG = "etag";
-
- /**
- * The name of the column containing the UID of the application that
- * initiated the download.
- * <P>Type: INTEGER</P>
- * @hide
- */
- public static final String UID = "uid";
-
- /**
* The name of the column where the initiating application can provide the
* UID of another application that is allowed to access this download. If
* multiple applications share the same UID, all those applications will be
* allowed to access this download. This column can be updated after the
- * download is initiated.
+ * download is initiated. This requires the permission
+ * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED.
* <P>Type: INTEGER</P>
- * <P>Owner can Init/Read/Write</P>
+ * <P>Owner can Init</P>
*/
public static final String OTHER_UID = "otheruid";
@@ -300,7 +260,6 @@ public final class Downloads implements BaseColumns {
* list of downloads.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read/Write</P>
- * <P>UI can Read</P>
*/
public static final String TITLE = "title";
@@ -310,18 +269,9 @@ public final class Downloads implements BaseColumns {
* user in the list of downloads.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read/Write</P>
- * <P>UI can Read</P>
*/
public static final String DESCRIPTION = "description";
- /**
- * The name of the column where the download manager indicates whether the
- * media scanner was notified about this download.
- * <P>Type: BOOLEAN</P>
- * @hide
- */
- public static final String MEDIA_SCANNED = "scanned";
-
/*
* Lists the destinations that an application can specify for a download.
*/
@@ -343,7 +293,8 @@ public final class Downloads implements BaseColumns {
* download private files that are used and deleted soon after they
* get downloaded. All file types are allowed, and only the initiating
* application can access the file (indirectly through a content
- * provider).
+ * provider). This requires the
+ * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission.
*/
public static final int DESTINATION_CACHE_PARTITION = 1;
@@ -357,40 +308,21 @@ public final class Downloads implements BaseColumns {
public static final int DESTINATION_CACHE_PARTITION_PURGEABLE = 2;
/**
- * This download will be saved to the download manager's cache
- * on the shared data partition. Use CACHE_PARTITION_PURGEABLE instead.
- */
- public static final int DESTINATION_DATA_CACHE = 3;
-
- /* (not javadoc)
- * This download will be saved to a file specified by the initiating
- * applications.
- * @hide
- */
- //public static final int DESTINATION_PROVIDER = 4;
-
- /*
- * Lists the commands that an application can set to control an ongoing
- * download. Note: those aren't working.
+ * This download will be saved to the download manager's private
+ * partition, as with DESTINATION_CACHE_PARTITION, but the download
+ * will not proceed if the user is on a roaming data connection.
*/
+ public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3;
/**
- * This download can run
- * @hide
+ * This download is allowed to run.
*/
public static final int CONTROL_RUN = 0;
/**
- * This download must pause (might be restarted)
- * @hide
- */
- public static final int CONTROL_PAUSE = 1;
-
- /**
- * This download must abort (will never be restarted)
- * @hide
+ * This download must pause at the first opportunity.
*/
- public static final int CONTROL_STOP = 2;
+ public static final int CONTROL_PAUSED = 1;
/*
* Lists the states that the download manager can set on a download
@@ -490,11 +422,6 @@ public final class Downloads implements BaseColumns {
public static final int STATUS_BAD_REQUEST = 400;
/**
- * The server returned an auth error.
- */
- public static final int STATUS_NOT_AUTHORIZED = 401;
-
- /**
* This download can't be performed because the content type cannot be
* handled.
*/
@@ -522,11 +449,6 @@ public final class Downloads implements BaseColumns {
* This download was canceled
*/
public static final int STATUS_CANCELED = 490;
- /**
- * @hide
- * Alternate spelling
- */
- public static final int STATUS_CANCELLED = 490;
/**
* This download has completed with an error.
@@ -534,11 +456,6 @@ public final class Downloads implements BaseColumns {
* the future. Use isStatusError() to capture the entire category.
*/
public static final int STATUS_UNKNOWN_ERROR = 491;
- /**
- * @hide
- * Legacy name - use STATUS_UNKNOWN_ERROR
- */
- public static final int STATUS_ERROR = 491;
/**
* This download couldn't be completed because of a storage issue.
@@ -548,54 +465,40 @@ public final class Downloads implements BaseColumns {
/**
* This download couldn't be completed because of an HTTP
- * redirect code.
+ * redirect response that the download manager couldn't
+ * handle.
*/
public static final int STATUS_UNHANDLED_REDIRECT = 493;
/**
- * This download couldn't be completed because of an
- * unspecified unhandled HTTP code.
+ * This download couldn't be completed because there were
+ * too many redirects.
*/
- public static final int STATUS_UNHANDLED_HTTP_CODE = 494;
+ public static final int STATUS_TOO_MANY_REDIRECTS = 494;
/**
* This download couldn't be completed because of an
- * error receiving or processing data at the HTTP level.
+ * unspecified unhandled HTTP code.
*/
- public static final int STATUS_HTTP_DATA_ERROR = 495;
+ public static final int STATUS_UNHANDLED_HTTP_CODE = 495;
/**
* This download couldn't be completed because of an
- * HttpException while setting up the request.
- */
- public static final int STATUS_HTTP_EXCEPTION = 496;
-
- /*
- * Lists the HTTP methods that the download manager can use.
- */
-
- /**
- * GET
+ * error receiving or processing data at the HTTP level.
*/
- public static final int METHOD_GET = 0;
+ public static final int STATUS_HTTP_DATA_ERROR = 496;
/**
- * POST
+ * This download is visible and shows in the notifications while
+ * in progress and after completion.
*/
- public static final int METHOD_POST = 1;
+ public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 0;
/**
* This download is visible but only shows in the notifications
- * while it's running (a separate download UI would still show it
- * after completion).
- */
- public static final int VISIBILITY_VISIBLE = 0;
-
- /**
- * This download is visible and shows in the notifications after
- * completion.
+ * while it's in progress.
*/
- public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;
+ public static final int VISIBILITY_VISIBLE = 1;
/**
* This download doesn't show in the UI or in the notifications.
diff --git a/core/java/android/provider/Gmail.java b/core/java/android/provider/Gmail.java
index 038ba21..0b6a758 100644
--- a/core/java/android/provider/Gmail.java
+++ b/core/java/android/provider/Gmail.java
@@ -72,6 +72,7 @@ public final class Gmail {
public static final String LABEL_STARRED = "^t";
public static final String LABEL_CHAT = "^b"; // 'b' for 'buzz'
public static final String LABEL_VOICEMAIL = "^vm";
+ public static final String LABEL_IGNORED = "^g";
public static final String LABEL_ALL = "^all";
// These constants (starting with "^^") are only used locally and are not understood by the
// server.
@@ -142,6 +143,8 @@ public final class Gmail {
public static final String RESPOND_INPUT_COMMAND = "command";
public static final String COMMAND_RETRY = "retry";
public static final String COMMAND_ACTIVATE = "activate";
+ public static final String COMMAND_SET_VISIBLE = "setVisible";
+ public static final String SET_VISIBLE_PARAM_VISIBLE = "visible";
public static final String RESPOND_OUTPUT_COMMAND_RESPONSE = "commandResponse";
public static final String COMMAND_RESPONSE_OK = "ok";
public static final String COMMAND_RESPONSE_UNKNOWN = "unknownCommand";
@@ -210,7 +213,8 @@ public final class Gmail {
Gmail.LABEL_UNREAD,
Gmail.LABEL_TRASH,
Gmail.LABEL_SPAM,
- Gmail.LABEL_STARRED);
+ Gmail.LABEL_STARRED,
+ Gmail.LABEL_IGNORED);
/**
* Returns whether the label is user-settable. For example, labels such as LABEL_DRAFT should
@@ -337,6 +341,8 @@ public final class Gmail {
public static final String LABEL_IDS = "labelIds";
public static final String JOINED_ATTACHMENT_INFOS = "joinedAttachmentInfos";
public static final String ERROR = "error";
+ // TODO: add a method for accessing this
+ public static final String REF_MESSAGE_ID = "refMessageId";
// Fake columns used only for saving or sending messages.
public static final String FAKE_SAVE = "save";
@@ -773,7 +779,8 @@ public final class Gmail {
priorityToLength.clear();
int maxFoundPriority = Integer.MIN_VALUE;
- String numMessagesFragment = "";
+ int numMessages = 0;
+ int numDrafts = 0;
CharSequence draftsFragment = "";
CharSequence sendingFragment = "";
CharSequence sendFailedFragment = "";
@@ -799,10 +806,10 @@ public final class Gmail {
} else if (Gmail.SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) {
// ignore
} else if (Gmail.SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) {
- numMessagesFragment = " (" + fragments[i++] + ")";
+ numMessages = Integer.valueOf(fragments[i++]);
} else if (Gmail.SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) {
String numDraftsString = fragments[i++];
- int numDrafts = Integer.parseInt(numDraftsString);
+ numDrafts = Integer.parseInt(numDraftsString);
draftsFragment = numDrafts == 1 ? draftString :
draftPluralString + " (" + numDraftsString + ")";
} else if (Gmail.SENDER_LIST_TOKEN_LITERAL.equals(fragment0)) {
@@ -821,6 +828,8 @@ public final class Gmail {
maxFoundPriority = Math.max(maxFoundPriority, priority);
}
}
+ String numMessagesFragment =
+ (numMessages != 0) ? " (" + Integer.toString(numMessages + numDrafts) + ")" : "";
// Don't allocate fixedFragment unless we need it
SpannableStringBuilder fixedFragment = null;
@@ -1242,6 +1251,7 @@ public final class Gmail {
private long mLabelIdStarred;
private long mLabelIdChat;
private long mLabelIdVoicemail;
+ private long mLabelIdIgnored;
private long mLabelIdVoicemailInbox;
private long mLabelIdCached;
private long mLabelIdOutbox;
@@ -1313,6 +1323,8 @@ public final class Gmail {
mLabelIdStarred = labelId;
} else if (LABEL_CHAT.equals(canonicalName)) {
mLabelIdChat = labelId;
+ } else if (LABEL_IGNORED.equals(canonicalName)) {
+ mLabelIdIgnored = labelId;
} else if (LABEL_VOICEMAIL.equals(canonicalName)) {
mLabelIdVoicemail = labelId;
} else if (LABEL_VOICEMAIL_INBOX.equals(canonicalName)) {
@@ -1330,6 +1342,7 @@ public final class Gmail {
&& mLabelIdSpam != 0
&& mLabelIdStarred != 0
&& mLabelIdChat != 0
+ && mLabelIdIgnored != 0
&& mLabelIdVoicemail != 0;
}
}
@@ -1374,6 +1387,11 @@ public final class Gmail {
return mLabelIdChat;
}
+ public long getLabelIdIgnored() {
+ checkLabelsSynced();
+ return mLabelIdIgnored;
+ }
+
public long getLabelIdVoicemail() {
checkLabelsSynced();
return mLabelIdVoicemail;
@@ -2237,9 +2255,28 @@ public final class Gmail {
}
/**
- * Gets the conversation id. This must be immutable. (For example, with
- * GMail this should be the original conversation id rather than the
- * default notion of converation id.)
+ * Tells the cursor whether its contents are visible to the user. The cursor will
+ * automatically broadcast intents to remove any matching new-mail notifications when this
+ * cursor's results become visible and, if they are visible, when the cursor is requeried.
+ *
+ * Note that contents shown in an activity that is resumed but not focused
+ * (onWindowFocusChanged/hasWindowFocus) then results shown in that activity do not count
+ * as visible. (This happens when the activity is behind the lock screen or a dialog.)
+ *
+ * @param visible whether the contents of this cursor are visible to the user.
+ */
+ public void setContentsVisibleToUser(boolean visible) {
+ Bundle input = new Bundle();
+ input.putString(RESPOND_INPUT_COMMAND, COMMAND_SET_VISIBLE);
+ input.putBoolean(SET_VISIBLE_PARAM_VISIBLE, visible);
+ Bundle output = mCursor.respond(input);
+ String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
+ assert COMMAND_RESPONSE_OK.equals(response);
+ }
+
+ /**
+ * Gets the conversation id. This is immutable. (The server calls it the original
+ * conversation id.)
*
* @return the conversation id
*/
diff --git a/core/java/android/provider/Im.java b/core/java/android/provider/Im.java
index 8ca97e1..68b2acd 100644
--- a/core/java/android/provider/Im.java
+++ b/core/java/android/provider/Im.java
@@ -43,16 +43,25 @@ public class Im {
public interface ProviderColumns {
/**
* The name of the IM provider
+ * <P>Type: TEXT</P>
*/
String NAME = "name";
/**
* The full name of the provider
+ * <P>Type: TEXT</P>
*/
String FULLNAME = "fullname";
/**
+ * The category for the provider, used to form intent.
+ * <P>Type: TEXT</P>
+ */
+ String CATEGORY = "category";
+
+ /**
* The url users should visit to create a new account for this provider
+ * <P>Type: TEXT</P>
*/
String SIGNUP_URL = "signup_url";
}
@@ -187,6 +196,9 @@ public class Im {
public static final String ACTIVE_ACCOUNT_ID = "account_id";
public static final String ACTIVE_ACCOUNT_USERNAME = "account_username";
public static final String ACTIVE_ACCOUNT_PW = "account_pw";
+ public static final String ACTIVE_ACCOUNT_LOCKED = "account_locked";
+ public static final String ACCOUNT_PRESENCE_STATUS = "account_presenceStatus";
+ public static final String ACCOUNT_CONNECTION_STATUS = "account_connStatus";
/**
* The content:// style URL for this table
@@ -204,6 +216,9 @@ public class Im {
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/im-providers";
+ public static final String CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/im-providers";
+
/**
* The default sort order for this table
*/
@@ -320,6 +335,76 @@ public class Im {
}
/**
+ * Connection status
+ */
+ public interface ConnectionStatus {
+ /**
+ * The connection is offline, not logged in.
+ */
+ int OFFLINE = 0;
+
+ /**
+ * The connection is attempting to connect.
+ */
+ int CONNECTING = 1;
+
+ /**
+ * The connection is suspended due to network not available.
+ */
+ int SUSPENDED = 2;
+
+ /**
+ * The connection is logged in and online.
+ */
+ int ONLINE = 3;
+ }
+
+ public interface AccountStatusColumns {
+ /**
+ * account id
+ * <P>Type: INTEGER</P>
+ */
+ String ACCOUNT = "account";
+
+ /**
+ * User's presence status, see definitions in {#link CommonPresenceColumn}
+ * <P>Type: INTEGER</P>
+ */
+ String PRESENCE_STATUS = "presenceStatus";
+
+ /**
+ * The connection status of this account, see {#link ConnectionStatus}
+ * <P>Type: INTEGER</P>
+ */
+ String CONNECTION_STATUS = "connStatus";
+ }
+
+ public static final class AccountStatus implements BaseColumns, AccountStatusColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://im/accountStatus");
+
+ /**
+ * The MIME type of {@link #CONTENT_URI} providing a directory of account status.
+ */
+ public static final String CONTENT_TYPE =
+ "vnd.android.cursor.dir/im-account-status";
+
+ /**
+ * The MIME type of a {@link #CONTENT_URI} subdirectory of a single account status.
+ */
+ public static final String CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/im-account-status";
+
+ /**
+ * The default sort order for this table
+ */
+ public static final String DEFAULT_SORT_ORDER = "name ASC";
+ }
+
+ /**
* Columns from the Contacts table.
*/
public interface ContactsColumns {
@@ -451,9 +536,37 @@ public class Im {
* <P>Type: INTEGER</P>
*/
String REJECTED = "rejected";
+
+ /**
+ * Off The Record status: 0 for disabled, 1 for enabled
+ * <P>Type: INTEGER </P>
+ */
+ String OTR = "otr";
}
/**
+ * This defines the different type of values of {@link ContactsColumns#OTR}
+ */
+ public interface OffTheRecordType {
+ /*
+ * Off the record not turned on
+ */
+ int DISABLED = 0;
+ /**
+ * Off the record turned on, but we don't know who turned it on
+ */
+ int ENABLED = 1;
+ /**
+ * Off the record turned on by the user
+ */
+ int ENABLED_BY_USER = 2;
+ /**
+ * Off the record turned on by the buddy
+ */
+ int ENABLED_BY_BUDDY = 3;
+ };
+
+ /**
* This table contains contacts.
*/
public static final class Contacts implements BaseColumns,
@@ -755,15 +868,32 @@ public class Im {
* Message type definition
*/
public interface MessageType {
+ /* sent message */
int OUTGOING = 0;
+ /* received message */
int INCOMING = 1;
+ /* presence became available */
int PRESENCE_AVAILABLE = 2;
+ /* presence became away */
int PRESENCE_AWAY = 3;
+ /* presence became DND (busy) */
int PRESENCE_DND = 4;
+ /* presence became unavailable */
int PRESENCE_UNAVAILABLE = 5;
+ /* the message is converted to a group chat */
int CONVERT_TO_GROUPCHAT = 6;
+ /* generic status */
int STATUS = 7;
+ /* the message cannot be sent now, but will be sent later */
int POSTPONED = 8;
+ /* off The Record status is turned off */
+ int OTR_IS_TURNED_OFF = 9;
+ /* off the record status is turned on */
+ int OTR_IS_TURNED_ON = 10;
+ /* off the record status turned on by user */
+ int OTR_TURNED_ON_BY_USER = 11;
+ /* off the record status turned on by buddy */
+ int OTR_TURNED_ON_BY_BUDDY = 12;
}
/**
@@ -1244,26 +1374,25 @@ public class Im {
public interface ChatsColumns {
/**
* The contact ID this chat belongs to. The value is a long.
- * <P>Type: TEXT</P>
+ * <P>Type: INT</P>
*/
String CONTACT_ID = "contact_id";
/**
* The GTalk JID resource. The value is a string.
+ * <P>Type: TEXT</P>
*/
String JID_RESOURCE = "jid_resource";
/**
* Whether this is a groupchat or not.
+ * <P>Type: INT</P>
*/
- // TODO: remove this column since we already have a tag in contacts
- // table to indicate it's a group chat.
String GROUP_CHAT = "groupchat";
/**
* The last unread message. This both indicates that there is an
* unread message, and what the message is.
- *
* <P>Type: TEXT</P>
*/
String LAST_UNREAD_MESSAGE = "last_unread_message";
@@ -1278,10 +1407,17 @@ public class Im {
* A message that is being composed. This indicates that there was a
* message being composed when the chat screen was shutdown, and what the
* message is.
- *
* <P>Type: TEXT</P>
*/
String UNSENT_COMPOSED_MESSAGE = "unsent_composed_message";
+
+ /**
+ * A value from 0-9 indicating which quick-switch chat screen slot this
+ * chat is occupying. If none (for instance, this is the 12th active chat)
+ * then the value is -1.
+ * <P>Type: INT</P>
+ */
+ String SHORTCUT = "shortcut";
}
/**
diff --git a/core/java/android/provider/LiveFolders.java b/core/java/android/provider/LiveFolders.java
new file mode 100644
index 0000000..6e95fb7
--- /dev/null
+++ b/core/java/android/provider/LiveFolders.java
@@ -0,0 +1,298 @@
+/*
+ * 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.provider;
+
+import android.annotation.SdkConstant;
+
+/**
+ * <p>A LiveFolder is a special folder whose content is provided by a
+ * {@link android.content.ContentProvider}. To create a live folder, two components
+ * are required:</p>
+ * <ul>
+ * <li>An activity that can respond to the intent action {@link #ACTION_CREATE_LIVE_FOLDER}. The
+ * activity is responsible for creating the live folder.</li>
+ * <li>A {@link android.content.ContentProvider} to provide the live folder items.</li>
+ * </ul>
+ *
+ * <h3>Lifecycle</h3>
+ * <p>When a user wants to create a live folder, the system looks for all activities with the
+ * intent filter action {@link #ACTION_CREATE_LIVE_FOLDER} and presents the list to the user.
+ * When the user chooses one of the activities, the activity is invoked with the
+ * {@link #ACTION_CREATE_LIVE_FOLDER} action. The activity then creates the live folder and
+ * passes it back to the system by setting it as an
+ * {@link android.app.Activity#setResult(int, android.content.Intent) activity result}. The
+ * live folder is described by a content provider URI, a name, an icon and a display mode.
+ * Finally, when the user opens the live folder, the system queries the content provider
+ * to retrieve the folder's content.</p>
+ *
+ * <h3>Setting up the live folder activity</h3>
+ * <p>The following code sample shows how to write an activity that creates a live fodler:</p>
+ * <pre>
+ * public static class MyLiveFolder extends Activity {
+ * public static final Uri CONTENT_URI = Uri.parse("content://my.app/live");
+ *
+ * @Override
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ *
+ * final Intent intent = getIntent();
+ * final String action = intent.getAction();
+ *
+ * if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action)) {
+ * setResult(RESULT_OK, createLiveFolder(this, CONTENT_URI, "My LiveFolder",
+ * R.drawable.ic_launcher_contacts_phones));
+ * } else {
+ * setResult(RESULT_CANCELED);
+ * }
+ *
+ * finish();
+ * }
+ *
+ * private static Intent createLiveFolder(Context context, Uri uri, String name,
+ * int icon) {
+ *
+ * final Intent intent = new Intent();
+ *
+ * intent.setData(uri);
+ * intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, name);
+ * intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON,
+ * Intent.ShortcutIconResource.fromContext(context, icon));
+ * intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE, LiveFolders.DISPLAY_MODE_LIST);
+ *
+ * return intent;
+ * }
+ * }
+ * </pre>
+ * <p>The live folder is described by an {@link android.content.Intent} as follows:</p>
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ * <thead>
+ * <tr><th>Component</th> <th>Type</th> <th>Description</th> <th>Required</th></tr>
+ * </thead>
+ *
+ * <tbody>
+ * <tr><th>URI</th>
+ * <td>URI</td>
+ * <td>The ContentProvider URI</td>
+ * <td align="center">Yes</td>
+ * </tr>
+ * <tr><th>{@link #EXTRA_LIVE_FOLDER_NAME}</th>
+ * <td>Extra String</td>
+ * <td>The name of the live folder</td>
+ * <td align="center">Yes</td>
+ * </tr>
+ * <tr><th>{@link #EXTRA_LIVE_FOLDER_ICON}</th>
+ * <td>Extra {@link android.content.Intent.ShortcutIconResource}</td>
+ * <td>The icon of the live folder</td>
+ * <td align="center">Yes</td>
+ * </tr>
+ * <tr><th>{@link #EXTRA_LIVE_FOLDER_DISPLAY_MODE}</th>
+ * <td>Extra int</td>
+ * <td>The display mode of the live folder. The value must be either
+ * {@link #DISPLAY_MODE_GRID} or {@link #DISPLAY_MODE_LIST}.</td>
+ * <td align="center">Yes</td>
+ * </tr>
+ * <tr><th>{@link #EXTRA_LIVE_FOLDER_BASE_INTENT}</th>
+ * <td>Extra Intent</td>
+ * <td>When the user clicks an item inside a live folder, the system will either fire
+ * the intent associated with that item or, if present, the live folder's base intent
+ * with the id of the item appended to the base intent's URI.</td>
+ * <td align="center">No</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * <h3>Setting up the content provider</h3>
+ * <p>The live folder's content provider must, upon query, return a {@link android.database.Cursor}
+ * whose columns match the following names:</p>
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ * <thead>
+ * <tr><th>Column</th> <th>Type</th> <th>Description</th> <th>Required</th></tr>
+ * </thead>
+ *
+ * <tbody>
+ * <tr><th>{@link #NAME}</th>
+ * <td>String</td>
+ * <td>The name of the item</td>
+ * <td align="center">Yes</td>
+ * </tr>
+ * <tr><th>{@link #DESCRIPTION}</th>
+ * <td>String</td>
+ * <td>The description of the item. The description is ignored when the live folder's
+ * display mode is {@link #DISPLAY_MODE_GRID}.</td>
+ * <td align="center">No</td>
+ * </tr>
+ * <tr><th>{@link #INTENT}</th>
+ * <td>{@link android.content.Intent}</td>
+ * <td>The intent to fire when the item is clicked. Ignored when the live folder defines
+ * a base intent.</td>
+ * <td align="center">No</td>
+ * </tr>
+ * <tr><th>{@link #ICON_BITMAP}</th>
+ * <td>Bitmap</td>
+ * <td>The icon for the item. When this column value is not null, the values for the
+ * columns {@link #ICON_PACKAGE} and {@link #ICON_RESOURCE} must be null.</td>
+ * <td align="center">No</td>
+ * </tr>
+ * <tr><th>{@link #ICON_PACKAGE}</th>
+ * <td>String</td>
+ * <td>The package of the item's icon. When this value is not null, the value for the
+ * column {@link #ICON_RESOURCE} must be specified and the value for the column
+ * {@link #ICON_BITMAP} must be null.</td>
+ * <td align="center">No</td>
+ * </tr>
+ * <tr><th>{@link #ICON_RESOURCE}</th>
+ * <td>String</td>
+ * <td>The resource name of the item's icon. When this value is not null, the value for the
+ * column {@link #ICON_PACKAGE} must be specified and the value for the column
+ * {@link #ICON_BITMAP} must be null.</td>
+ * <td align="center">No</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ */
+public final class LiveFolders implements BaseColumns {
+ /**
+ * <p>Content provider column.</p>
+ * <p>Name of the live folder item.</p>
+ * <p>Required.</p>
+ * <p>Type: String.</p>
+ */
+ public static final String NAME = "name";
+
+ /**
+ * <p>Content provider column.</p>
+ * <p>Description of the live folder item. This value is ignored if the
+ * live folder's display mode is {@link LiveFolders#DISPLAY_MODE_GRID}.</p>
+ * <p>Optional.</p>
+ * <p>Type: String.</p>
+ *
+ * @see LiveFolders#EXTRA_LIVE_FOLDER_DISPLAY_MODE
+ */
+ public static final String DESCRIPTION = "description";
+
+ /**
+ * <p>Content provider column.</p>
+ * <p>Intent of the live folder item.</p>
+ * <p>Optional if the live folder has a base intent.</p>
+ * <p>Type: {@link android.content.Intent}.</p>
+ *
+ * @see LiveFolders#EXTRA_LIVE_FOLDER_BASE_INTENT
+ */
+ public static final String INTENT = "intent";
+
+ /**
+ * <p>Content provider column.</p>
+ * <p>Icon of the live folder item, as a custom bitmap.</p>
+ * <p>Optional.</p>
+ * <p>Type: {@link android.graphics.Bitmap}.</p>
+ */
+ public static final String ICON_BITMAP = "icon_bitmap";
+
+ /**
+ * <p>Content provider column.</p>
+ * <p>Package where to find the icon of the live folder item. This value can be
+ * obtained easily using
+ * {@link android.content.Intent.ShortcutIconResource#fromContext(android.content.Context, int)}.</p>
+ * <p>Optional.</p>
+ * <p>Type: String.</p>
+ *
+ * @see #ICON_RESOURCE
+ * @see android.content.Intent.ShortcutIconResource
+ */
+ public static final String ICON_PACKAGE = "icon_package";
+
+ /**
+ * <p>Content provider column.</p>
+ * <p>Resource name of the live folder item. This value can be obtained easily using
+ * {@link android.content.Intent.ShortcutIconResource#fromContext(android.content.Context, int)}.</p>
+ * <p>Optional.</p>
+ * <p>Type: String.</p>
+ *
+ * @see #ICON_PACKAGE
+ * @see android.content.Intent.ShortcutIconResource
+ */
+ public static final String ICON_RESOURCE = "icon_resource";
+
+ /**
+ * Displays a live folder's content in a grid.
+ *
+ * @see LiveFolders#EXTRA_LIVE_FOLDER_DISPLAY_MODE
+ */
+ public static final int DISPLAY_MODE_GRID = 0x1;
+
+ /**
+ * Displays a live folder's content in a list.
+ *
+ * @see LiveFolders#EXTRA_LIVE_FOLDER_DISPLAY_MODE
+ */
+ public static final int DISPLAY_MODE_LIST = 0x2;
+
+ /**
+ * The name of the extra used to define the name of a live folder.
+ *
+ * @see #ACTION_CREATE_LIVE_FOLDER
+ */
+ public static final String EXTRA_LIVE_FOLDER_NAME = "android.intent.extra.livefolder.NAME";
+
+ /**
+ * The name of the extra used to define the icon of a live folder.
+ *
+ * @see #ACTION_CREATE_LIVE_FOLDER
+ */
+ public static final String EXTRA_LIVE_FOLDER_ICON = "android.intent.extra.livefolder.ICON";
+
+ /**
+ * The name of the extra used to define the display mode of a live folder.
+ *
+ * @see #ACTION_CREATE_LIVE_FOLDER
+ * @see #DISPLAY_MODE_GRID
+ * @see #DISPLAY_MODE_LIST
+ */
+ public static final String EXTRA_LIVE_FOLDER_DISPLAY_MODE =
+ "android.intent.extra.livefolder.DISPLAY_MODE";
+
+ /**
+ * The name of the extra used to define the base Intent of a live folder.
+ *
+ * @see #ACTION_CREATE_LIVE_FOLDER
+ */
+ public static final String EXTRA_LIVE_FOLDER_BASE_INTENT =
+ "android.intent.extra.livefolder.BASE_INTENT";
+
+ /**
+ * Activity Action: Creates a live folder.
+ * <p>Input: Nothing.</p>
+ * <p>Output: An Intent representing the live folder. The intent must contain four
+ * extras: EXTRA_LIVE_FOLDER_NAME (value: String),
+ * EXTRA_LIVE_FOLDER_ICON (value: ShortcutIconResource),
+ * EXTRA_LIVE_FOLDER_URI (value: String) and
+ * EXTRA_LIVE_FOLDER_DISPLAY_MODE (value: int). The Intent can optionnally contain
+ * EXTRA_LIVE_FOLDER_BASE_INTENT (value: Intent).</p>
+ *
+ * @see #EXTRA_LIVE_FOLDER_NAME
+ * @see #EXTRA_LIVE_FOLDER_ICON
+ * @see #EXTRA_LIVE_FOLDER_DISPLAY_MODE
+ * @see #EXTRA_LIVE_FOLDER_BASE_INTENT
+ * @see android.content.Intent.ShortcutIconResource
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CREATE_LIVE_FOLDER =
+ "android.intent.action.CREATE_LIVE_FOLDER";
+
+ private LiveFolders() {
+ }
+}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index d99ad36..d4b728b 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -45,11 +45,70 @@ import java.text.Collator;
public final class MediaStore
{
private final static String TAG = "MediaStore";
-
+
public static final String AUTHORITY = "media";
-
+
private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/";
-
+
+ /**
+ * Activity Action: Perform a search for media.
+ * Contains at least the {@link android.app.SearchManager#QUERY} extra.
+ * May also contain any combination of the following extras:
+ * EXTRA_MEDIA_ARTIST, EXTRA_MEDIA_ALBUM, EXTRA_MEDIA_TITLE, EXTRA_MEDIA_FOCUS
+ *
+ * @see android.provider.MediaStore#EXTRA_MEDIA_ARTIST
+ * @see android.provider.MediaStore#EXTRA_MEDIA_ALBUM
+ * @see android.provider.MediaStore#EXTRA_MEDIA_TITLE
+ * @see android.provider.MediaStore#EXTRA_MEDIA_FOCUS
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String INTENT_ACTION_MEDIA_SEARCH = "android.intent.action.MEDIA_SEARCH";
+
+ /**
+ * The name of the Intent-extra used to define the artist
+ */
+ public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist";
+ /**
+ * The name of the Intent-extra used to define the album
+ */
+ public static final String EXTRA_MEDIA_ALBUM = "android.intent.extra.album";
+ /**
+ * The name of the Intent-extra used to define the song title
+ */
+ public static final String EXTRA_MEDIA_TITLE = "android.intent.extra.title";
+ /**
+ * The name of the Intent-extra used to define the search focus. The search focus
+ * indicates whether the search should be for things related to the artist, album
+ * or song that is identified by the other extras.
+ */
+ public static final String EXTRA_MEDIA_FOCUS = "android.intent.extra.focus";
+
+ /**
+ * The name of the Intent-extra used to control the orientation of a MovieView.
+ * This is an int property that overrides the MovieView activity's requestedOrientation.
+ * @see android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+ */
+ public static final String EXTRA_SCREEN_ORIENTATION = "android.intent.extra.screenOrientation";
+
+ /**
+ * The name of the Intent-extra used to control the onCompletion behavior of a MovieView.
+ * This is a boolean property that specifies whether or not to finish the MovieView activity
+ * when the movie completes playing. The default value is true, which means to automatically
+ * exit the movie player activity when the movie completes playing.
+ */
+ public static final String EXTRA_FINISH_ON_COMPLETION = "android.intent.extra.finishOnCompletion";
+
+ /**
+ * The name of the Intent action used to launch a camera in still image mode.
+ */
+ 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
@@ -57,11 +116,11 @@ public final class MediaStore
* @hide
*/
public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
-
- /**
+
+ /**
* Common fields for most MediaProvider tables
*/
-
+
public interface MediaColumns extends BaseColumns {
/**
* The data stream for the file
@@ -108,7 +167,7 @@ public final class MediaStore
*/
public static final String MIME_TYPE = "mime_type";
}
-
+
/**
* Contains meta data for all available images.
*/
@@ -120,19 +179,19 @@ public final class MediaStore
* <P>Type: TEXT</P>
*/
public static final String DESCRIPTION = "description";
-
+
/**
* The picasa id of the image
* <P>Type: TEXT</P>
*/
public static final String PICASA_ID = "picasa_id";
-
+
/**
* Whether the video should be published as public or private
* <P>Type: INTEGER</P>
*/
public static final String IS_PRIVATE = "isprivate";
-
+
/**
* The latitude where the image was captured.
* <P>Type: DOUBLE</P>
@@ -144,14 +203,14 @@ public final class MediaStore
* <P>Type: DOUBLE</P>
*/
public static final String LONGITUDE = "longitude";
-
+
/**
* The date & time that the image was taken in units
* of milliseconds since jan 1, 1970.
* <P>Type: INTEGER</P>
*/
public static final String DATE_TAKEN = "datetaken";
-
+
/**
* The orientation for the image expressed as degrees.
* Only degrees 0, 90, 180, 270 will work.
@@ -164,15 +223,17 @@ public final class MediaStore
* <P>Type: INTEGER</P>
*/
public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
-
+
/**
- * The bucket id of the image
+ * The bucket id of the image. This is a read-only property that
+ * is automatically computed from the DATA column.
* <P>Type: TEXT</P>
*/
public static final String BUCKET_ID = "bucket_id";
-
+
/**
- * The bucket display name of the image
+ * The bucket display name of the image. This is a read-only property that
+ * is automatically computed from the DATA column.
* <P>Type: TEXT</P>
*/
public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
@@ -183,14 +244,14 @@ public final class MediaStore
{
return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
}
-
+
public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
String where, String orderBy)
{
return cr.query(uri, projection, where,
null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
}
-
+
public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
String selection, String [] selectionArgs, String orderBy)
{
@@ -200,7 +261,7 @@ public final class MediaStore
/**
* Retrieves an image for the given url as a {@link Bitmap}.
- *
+ *
* @param cr The content resolver to use
* @param url The url of the image
* @throws FileNotFoundException
@@ -214,10 +275,10 @@ public final class MediaStore
input.close();
return bitmap;
}
-
+
/**
* Insert an image and create a thumbnail for it.
- *
+ *
* @param cr The content resolver to use
* @param imagePath The path to the image to insert
* @param name The name of the image
@@ -239,38 +300,38 @@ public final class MediaStore
}
}
}
-
+
private static final Bitmap StoreThumbnail(
- ContentResolver cr,
+ ContentResolver cr,
Bitmap source,
long id,
- float width, float height,
+ float width, float height,
int kind) {
// create the matrix to scale it
Matrix matrix = new Matrix();
-
+
float scaleX = width / source.getWidth();
float scaleY = height / source.getHeight();
-
+
matrix.setScale(scaleX, scaleY);
-
- Bitmap thumb = Bitmap.createBitmap(source, 0, 0,
+
+ Bitmap thumb = Bitmap.createBitmap(source, 0, 0,
source.getWidth(),
source.getHeight(), matrix,
true);
-
+
ContentValues values = new ContentValues(4);
values.put(Images.Thumbnails.KIND, kind);
values.put(Images.Thumbnails.IMAGE_ID, (int)id);
values.put(Images.Thumbnails.HEIGHT, thumb.getHeight());
values.put(Images.Thumbnails.WIDTH, thumb.getWidth());
-
+
Uri url = cr.insert(Images.Thumbnails.EXTERNAL_CONTENT_URI, values);
-
+
try {
OutputStream thumbOut = cr.openOutputStream(url);
-
- thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut);
+
+ thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut);
thumbOut.close();
return thumb;
}
@@ -281,10 +342,10 @@ public final class MediaStore
return null;
}
}
-
+
/**
* Insert an image and create a thumbnail for it.
- *
+ *
* @param cr The content resolver to use
* @param source The stream to use for the image
* @param title The name of the image
@@ -306,7 +367,7 @@ public final class MediaStore
try
{
url = cr.insert(EXTERNAL_CONTENT_URI, values);
-
+
if (source != null) {
OutputStream imageOut = cr.openOutputStream(url);
try {
@@ -337,11 +398,11 @@ public final class MediaStore
return stringUrl;
}
-
+
/**
- * Get the content:// style URI for the image media table on the
+ * Get the content:// style URI for the image media table on the
* given volume.
- *
+ *
* @param volumeName the name of the volume to get the URI for
* @return the URI to the image media table on the given volume
*/
@@ -355,7 +416,7 @@ public final class MediaStore
*/
public static final Uri INTERNAL_CONTENT_URI =
getContentUri("internal");
-
+
/**
* The content:// style URI for the "primary" external storage
* volume.
@@ -369,7 +430,7 @@ public final class MediaStore
* image MIME type as appropriate -- for example, image/jpeg.
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image";
-
+
/**
* The default sort order for this table
*/
@@ -382,23 +443,23 @@ public final class MediaStore
{
return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
}
-
+
public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind, String[] projection)
{
return cr.query(uri, projection, "kind = " + kind, null, DEFAULT_SORT_ORDER);
}
-
+
public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind, String[] projection)
{
return cr.query(EXTERNAL_CONTENT_URI, projection,
IMAGE_ID + " = " + origId + " AND " + KIND + " = " +
kind, null, null);
}
-
+
/**
- * Get the content:// style URI for the image media table on the
+ * Get the content:// style URI for the image media table on the
* given volume.
- *
+ *
* @param volumeName the name of the volume to get the URI for
* @return the URI to the image media table on the given volume
*/
@@ -406,13 +467,13 @@ public final class MediaStore
return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
"/images/thumbnails");
}
-
+
/**
* The content:// style URI for the internal storage.
*/
public static final Uri INTERNAL_CONTENT_URI =
getContentUri("internal");
-
+
/**
* The content:// style URI for the "primary" external storage
* volume.
@@ -424,35 +485,35 @@ public final class MediaStore
* The default sort order for this table
*/
public static final String DEFAULT_SORT_ORDER = "image_id ASC";
-
+
/**
* The data stream for the thumbnail
* <P>Type: DATA STREAM</P>
*/
public static final String DATA = "_data";
-
+
/**
* The original image for the thumbnal
* <P>Type: INTEGER (ID from Images table)</P>
*/
public static final String IMAGE_ID = "image_id";
-
+
/**
* The kind of the thumbnail
* <P>Type: INTEGER (One of the values below)</P>
*/
public static final String KIND = "kind";
-
+
public static final int MINI_KIND = 1;
public static final int FULL_SCREEN_KIND = 2;
public static final int MICRO_KIND = 3;
-
+
/**
* The width of the thumbnal
* <P>Type: INTEGER (long)</P>
*/
public static final String WIDTH = "width";
-
+
/**
* The height of the thumbnail
* <P>Type: INTEGER (long)</P>
@@ -460,7 +521,7 @@ public final class MediaStore
public static final String HEIGHT = "height";
}
}
-
+
/**
* Container for all audio content.
*/
@@ -532,7 +593,7 @@ public final class MediaStore
* <P>Type: TEXT</P>
*/
public static final String ALBUM_ART = "album_art";
-
+
/**
* The track number of this song on the album, if any.
* This number encodes both the track number and the
@@ -632,9 +693,9 @@ public final class MediaStore
public static final class Media implements AudioColumns {
/**
- * Get the content:// style URI for the audio media table on the
+ * Get the content:// style URI for the audio media table on the
* given volume.
- *
+ *
* @param volumeName the name of the volume to get the URI for
* @return the URI to the audio media table on the given volume
*/
@@ -642,46 +703,56 @@ public final class MediaStore
return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
"/audio/media");
}
-
+
public static Uri getContentUriForPath(String path) {
return (path.startsWith(Environment.getExternalStorageDirectory().getPath()) ?
EXTERNAL_CONTENT_URI : INTERNAL_CONTENT_URI);
}
-
+
/**
* The content:// style URI for the internal storage.
*/
public static final Uri INTERNAL_CONTENT_URI =
getContentUri("internal");
-
+
/**
* The content:// style URI for the "primary" external storage
* volume.
*/
public static final Uri EXTERNAL_CONTENT_URI =
getContentUri("external");
-
+
/**
* The MIME type for this table.
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio";
-
+
/**
* The default sort order for this table
*/
public static final String DEFAULT_SORT_ORDER = TITLE;
-
+
/**
* Activity Action: Start SoundRecorder application.
* <p>Input: nothing.
* <p>Output: An uri to the recorded sound stored in the Media Library
* if the recording was successful.
- *
+ * May also contain the extra EXTRA_MAX_BYTES.
+ * @see #EXTRA_MAX_BYTES
*/
- public static final String RECORD_SOUND_ACTION =
+ public static final String RECORD_SOUND_ACTION =
"android.provider.MediaStore.RECORD_SOUND";
+
+ /**
+ * The name of the Intent-extra used to define a maximum file size for
+ * a recording made by the SoundRecorder application.
+ *
+ * @see #RECORD_SOUND_ACTION
+ */
+ public static final String EXTRA_MAX_BYTES =
+ "android.provider.MediaStore.extra.MAX_BYTES";
}
-
+
/**
* Columns representing an audio genre
*/
@@ -698,9 +769,9 @@ public final class MediaStore
*/
public static final class Genres implements BaseColumns, GenresColumns {
/**
- * Get the content:// style URI for the audio genres table on the
+ * Get the content:// style URI for the audio genres table on the
* given volume.
- *
+ *
* @param volumeName the name of the volume to get the URI for
* @return the URI to the audio genres table on the given volume
*/
@@ -714,7 +785,7 @@ public final class MediaStore
*/
public static final Uri INTERNAL_CONTENT_URI =
getContentUri("internal");
-
+
/**
* The content:// style URI for the "primary" external storage
* volume.
@@ -726,7 +797,7 @@ public final class MediaStore
* The MIME type for this table.
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/genre";
-
+
/**
* The MIME type for entries in this table.
*/
@@ -794,7 +865,7 @@ public final class MediaStore
* <P>Type: INTEGER (long)</P>
*/
public static final String DATE_ADDED = "date_added";
-
+
/**
* The time the file was last modified
* Units are seconds since 1970.
@@ -803,16 +874,16 @@ public final class MediaStore
*/
public static final String DATE_MODIFIED = "date_modified";
}
-
+
/**
* Contains playlists for audio files
*/
public static final class Playlists implements BaseColumns,
PlaylistsColumns {
/**
- * Get the content:// style URI for the audio playlists table on the
+ * Get the content:// style URI for the audio playlists table on the
* given volume.
- *
+ *
* @param volumeName the name of the volume to get the URI for
* @return the URI to the audio playlists table on the given volume
*/
@@ -826,7 +897,7 @@ public final class MediaStore
*/
public static final Uri INTERNAL_CONTENT_URI =
getContentUri("internal");
-
+
/**
* The content:// style URI for the "primary" external storage
* volume.
@@ -838,7 +909,7 @@ public final class MediaStore
* The MIME type for this table.
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/playlist";
-
+
/**
* The MIME type for entries in this table.
*/
@@ -863,7 +934,7 @@ public final class MediaStore
* The ID within the playlist.
*/
public static final String _ID = "_id";
-
+
/**
* A subdirectory of each playlist containing all member audio
* files.
@@ -881,7 +952,7 @@ public final class MediaStore
* <P>Type: INTEGER (long)</P>
*/
public static final String PLAYLIST_ID = "playlist_id";
-
+
/**
* The order of the songs in the playlist
* <P>Type: INTEGER (long)></P>
@@ -922,15 +993,15 @@ public final class MediaStore
*/
public static final String NUMBER_OF_TRACKS = "number_of_tracks";
}
-
+
/**
* Contains artists for audio files
*/
public static final class Artists implements BaseColumns, ArtistColumns {
/**
- * Get the content:// style URI for the artists table on the
+ * Get the content:// style URI for the artists table on the
* given volume.
- *
+ *
* @param volumeName the name of the volume to get the URI for
* @return the URI to the audio artists table on the given volume
*/
@@ -944,7 +1015,7 @@ public final class MediaStore
*/
public static final Uri INTERNAL_CONTENT_URI =
getContentUri("internal");
-
+
/**
* The content:// style URI for the "primary" external storage
* volume.
@@ -956,7 +1027,7 @@ public final class MediaStore
* The MIME type for this table.
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/artists";
-
+
/**
* The MIME type for entries in this table.
*/
@@ -979,7 +1050,7 @@ public final class MediaStore
}
}
}
-
+
/**
* Columns representing an album
*/
@@ -1010,6 +1081,16 @@ public final class MediaStore
public static final String NUMBER_OF_SONGS = "numsongs";
/**
+ * This column is available when getting album info via artist,
+ * and indicates the number of songs on the album by the given
+ * artist.
+ * <P>Type: INTEGER</P>
+ *
+ * @hide pending API Council approval
+ */
+ public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist";
+
+ /**
* The year in which the earliest and latest songs
* on this album were released. These will often
* be the same, but for compilation albums they
@@ -1025,22 +1106,22 @@ public final class MediaStore
* <P>Type: TEXT</P>
*/
public static final String ALBUM_KEY = "album_key";
-
+
/**
* Cached album art.
* <P>Type: TEXT</P>
*/
public static final String ALBUM_ART = "album_art";
}
-
+
/**
* Contains artists for audio files
*/
public static final class Albums implements BaseColumns, AlbumColumns {
/**
- * Get the content:// style URI for the albums table on the
+ * Get the content:// style URI for the albums table on the
* given volume.
- *
+ *
* @param volumeName the name of the volume to get the URI for
* @return the URI to the audio albums table on the given volume
*/
@@ -1054,7 +1135,7 @@ public final class MediaStore
*/
public static final Uri INTERNAL_CONTENT_URI =
getContentUri("internal");
-
+
/**
* The content:// style URI for the "primary" external storage
* volume.
@@ -1066,7 +1147,7 @@ public final class MediaStore
* The MIME type for this table.
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/albums";
-
+
/**
* The MIME type for entries in this table.
*/
@@ -1145,7 +1226,7 @@ public final class MediaStore
* <P>Type: TEXT</P>
*/
public static final String LANGUAGE = "language";
-
+
/**
* The latitude where the image was captured.
* <P>Type: DOUBLE</P>
@@ -1157,7 +1238,7 @@ public final class MediaStore
* <P>Type: DOUBLE</P>
*/
public static final String LONGITUDE = "longitude";
-
+
/**
* The date & time that the image was taken in units
* of milliseconds since jan 1, 1970.
@@ -1170,13 +1251,27 @@ public final class MediaStore
* <P>Type: INTEGER</P>
*/
public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
+
+ /**
+ * The bucket id of the video. This is a read-only property that
+ * is automatically computed from the DATA column.
+ * <P>Type: TEXT</P>
+ */
+ public static final String BUCKET_ID = "bucket_id";
+
+ /**
+ * The bucket display name of the video. This is a read-only property that
+ * is automatically computed from the DATA column.
+ * <P>Type: TEXT</P>
+ */
+ public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
}
public static final class Media implements VideoColumns {
/**
- * Get the content:// style URI for the video media table on the
+ * Get the content:// style URI for the video media table on the
* given volume.
- *
+ *
* @param volumeName the name of the volume to get the URI for
* @return the URI to the video media table on the given volume
*/
@@ -1190,7 +1285,7 @@ public final class MediaStore
*/
public static final Uri INTERNAL_CONTENT_URI =
getContentUri("internal");
-
+
/**
* The content:// style URI for the "primary" external storage
* volume.
diff --git a/core/java/android/provider/SearchRecentSuggestions.java b/core/java/android/provider/SearchRecentSuggestions.java
index 1439b26..0632d94 100644
--- a/core/java/android/provider/SearchRecentSuggestions.java
+++ b/core/java/android/provider/SearchRecentSuggestions.java
@@ -190,6 +190,11 @@ public class SearchRecentSuggestions {
/**
* Completely delete the history. Use this call to implement a "clear history" UI.
+ *
+ * Any application that implements search suggestions based on previous actions (such as
+ * recent queries, page/items viewed, etc.) should provide a way for the user to clear the
+ * history. This gives the user a measure of privacy, if they do not wish for their recent
+ * searches to be replayed by other users of the device (via suggestions).
*/
public void clearHistory() {
ContentResolver cr = mContext.getContentResolver();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6897bd5..e93bbeb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -40,6 +40,7 @@ import android.util.Log;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
+import java.util.HashSet;
/**
@@ -100,6 +101,20 @@ public final class Settings {
"android.settings.WIRELESS_SETTINGS";
/**
+ * Activity Action: Show settings to allow entering/exiting airplane mode.
+ * <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_AIRPLANE_MODE_SETTINGS =
+ "android.settings.AIRPLANE_MODE_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of security and
* location privacy.
* <p>
@@ -116,6 +131,7 @@ public final class Settings {
/**
* Activity Action: Show settings to allow configuration of Wi-Fi.
+
* <p>
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
@@ -123,10 +139,26 @@ public final class Settings {
* Input: Nothing.
* <p>
* Output: Nothing.
+
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_WIFI_SETTINGS =
"android.settings.WIFI_SETTINGS";
+
+ /**
+ * Activity Action: Show settings to allow configuration of a static IP
+ * address for Wi-Fi.
+ * <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_WIFI_IP_SETTINGS =
+ "android.settings.WIFI_IP_SETTINGS";
/**
* Activity Action: Show settings to allow configuration of Bluetooth.
@@ -213,7 +245,50 @@ public final class Settings {
"android.settings.APPLICATION_SETTINGS";
/**
- * Activity Action: Show settings to allow configuration of sync settings.
+ * Activity Action: Show settings to allow configuration of application
+ * development-related settings.
+ * <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_APPLICATION_DEVELOPMENT_SETTINGS =
+ "android.settings.APPLICATION_DEVELOPMENT_SETTINGS";
+
+ /**
+ * Activity Action: Show settings to allow configuration of quick launch shortcuts.
+ * <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_QUICK_LAUNCH_SETTINGS =
+ "android.settings.QUICK_LAUNCH_SETTINGS";
+
+ /**
+ * Activity Action: Show settings to manage installed applications.
+ * <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_MANAGE_APPLICATIONS_SETTINGS =
+ "android.settings.MANAGE_APPLICATIONS_SETTINGS";
+
+ /**
+ * Activity Action: Show settings for system update functionality.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
@@ -225,9 +300,78 @@ public final class Settings {
* @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SYSTEM_UPDATE_SETTINGS =
+ "android.settings.SYSTEM_UPDATE_SETTINGS";
+
+ /**
+ * Activity Action: Show settings to allow configuration of sync settings.
+ * <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_SYNC_SETTINGS =
"android.settings.SYNC_SETTINGS";
+ /**
+ * Activity Action: Show settings for selecting the network operator.
+ * <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_NETWORK_OPERATOR_SETTINGS =
+ "android.settings.NETWORK_OPERATOR_SETTINGS";
+
+ /**
+ * Activity Action: Show settings for selection of 2G/3G.
+ * <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_DATA_ROAMING_SETTINGS =
+ "android.settings.DATA_ROAMING_SETTINGS";
+
+ /**
+ * Activity Action: Show settings for internal storage.
+ * <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_INTERNAL_STORAGE_SETTINGS =
+ "android.settings.INTERNAL_STORAGE_SETTINGS";
+ /**
+ * Activity Action: Show settings for memory card storage.
+ * <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_MEMORY_CARD_SETTINGS =
+ "android.settings.MEMORY_CARD_SETTINGS";
+
// End of Intent actions for Settings
private static final String JID_RESOURCE_PREFIX = "android";
@@ -246,8 +390,6 @@ public final class Settings {
/**
* Common base for tables of name/value settings.
- *
- *
*/
public static class NameValueTable implements BaseColumns {
public static final String NAME = "name";
@@ -296,7 +438,7 @@ public final class Settings {
try {
c = cr.query(mUri, new String[] { Settings.NameValueTable.VALUE },
Settings.NameValueTable.NAME + "=?", new String[]{name}, null);
- if (c.moveToNext()) value = c.getString(0);
+ if (c != null && c.moveToNext()) value = c.getString(0);
mValues.put(name, value);
} catch (SQLException e) {
// SQL error: return null, but don't cache it.
@@ -320,6 +462,41 @@ public final class Settings {
public static final String SYS_PROP_SETTING_VERSION = "sys.settings_system_version";
private static volatile NameValueCache mNameValueCache = null;
+
+ private static final HashSet<String> MOVED_TO_SECURE;
+ static {
+ MOVED_TO_SECURE = new HashSet<String>(30);
+ MOVED_TO_SECURE.add(Secure.ADB_ENABLED);
+ MOVED_TO_SECURE.add(Secure.ANDROID_ID);
+ MOVED_TO_SECURE.add(Secure.BLUETOOTH_ON);
+ MOVED_TO_SECURE.add(Secure.DATA_ROAMING);
+ MOVED_TO_SECURE.add(Secure.DEVICE_PROVISIONED);
+ MOVED_TO_SECURE.add(Secure.HTTP_PROXY);
+ MOVED_TO_SECURE.add(Secure.INSTALL_NON_MARKET_APPS);
+ MOVED_TO_SECURE.add(Secure.LOCATION_PROVIDERS_ALLOWED);
+ MOVED_TO_SECURE.add(Secure.LOGGING_ID);
+ MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_ENABLED);
+ MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_LAST_UPDATE);
+ MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_REDIRECT_URL);
+ MOVED_TO_SECURE.add(Secure.SETTINGS_CLASSNAME);
+ MOVED_TO_SECURE.add(Secure.USB_MASS_STORAGE_ENABLED);
+ MOVED_TO_SECURE.add(Secure.USE_GOOGLE_MAIL);
+ MOVED_TO_SECURE.add(Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON);
+ MOVED_TO_SECURE.add(Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY);
+ MOVED_TO_SECURE.add(Secure.WIFI_NUM_OPEN_NETWORKS_KEPT);
+ MOVED_TO_SECURE.add(Secure.WIFI_ON);
+ MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE);
+ MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_AP_COUNT);
+ MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS);
+ MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED);
+ MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS);
+ MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT);
+ MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_MAX_AP_CHECKS);
+ MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_ON);
+ MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_PING_COUNT);
+ MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_PING_DELAY_MS);
+ MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS);
+ }
/**
* Look up a name in the database.
@@ -328,6 +505,11 @@ public final class Settings {
* @return the corresponding value, or null if not present
*/
public synchronized static String getString(ContentResolver resolver, String name) {
+ if (MOVED_TO_SECURE.contains(name)) {
+ Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ + " to android.provider.Settings.Secure, returning read-only value.");
+ return Secure.getString(resolver, name);
+ }
if (mNameValueCache == null) {
mNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI);
}
@@ -341,8 +523,12 @@ public final class Settings {
* @param value to associate with the name
* @return true if the value was set, false on database errors
*/
- public static boolean putString(ContentResolver resolver,
- String name, String value) {
+ public static boolean putString(ContentResolver resolver, String name, String value) {
+ if (MOVED_TO_SECURE.contains(name)) {
+ Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ + " to android.provider.Settings.Secure, value is unchanged.");
+ return false;
+ }
return putString(resolver, CONTENT_URI, name, value);
}
@@ -353,6 +539,11 @@ public final class Settings {
* @return the corresponding content URI, or null if not present
*/
public static Uri getUriFor(String name) {
+ if (MOVED_TO_SECURE.contains(name)) {
+ Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ + " to android.provider.Settings.Secure, returning Secure URI.");
+ return Secure.getUriFor(Secure.CONTENT_URI, name);
+ }
return getUriFor(CONTENT_URI, name);
}
@@ -426,6 +617,75 @@ public final class Settings {
/**
* Convenience function for retrieving a single system settings value
+ * as a {@code long}. Note that internally setting values are always
+ * stored as strings; this function converts the string to a {@code long}
+ * for you. The default value will be returned if the setting is
+ * not defined or not a {@code long}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid {@code long}.
+ */
+ public static long getLong(ContentResolver cr, String name, long def) {
+ String valString = getString(cr, name);
+ long value;
+ try {
+ value = valString != null ? Long.parseLong(valString) : def;
+ } catch (NumberFormatException e) {
+ value = def;
+ }
+ return value;
+ }
+
+ /**
+ * Convenience function for retrieving a single system settings value
+ * as a {@code long}. Note that internally setting values are always
+ * stored as strings; this function converts the string to a {@code long}
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link SettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @return The setting's current value.
+ * @throws SettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not an integer.
+ */
+ public static long getLong(ContentResolver cr, String name)
+ throws SettingNotFoundException {
+ String valString = getString(cr, name);
+ try {
+ return Long.parseLong(valString);
+ } catch (NumberFormatException e) {
+ throw new SettingNotFoundException(name);
+ }
+ }
+
+ /**
+ * Convenience function for updating a single settings value as a long
+ * integer. This will either create a new entry in the table if the
+ * given name does not exist, or modify the value of the existing row
+ * with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a
+ * string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putLong(ContentResolver cr, String name, long value) {
+ return putString(cr, name, Long.toString(value));
+ }
+
+ /**
+ * Convenience function for retrieving a single system settings value
* as a floating point number. Note that internally setting values are
* always stored as strings; this function converts the string to an
* float for you. The default value will be returned if the setting
@@ -536,7 +796,13 @@ public final class Settings {
/**
* Whether we keep the device on while the device is plugged in.
- * 0=no 1=yes
+ * Supported values are:
+ * <ul>
+ * <li>{@code 0} to never stay on while plugged in</li>
+ * <li>{@link BatteryManager#BATTERY_PLUGGED_AC} to stay on for AC charger</li>
+ * <li>{@link BatteryManager#BATTERY_PLUGGED_USB} to stay on for USB charger</li>
+ * </ul>
+ * These values can be OR-ed together.
*/
public static final String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
@@ -580,104 +846,15 @@ public final class Settings {
public static final String AIRPLANE_MODE_RADIOS = "airplane_mode_radios";
/**
- * Whether the Wi-Fi should be on. Only the Wi-Fi service should touch this.
- */
- public static final String WIFI_ON = "wifi_on";
-
- /**
- * Whether to notify the user of open networks.
- * <p>
- * If not connected and the scan results have an open network, we will
- * put this notification up. If we attempt to connect to a network or
- * the open network(s) disappear, we remove the notification. When we
- * show the notification, we will not show it again for
- * {@link #WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} time.
- */
- public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
- "wifi_networks_available_notification_on";
-
- /**
- * Delay (in seconds) before repeating the Wi-Fi networks available notification.
- * Connecting to a network will reset the timer.
- */
- public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY =
- "wifi_networks_available_repeat_delay";
-
- /**
- * When the number of open networks exceeds this number, the
- * least-recently-used excess networks will be removed.
- */
- public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept";
-
- /**
- * Whether the Wi-Fi watchdog is enabled.
- */
- public static final String WIFI_WATCHDOG_ON = "wifi_watchdog_on";
-
- /**
- * The number of access points required for a network in order for the
- * watchdog to monitor it.
- */
- public static final String WIFI_WATCHDOG_AP_COUNT = "wifi_watchdog_ap_count";
-
- /**
- * The number of initial pings to perform that *may* be ignored if they
- * fail. Again, if these fail, they will *not* be used in packet loss
- * calculation. For example, one network always seemed to time out for
- * the first couple pings, so this is set to 3 by default.
- */
- public static final String WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT = "wifi_watchdog_initial_ignored_ping_count";
-
- /**
- * The number of pings to test if an access point is a good connection.
- */
- public static final String WIFI_WATCHDOG_PING_COUNT = "wifi_watchdog_ping_count";
-
- /**
- * The timeout per ping.
- */
- public static final String WIFI_WATCHDOG_PING_TIMEOUT_MS = "wifi_watchdog_ping_timeout_ms";
-
- /**
- * The delay between pings.
- */
- public static final String WIFI_WATCHDOG_PING_DELAY_MS = "wifi_watchdog_ping_delay_ms";
-
- /**
- * The acceptable packet loss percentage (range 0 - 100) before trying
- * another AP on the same network.
- */
- public static final String WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE =
- "wifi_watchdog_acceptable_packet_loss_percentage";
-
- /**
- * The maximum number of access points (per network) to attempt to test.
- * If this number is reached, the watchdog will no longer monitor the
- * initial connection state for the network. This is a safeguard for
- * networks containing multiple APs whose DNS does not respond to pings.
- */
- public static final String WIFI_WATCHDOG_MAX_AP_CHECKS = "wifi_watchdog_max_ap_checks";
-
- /**
- * Whether the Wi-Fi watchdog is enabled for background checking even
- * after it thinks the user has connected to a good access point.
+ * 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_WATCHDOG_BACKGROUND_CHECK_ENABLED =
- "wifi_watchdog_background_check_enabled";
+ public static final String WIFI_IDLE_MS = "wifi_idle_ms";
/**
- * The timeout for a background ping
- */
- public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS =
- "wifi_watchdog_background_check_timeout_ms";
-
- /**
- * The delay between background checks.
- */
- public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS =
- "wifi_watchdog_background_check_delay_ms";
-
- /**
* Whether to use static IP and other static network attributes.
* <p>
* Set to 1 for true and 0 for false.
@@ -720,16 +897,11 @@ public final class Settings {
public static final String WIFI_STATIC_DNS2 = "wifi_static_dns2";
/**
- * User preference for which network(s) should be used. Only the
- * connectivity service should touch this.
- */
- public static final String NETWORK_PREFERENCE = "network_preference";
-
- /**
- * Whether bluetooth is enabled/disabled
- * 0=disabled. 1=enabled.
+ * The number of radio channels that are allowed in the local
+ * 802.11 regulatory domain.
+ * @hide
*/
- public static final String BLUETOOTH_ON = "bluetooth_on";
+ public static final String WIFI_NUM_ALLOWED_CHANNELS = "wifi_num_allowed_channels";
/**
* Determines whether remote devices may discover and/or connect to
@@ -756,14 +928,15 @@ public final class Settings {
public static final String LOCK_PATTERN_ENABLED = "lock_pattern_autolock";
/**
- * Whether the device has been provisioned (0 = false, 1 = true)
+ * Whether lock pattern is visible as user enters (0 = false, 1 = true)
*/
- public static final String DEVICE_PROVISIONED = "device_provisioned";
+ public static final String LOCK_PATTERN_VISIBLE = "lock_pattern_visible_pattern";
/**
- * Whether lock pattern is visible as user enters (0 = false, 1 = true)
+ * Whether lock pattern will vibrate as user enters (0 = false, 1 = true)
*/
- public static final String LOCK_PATTERN_VISIBLE = "lock_pattern_visible_pattern";
+ public static final String LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED =
+ "lock_pattern_tactile_feedback_enabled";
/**
@@ -773,16 +946,6 @@ public final class Settings {
public static final String NEXT_ALARM_FORMATTED = "next_alarm_formatted";
/**
- * Comma-separated list of location providers that activities may access.
- */
- public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
-
- /**
- * Whether or not data roaming is enabled. (0 = false, 1 = true)
- */
- public static final String DATA_ROAMING = "data_roaming";
-
- /**
* Scaling factor for fonts, float.
*/
public static final String FONT_SCALE = "font_scale";
@@ -884,10 +1047,33 @@ public final class Settings {
public static final String VOLUME_ALARM = "volume_alarm";
/**
+ * Notification volume. This is used internally, changing this
+ * value will not change the volume. See AudioManager.
+ */
+ public static final String VOLUME_NOTIFICATION = "volume_notification";
+
+ /**
+ * Whether the notifications should use the ring volume (value of 1) or
+ * a separate notification volume (value of 0). In most cases, users
+ * will have this enabled so the notification and ringer volumes will be
+ * the same. However, power users can disable this and use the separate
+ * notification volume control.
+ * <p>
+ * Note: This is a one-off setting that will be removed in the future
+ * when there is profile support. For this reason, it is kept hidden
+ * from the public APIs.
+ *
+ * @hide
+ */
+ public static final String NOTIFICATIONS_USE_RING_VOLUME =
+ "notifications_use_ring_volume";
+
+ /**
* The mapping of stream type (integer) to its setting.
*/
public static final String[] VOLUME_SETTINGS = {
- VOLUME_VOICE, VOLUME_SYSTEM, VOLUME_RING, VOLUME_MUSIC, VOLUME_ALARM
+ VOLUME_VOICE, VOLUME_SYSTEM, VOLUME_RING, VOLUME_MUSIC,
+ VOLUME_ALARM, VOLUME_NOTIFICATION
};
/**
@@ -949,16 +1135,11 @@ public final class Settings {
* feature converts two spaces to a "." and space.
*/
public static final String TEXT_AUTO_PUNCTUATE = "auto_punctuate";
-
+
/**
* Setting to showing password characters in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_SHOW_PASSWORD = "show_password";
- /**
- * USB Mass Storage Enabled
- */
- public static final String USB_MASS_STORAGE_ENABLED =
- "usb_mass_storage_enabled";
public static final String SHOW_GTALK_SERVICE_STATUS =
"SHOW_GTALK_SERVICE_STATUS";
@@ -969,11 +1150,6 @@ public final class Settings {
public static final String WALLPAPER_ACTIVITY = "wallpaper_activity";
/**
- * Host name and port for a user-selected proxy.
- */
- public static final String HTTP_PROXY = "http_proxy";
-
- /**
* Value to specify if the user prefers the date, time and time zone
* to be automatically fetched from the network (NITZ). 1=yes, 0=no
*/
@@ -995,13 +1171,6 @@ public final class Settings {
public static final String DATE_FORMAT = "date_format";
/**
- * Settings classname to launch when Settings is clicked from All
- * Applications. Needed because of user testing between the old
- * and new Settings apps. TODO: 881807
- */
- public static final String SETTINGS_CLASSNAME = "settings_classname";
-
- /**
* Whether the setup wizard has been run before (on first boot), or if
* it still needs to be run.
*
@@ -1011,85 +1180,279 @@ public final class Settings {
public static final String SETUP_WIZARD_HAS_RUN = "setup_wizard_has_run";
/**
- * The Android ID (a unique 64-bit value) as a hex string.
- * Identical to that obtained by calling
- * GoogleLoginService.getAndroidId(); it is also placed here
- * so you can get it without binding to a service.
+ * Scaling factor for normal window animations. Setting to 0 will disable window
+ * animations.
*/
- public static final String ANDROID_ID = "android_id";
+ public static final String WINDOW_ANIMATION_SCALE = "window_animation_scale";
/**
- * The Logging ID (a unique 64-bit value) as a hex string.
- * Used as a pseudonymous identifier for logging.
+ * Scaling factor for activity transition animations. Setting to 0 will disable window
+ * animations.
*/
- public static final String LOGGING_ID = "logging_id";
+ public static final String TRANSITION_ANIMATION_SCALE = "transition_animation_scale";
/**
- * If this setting is set (to anything), then all references
- * to Gmail on the device must change to Google Mail.
+ * 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
+ * disabled by the application.
*/
- public static final String USE_GOOGLE_MAIL = "use_google_mail";
+ public static final String ACCELEROMETER_ROTATION = "accelerometer_rotation";
/**
- * Whether the package installer should allow installation of apps downloaded from
- * sources other than the Android Market (vending machine).
- *
- * 1 = allow installing from other sources
- * 0 = only allow installing from the Android Market
+ * Whether the audible DTMF tones are played by the dialer when dialing. The value is
+ * boolean (1 or 0).
*/
- public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
+ public static final String DTMF_TONE_WHEN_DIALING = "dtmf_tone";
/**
- * Scaling factor for normal window animations. Setting to 0 will disable window
- * animations.
+ * Whether the sounds effects (key clicks, lid open ...) are enabled. The value is
+ * boolean (1 or 0).
*/
- public static final String WINDOW_ANIMATION_SCALE = "window_animation_scale";
+ public static final String SOUND_EFFECTS_ENABLED = "sound_effects_enabled";
+
+ // Settings moved to Settings.Secure
/**
- * Scaling factor for activity transition animations. Setting to 0 will disable window
- * animations.
+ * @deprecated Use {@link android.provider.Settings.Secure#LOCATION_PROVIDERS_ALLOWED}
+ * instead
*/
- public static final String TRANSITION_ANIMATION_SCALE = "transition_animation_scale";
+ @Deprecated
+ public static final String ADB_ENABLED = Secure.ADB_ENABLED;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#ANDROID_ID} instead
+ */
+ @Deprecated
+ public static final String ANDROID_ID = Secure.ANDROID_ID;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#BLUETOOTH_ON} instead
+ */
+ @Deprecated
+ public static final String BLUETOOTH_ON = Secure.BLUETOOTH_ON;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#DATA_ROAMING} instead
+ */
+ @Deprecated
+ public static final String DATA_ROAMING = Secure.DATA_ROAMING;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#DEVICE_PROVISIONED} instead
+ */
+ @Deprecated
+ public static final String DEVICE_PROVISIONED = Secure.DEVICE_PROVISIONED;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#HTTP_PROXY} instead
+ */
+ @Deprecated
+ public static final String HTTP_PROXY = Secure.HTTP_PROXY;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#INSTALL_NON_MARKET_APPS} instead
+ */
+ @Deprecated
+ public static final String INSTALL_NON_MARKET_APPS = Secure.INSTALL_NON_MARKET_APPS;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#LOCATION_PROVIDERS_ALLOWED}
+ * instead
+ */
+ @Deprecated
+ public static final String LOCATION_PROVIDERS_ALLOWED = Secure.LOCATION_PROVIDERS_ALLOWED;
- public static final String PARENTAL_CONTROL_ENABLED =
- "parental_control_enabled";
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#LOGGING_ID} instead
+ */
+ @Deprecated
+ public static final String LOGGING_ID = Secure.LOGGING_ID;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#NETWORK_PREFERENCE} instead
+ */
+ @Deprecated
+ public static final String NETWORK_PREFERENCE = Secure.NETWORK_PREFERENCE;
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#PARENTAL_CONTROL_ENABLED}
+ * instead
+ */
+ @Deprecated
+ public static final String PARENTAL_CONTROL_ENABLED = Secure.PARENTAL_CONTROL_ENABLED;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#PARENTAL_CONTROL_LAST_UPDATE}
+ * instead
+ */
+ @Deprecated
+ public static final String PARENTAL_CONTROL_LAST_UPDATE = Secure.PARENTAL_CONTROL_LAST_UPDATE;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#PARENTAL_CONTROL_REDIRECT_URL}
+ * instead
+ */
+ @Deprecated
public static final String PARENTAL_CONTROL_REDIRECT_URL =
- "parental_control_redirect_url";
+ Secure.PARENTAL_CONTROL_REDIRECT_URL;
- public static final String PARENTAL_CONTROL_LAST_UPDATE =
- "parental_control_last_update";
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#SETTINGS_CLASSNAME} instead
+ */
+ @Deprecated
+ public static final String SETTINGS_CLASSNAME = Secure.SETTINGS_CLASSNAME;
/**
- * Whether ADB is enabled.
+ * @deprecated Use {@link android.provider.Settings.Secure#USB_MASS_STORAGE_ENABLED} instead
*/
- public static final String ADB_ENABLED = "adb_enabled";
+ @Deprecated
+ public static final String USB_MASS_STORAGE_ENABLED = Secure.USB_MASS_STORAGE_ENABLED;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#USE_GOOGLE_MAIL} instead
+ */
+ @Deprecated
+ public static final String USE_GOOGLE_MAIL = Secure.USE_GOOGLE_MAIL;
+
+// /**
+// * @deprecated Use {@link android.provider.Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT}
+// * instead
+// */
+ @Deprecated
+ public static final String WIFI_MAX_DHCP_RETRY_COUNT = Secure.WIFI_MAX_DHCP_RETRY_COUNT;
+
+// /**
+// * @deprecated Use
+// * {@link android.provider.Settings.Secure#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS}
+// * instead
+// */
+ @Deprecated
+ public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS =
+ Secure.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS;
/**
- * Whether the audible DTMF tones are played by the dialer when dialing. The value is
- * boolean (1 or 0).
+ * @deprecated Use
+ * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON} instead
*/
- public static final String DTMF_TONE_WHEN_DIALING = "dtmf_tone";
+ @Deprecated
+ public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
+ Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON;
/**
- * Whether the sounds effects (key clicks, lid open ...) are enabled. The value is
- * boolean (1 or 0).
+ * @deprecated Use
+ * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} instead
*/
- public static final String SOUND_EFFECTS_ENABLED = "sound_effects_enabled";
- }
+ @Deprecated
+ public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY =
+ Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#WIFI_NUM_OPEN_NETWORKS_KEPT}
+ * instead
+ */
+ @Deprecated
+ public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = Secure.WIFI_NUM_OPEN_NETWORKS_KEPT;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#WIFI_ON} instead
+ */
+ @Deprecated
+ public static final String WIFI_ON = Secure.WIFI_ON;
+
+ /**
+ * @deprecated Use
+ * {@link android.provider.Settings.Secure#WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE}
+ * instead
+ */
+ @Deprecated
+ public static final String WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE =
+ Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_AP_COUNT} instead
+ */
+ @Deprecated
+ public static final String WIFI_WATCHDOG_AP_COUNT = Secure.WIFI_WATCHDOG_AP_COUNT;
+
+ /**
+ * @deprecated Use
+ * {@link android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS} instead
+ */
+ @Deprecated
+ public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS =
+ Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS;
+
+ /**
+ * @deprecated Use
+ * {@link android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED} instead
+ */
+ @Deprecated
+ public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED =
+ Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED;
+
+ /**
+ * @deprecated Use
+ * {@link android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS}
+ * instead
+ */
+ @Deprecated
+ public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS =
+ Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS;
+
+ /**
+ * @deprecated Use
+ * {@link android.provider.Settings.Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} instead
+ */
+ @Deprecated
+ public static final String WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT =
+ Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS}
+ * instead
+ */
+ @Deprecated
+ public static final String WIFI_WATCHDOG_MAX_AP_CHECKS = Secure.WIFI_WATCHDOG_MAX_AP_CHECKS;
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_ON} instead
+ */
+ @Deprecated
+ public static final String WIFI_WATCHDOG_ON = Secure.WIFI_WATCHDOG_ON;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT} instead
+ */
+ @Deprecated
+ public static final String WIFI_WATCHDOG_PING_COUNT = Secure.WIFI_WATCHDOG_PING_COUNT;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_PING_DELAY_MS}
+ * instead
+ */
+ @Deprecated
+ public static final String WIFI_WATCHDOG_PING_DELAY_MS = Secure.WIFI_WATCHDOG_PING_DELAY_MS;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_PING_TIMEOUT_MS}
+ * instead
+ */
+ @Deprecated
+ public static final String WIFI_WATCHDOG_PING_TIMEOUT_MS =
+ Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS;
+ }
/**
- * Gservices settings, containing the network names for Google's
- * various services. This table holds simple name/addr pairs.
- * Addresses can be accessed through the getString() method.
- * @hide
+ * Secure system settings, containing system preferences that applications
+ * can read but are not allowed to write. These are for preferences that
+ * the user must explicitly modify through the system UI or specialized
+ * APIs for those values, not modified directly by applications.
*/
- public static final class Gservices extends NameValueTable {
- public static final String SYS_PROP_SETTING_VERSION = "sys.settings_gservices_version";
+ public static final class Secure extends NameValueTable {
+ public static final String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version";
private static volatile NameValueCache mNameValueCache = null;
- private static final Object mNameValueCacheLock = new Object();
/**
* Look up a name in the database.
@@ -1097,13 +1460,11 @@ public final class Settings {
* @param name to look up in the table
* @return the corresponding value, or null if not present
*/
- public static String getString(ContentResolver resolver, String name) {
- synchronized (mNameValueCacheLock) {
- if (mNameValueCache == null) {
- mNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI);
- }
- return mNameValueCache.getString(resolver, name);
+ public synchronized static String getString(ContentResolver resolver, String name) {
+ if (mNameValueCache == null) {
+ mNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI);
}
+ return mNameValueCache.getString(resolver, name);
}
/**
@@ -1119,184 +1480,566 @@ public final class Settings {
}
/**
- * Look up the value for name in the database, convert it to an int using Integer.parseInt
- * and return it. If it is null or if a NumberFormatException is caught during the
- * conversion then return defValue.
+ * Construct the content URI for a particular name/value pair,
+ * useful for monitoring changes with a ContentObserver.
+ * @param name to look up in the table
+ * @return the corresponding content URI, or null if not present
*/
- public static int getInt(ContentResolver resolver, String name, int defValue) {
- String valString = getString(resolver, name);
- int value;
+ public static Uri getUriFor(String name) {
+ return getUriFor(CONTENT_URI, name);
+ }
+
+ /**
+ * Convenience function for retrieving a single secure settings value
+ * as an integer. Note that internally setting values are always
+ * stored as strings; this function converts the string to an integer
+ * for you. The default value will be returned if the setting is
+ * not defined or not an integer.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid integer.
+ */
+ public static int getInt(ContentResolver cr, String name, int def) {
+ String v = getString(cr, name);
try {
- value = valString != null ? Integer.parseInt(valString) : defValue;
+ return v != null ? Integer.parseInt(v) : def;
} catch (NumberFormatException e) {
- value = defValue;
+ return def;
}
- return value;
}
/**
- * Look up the value for name in the database, convert it to a long using Long.parseLong
- * and return it. If it is null or if a NumberFormatException is caught during the
- * conversion then return defValue.
+ * Convenience function for retrieving a single secure settings value
+ * as an integer. Note that internally setting values are always
+ * stored as strings; this function converts the string to an integer
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link SettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @throws SettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not an integer.
+ *
+ * @return The setting's current value.
*/
- public static long getLong(ContentResolver resolver, String name, long defValue) {
- String valString = getString(resolver, name);
- long value;
+ public static int getInt(ContentResolver cr, String name)
+ throws SettingNotFoundException {
+ String v = getString(cr, name);
try {
- value = valString != null ? Long.parseLong(valString) : defValue;
+ return Integer.parseInt(v);
} catch (NumberFormatException e) {
- value = defValue;
+ throw new SettingNotFoundException(name);
}
- return value;
}
/**
- * Construct the content URI for a particular name/value pair,
- * useful for monitoring changes with a ContentObserver.
- * @param name to look up in the table
- * @return the corresponding content URI, or null if not present
+ * Convenience function for updating a single settings value as an
+ * integer. This will either create a new entry in the table if the
+ * given name does not exist, or modify the value of the existing row
+ * with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a
+ * string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
*/
- public static Uri getUriFor(String name) {
- return getUriFor(CONTENT_URI, name);
+ public static boolean putInt(ContentResolver cr, String name, int value) {
+ return putString(cr, name, Integer.toString(value));
}
/**
- * The content:// style URL for this table
+ * Convenience function for retrieving a single secure settings value
+ * as a {@code long}. Note that internally setting values are always
+ * stored as strings; this function converts the string to a {@code long}
+ * for you. The default value will be returned if the setting is
+ * not defined or not a {@code long}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid {@code long}.
*/
- public static final Uri CONTENT_URI =
- Uri.parse("content://" + AUTHORITY + "/gservices");
+ public static long getLong(ContentResolver cr, String name, long def) {
+ String valString = getString(cr, name);
+ long value;
+ try {
+ value = valString != null ? Long.parseLong(valString) : def;
+ } catch (NumberFormatException e) {
+ value = def;
+ }
+ return value;
+ }
/**
- * MMS - URL to use for HTTP "x-wap-profile" header
+ * Convenience function for retrieving a single secure settings value
+ * as a {@code long}. Note that internally setting values are always
+ * stored as strings; this function converts the string to a {@code long}
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link SettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @return The setting's current value.
+ * @throws SettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not an integer.
*/
- public static final String MMS_X_WAP_PROFILE_URL
- = "mms_x_wap_profile_url";
+ public static long getLong(ContentResolver cr, String name)
+ throws SettingNotFoundException {
+ String valString = getString(cr, name);
+ try {
+ return Long.parseLong(valString);
+ } catch (NumberFormatException e) {
+ throw new SettingNotFoundException(name);
+ }
+ }
/**
- * YouTube - "most viewed" url
+ * Convenience function for updating a secure settings value as a long
+ * integer. This will either create a new entry in the table if the
+ * given name does not exist, or modify the value of the existing row
+ * with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a
+ * string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
*/
- public static final String YOUTUBE_MOST_VIEWED_URL
- = "youtube_most_viewed_url";
+ public static boolean putLong(ContentResolver cr, String name, long value) {
+ return putString(cr, name, Long.toString(value));
+ }
/**
- * YouTube - "most recent" url
+ * Convenience function for retrieving a single secure settings value
+ * as a floating point number. Note that internally setting values are
+ * always stored as strings; this function converts the string to an
+ * float for you. The default value will be returned if the setting
+ * is not defined or not a valid float.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid float.
*/
- public static final String YOUTUBE_MOST_RECENT_URL
- = "youtube_most_recent_url";
+ public static float getFloat(ContentResolver cr, String name, float def) {
+ String v = getString(cr, name);
+ try {
+ return v != null ? Float.parseFloat(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
/**
- * YouTube - "top favorites" url
+ * Convenience function for retrieving a single secure settings value
+ * as a float. Note that internally setting values are always
+ * stored as strings; this function converts the string to a float
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link SettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @throws SettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not a float.
+ *
+ * @return The setting's current value.
*/
- public static final String YOUTUBE_TOP_FAVORITES_URL
- = "youtube_top_favorites_url";
+ public static float getFloat(ContentResolver cr, String name)
+ throws SettingNotFoundException {
+ String v = getString(cr, name);
+ try {
+ return Float.parseFloat(v);
+ } catch (NumberFormatException e) {
+ throw new SettingNotFoundException(name);
+ }
+ }
/**
- * YouTube - "most discussed" url
+ * Convenience function for updating a single settings value as a
+ * floating point number. This will either create a new entry in the
+ * table if the given name does not exist, or modify the value of the
+ * existing row with that name. Note that internally setting values
+ * are always stored as strings, so this function converts the given
+ * value to a string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
*/
- public static final String YOUTUBE_MOST_DISCUSSED_URL
- = "youtube_most_discussed_url";
+ public static boolean putFloat(ContentResolver cr, String name, float value) {
+ return putString(cr, name, Float.toString(value));
+ }
/**
- * YouTube - "most responded" url
+ * The content:// style URL for this table
*/
- public static final String YOUTUBE_MOST_RESPONDED_URL
- = "youtube_most_responded_url";
-
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://" + AUTHORITY + "/secure");
+
/**
- * YouTube - "most linked" url
+ * Whether ADB is enabled.
*/
- public static final String YOUTUBE_MOST_LINKED_URL
- = "youtube_most_linked_url";
-
+ public static final String ADB_ENABLED = "adb_enabled";
+
/**
- * YouTube - "top rated" url
+ * Setting to allow mock locations and location provider status to be injected into the
+ * LocationManager service for testing purposes during application development. These
+ * locations and status values override actual location and status information generated
+ * by network, gps, or other location providers.
*/
- public static final String YOUTUBE_TOP_RATED_URL
- = "youtube_top_rated_url";
-
+ public static final String ALLOW_MOCK_LOCATION = "mock_location";
+
/**
- * YouTube - "recently featured" url
+ * The Android ID (a unique 64-bit value) as a hex string.
+ * Identical to that obtained by calling
+ * GoogleLoginService.getAndroidId(); it is also placed here
+ * so you can get it without binding to a service.
*/
- public static final String YOUTUBE_RECENTLY_FEATURED_URL
- = "youtube_recently_featured_url";
-
+ public static final String ANDROID_ID = "android_id";
+
/**
- * YouTube - my uploaded videos
+ * Whether bluetooth is enabled/disabled
+ * 0=disabled. 1=enabled.
*/
- public static final String YOUTUBE_MY_VIDEOS_URL
- = "youtube_my_videos_url";
-
+ public static final String BLUETOOTH_ON = "bluetooth_on";
+
/**
- * YouTube - "my favorite" videos url
+ * Whether or not data roaming is enabled. (0 = false, 1 = true)
*/
- public static final String YOUTUBE_MY_FAVORITES_URL
- = "youtube_my_favorites_url";
-
+ public static final String DATA_ROAMING = "data_roaming";
+
/**
- * YouTube - "by author" videos url -- used for My videos
+ * Setting to record the input method used by default, holding the ID
+ * of the desired method.
*/
- public static final String YOUTUBE_BY_AUTHOR_URL
- = "youtube_by_author_url";
-
+ public static final String DEFAULT_INPUT_METHOD = "default_input_method";
+
/**
- * YouTube - save a video to favorite videos url
+ * Whether the device has been provisioned (0 = false, 1 = true)
*/
- public static final String YOUTUBE_SAVE_TO_FAVORITES_URL
- = "youtube_save_to_favorites_url";
-
+ public static final String DEVICE_PROVISIONED = "device_provisioned";
+
+ /**
+ * List of input methods that are currently enabled. This is a string
+ * containing the IDs of all enabled input methods, each ID separated
+ * by ':'.
+ */
+ public static final String ENABLED_INPUT_METHODS = "enabled_input_methods";
+
/**
- * YouTube - "mobile" videos url
+ * Host name and port for a user-selected proxy.
*/
- public static final String YOUTUBE_MOBILE_VIDEOS_URL
- = "youtube_mobile_videos_url";
+ public static final String HTTP_PROXY = "http_proxy";
+
+ /**
+ * Whether the package installer should allow installation of apps downloaded from
+ * sources other than the Android Market (vending machine).
+ *
+ * 1 = allow installing from other sources
+ * 0 = only allow installing from the Android Market
+ */
+ public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
+
+ /**
+ * Comma-separated list of location providers that activities may access.
+ */
+ public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
+
+ /**
+ * The Logging ID (a unique 64-bit value) as a hex string.
+ * Used as a pseudonymous identifier for logging.
+ */
+ public static final String LOGGING_ID = "logging_id";
+
+ /**
+ * User preference for which network(s) should be used. Only the
+ * connectivity service should touch this.
+ */
+ public static final String NETWORK_PREFERENCE = "network_preference";
+
+ /**
+ */
+ public static final String PARENTAL_CONTROL_ENABLED = "parental_control_enabled";
+
+ /**
+ */
+ public static final String PARENTAL_CONTROL_LAST_UPDATE = "parental_control_last_update";
+
+ /**
+ */
+ public static final String PARENTAL_CONTROL_REDIRECT_URL = "parental_control_redirect_url";
+
+ /**
+ * Settings classname to launch when Settings is clicked from All
+ * Applications. Needed because of user testing between the old
+ * and new Settings apps.
+ */
+ // TODO: 881807
+ public static final String SETTINGS_CLASSNAME = "settings_classname";
+
+ /**
+ * USB Mass Storage Enabled
+ */
+ public static final String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
+
+ /**
+ * If this setting is set (to anything), then all references
+ * to Gmail on the device must change to Google Mail.
+ */
+ public static final String USE_GOOGLE_MAIL = "use_google_mail";
+
+ /**
+ * Whether to notify the user of open networks.
+ * <p>
+ * If not connected and the scan results have an open network, we will
+ * put this notification up. If we attempt to connect to a network or
+ * the open network(s) disappear, we remove the notification. When we
+ * show the notification, we will not show it again for
+ * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} time.
+ */
+ public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
+ "wifi_networks_available_notification_on";
+
+ /**
+ * Delay (in seconds) before repeating the Wi-Fi networks available notification.
+ * Connecting to a network will reset the timer.
+ */
+ public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY =
+ "wifi_networks_available_repeat_delay";
+
+ /**
+ * The number of radio channels that are allowed in the local
+ * 802.11 regulatory domain.
+ * @hide
+ */
+ public static final String WIFI_NUM_ALLOWED_CHANNELS = "wifi_num_allowed_channels";
+
+ /**
+ * When the number of open networks exceeds this number, the
+ * least-recently-used excess networks will be removed.
+ */
+ public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept";
+
+ /**
+ * Whether the Wi-Fi should be on. Only the Wi-Fi service should touch this.
+ */
+ public static final String WIFI_ON = "wifi_on";
+
+ /**
+ * The acceptable packet loss percentage (range 0 - 100) before trying
+ * another AP on the same network.
+ */
+ public static final String WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE =
+ "wifi_watchdog_acceptable_packet_loss_percentage";
+
+ /**
+ * The number of access points required for a network in order for the
+ * watchdog to monitor it.
+ */
+ public static final String WIFI_WATCHDOG_AP_COUNT = "wifi_watchdog_ap_count";
+
+ /**
+ * The delay between background checks.
+ */
+ public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS =
+ "wifi_watchdog_background_check_delay_ms";
+
+ /**
+ * Whether the Wi-Fi watchdog is enabled for background checking even
+ * after it thinks the user has connected to a good access point.
+ */
+ public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED =
+ "wifi_watchdog_background_check_enabled";
+
+ /**
+ * The timeout for a background ping
+ */
+ public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS =
+ "wifi_watchdog_background_check_timeout_ms";
+
+ /**
+ * The number of initial pings to perform that *may* be ignored if they
+ * fail. Again, if these fail, they will *not* be used in packet loss
+ * calculation. For example, one network always seemed to time out for
+ * the first couple pings, so this is set to 3 by default.
+ */
+ public static final String WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT =
+ "wifi_watchdog_initial_ignored_ping_count";
+
+ /**
+ * The maximum number of access points (per network) to attempt to test.
+ * If this number is reached, the watchdog will no longer monitor the
+ * initial connection state for the network. This is a safeguard for
+ * networks containing multiple APs whose DNS does not respond to pings.
+ */
+ public static final String WIFI_WATCHDOG_MAX_AP_CHECKS = "wifi_watchdog_max_ap_checks";
+
+ /**
+ * Whether the Wi-Fi watchdog is enabled.
+ */
+ public static final String WIFI_WATCHDOG_ON = "wifi_watchdog_on";
+
+ /**
+ * The number of pings to test if an access point is a good connection.
+ */
+ public static final String WIFI_WATCHDOG_PING_COUNT = "wifi_watchdog_ping_count";
+
+ /**
+ * The delay between pings.
+ */
+ public static final String WIFI_WATCHDOG_PING_DELAY_MS = "wifi_watchdog_ping_delay_ms";
+
+ /**
+ * The timeout per ping.
+ */
+ public static final String WIFI_WATCHDOG_PING_TIMEOUT_MS = "wifi_watchdog_ping_timeout_ms";
+
+ /**
+ * The maximum number of times we will retry a connection to an access
+ * point for which we have failed in acquiring an IP address from DHCP.
+ * A value of N means that we will make N+1 connection attempts in all.
+ *
+ * @hide pending API Council approval
+ */
+ public static final String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";
+
+ /**
+ * Maximum amount of time in milliseconds to hold a wakelock while waiting for mobile
+ * data connectivity to be established after a disconnect from Wi-Fi.
+ *
+ * @hide pending API Council approval
+ */
+ public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS =
+ "wifi_mobile_data_transition_wakelock_timeout_ms";
+ }
+
+ /**
+ * Gservices settings, containing the network names for Google's
+ * various services. This table holds simple name/addr pairs.
+ * Addresses can be accessed through the getString() method.
+ *
+ * TODO: This should move to partner/google/... somewhere.
+ *
+ * @hide
+ */
+ public static final class Gservices extends NameValueTable {
+ public static final String SYS_PROP_SETTING_VERSION = "sys.settings_gservices_version";
/**
- * YouTube - search videos url
+ * Intent action broadcast when the Gservices table is updated by the server.
+ * This is broadcast once after settings change (so many values may have been updated).
*/
- public static final String YOUTUBE_SEARCH_URL
- = "youtube_search_url";
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String CHANGED_ACTION =
+ "com.google.gservices.intent.action.GSERVICES_CHANGED";
+
+ private static volatile NameValueCache mNameValueCache = null;
+ private static final Object mNameValueCacheLock = new Object();
/**
- * YouTube - category search videos url
+ * Look up a name in the database.
+ * @param resolver to access the database with
+ * @param name to look up in the table
+ * @return the corresponding value, or null if not present
*/
- public static final String YOUTUBE_CATEGORY_SEARCH_URL
- = "youtube_category_search_url";
+ public static String getString(ContentResolver resolver, String name) {
+ synchronized (mNameValueCacheLock) {
+ if (mNameValueCache == null) {
+ mNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI);
+ }
+ return mNameValueCache.getString(resolver, name);
+ }
+ }
/**
- * YouTube - url to get the list of categories
+ * Store a name/value pair into the database.
+ * @param resolver to access the database with
+ * @param name to store
+ * @param value to associate with the name
+ * @return true if the value was set, false on database errors
*/
- public static final String YOUTUBE_CATEGORY_LIST_URL
- = "youtube_category_list_url";
+ public static boolean putString(ContentResolver resolver,
+ String name, String value) {
+ return putString(resolver, CONTENT_URI, name, value);
+ }
/**
- * YouTube - related videos url
+ * Look up the value for name in the database, convert it to an int using Integer.parseInt
+ * and return it. If it is null or if a NumberFormatException is caught during the
+ * conversion then return defValue.
*/
- public static final String YOUTUBE_RELATED_VIDEOS_URL
- = "youtube_related_videos_url";
+ public static int getInt(ContentResolver resolver, String name, int defValue) {
+ String valString = getString(resolver, name);
+ int value;
+ try {
+ value = valString != null ? Integer.parseInt(valString) : defValue;
+ } catch (NumberFormatException e) {
+ value = defValue;
+ }
+ return value;
+ }
/**
- * YouTube - individual video url
+ * Look up the value for name in the database, convert it to a long using Long.parseLong
+ * and return it. If it is null or if a NumberFormatException is caught during the
+ * conversion then return defValue.
*/
- public static final String YOUTUBE_INDIVIDUAL_VIDEO_URL
- = "youtube_individual_video_url";
+ public static long getLong(ContentResolver resolver, String name, long defValue) {
+ String valString = getString(resolver, name);
+ long value;
+ try {
+ value = valString != null ? Long.parseLong(valString) : defValue;
+ } catch (NumberFormatException e) {
+ value = defValue;
+ }
+ return value;
+ }
/**
- * YouTube - user's playlist url
+ * Construct the content URI for a particular name/value pair,
+ * useful for monitoring changes with a ContentObserver.
+ * @param name to look up in the table
+ * @return the corresponding content URI, or null if not present
*/
- public static final String YOUTUBE_MY_PLAYLISTS_URL
- = "youtube_my_playlists_url";
+ public static Uri getUriFor(String name) {
+ return getUriFor(CONTENT_URI, name);
+ }
/**
- * YouTube - user's subscriptions url
+ * The content:// style URL for this table
*/
- public static final String YOUTUBE_MY_SUBSCRIPTIONS_URL
- = "youtube_my_subscriptions_url";
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://" + AUTHORITY + "/gservices");
/**
- * YouTube - the url we use to contact YouTube to get a device id
+ * MMS - URL to use for HTTP "x-wap-profile" header
*/
- public static final String YOUTUBE_REGISTER_DEVICE_URL
- = "youtube_register_device_url";
+ public static final String MMS_X_WAP_PROFILE_URL
+ = "mms_x_wap_profile_url";
/**
* YouTube - the flag to indicate whether to use proxy
@@ -1325,7 +2068,8 @@ public final class Settings {
* seconds. This allows for throttling of logs when the device is
* running for large amounts of time.
*/
- public static final String MEMCHECK_LOG_REALTIME_INTERVAL = "memcheck_log_realtime_interval";
+ public static final String MEMCHECK_LOG_REALTIME_INTERVAL =
+ "memcheck_log_realtime_interval";
/**
* Boolean indicating whether rebooting due to system memory checks
@@ -1423,7 +2167,7 @@ public final class Settings {
* the device is idle within the window.
*/
public static final String REBOOT_WINDOW = "reboot_window";
-
+
/**
* The minimum version of the server that is required in order for the device to accept
* the server's recommendations about the initial sync settings to use. When this is unset,
@@ -1446,6 +2190,12 @@ public final class Settings {
public static final String GMAIL_TIMEOUT_MS = "gmail_timeout_ms";
/**
+ * Controls whether Gmail will request an expedited sync when a message is sent. Value must
+ * be an integer where non-zero means true. Defaults to 1.
+ */
+ public static final String GMAIL_SEND_IMMEDIATELY = "gmail_send_immediately";
+
+ /**
* Hostname of the GTalk server.
*/
public static final String GTALK_SERVICE_HOSTNAME = "gtalk_hostname";
@@ -1667,7 +2417,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.
+ */
+ 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.
+ */
+ public static final String SETUP_GOOGLE_PRIVACY_URL = "setup_google_privacy_url";
+
/**
* Request an MSISDN token for various Google services.
*/
@@ -1684,6 +2455,12 @@ public final class Settings {
public static final String PARENTAL_CONTROL_CHECK_ENABLED =
"parental_control_check_enabled";
+ /**
+ * The list of applications we need to block if parental control is
+ * enabled.
+ */
+ public static final String PARENTAL_CONTROL_APPS_LIST =
+ "parental_control_apps_list";
/**
* Duration in which parental control status is valid.
@@ -1712,7 +2489,7 @@ public final class Settings {
*/
public static final String DISK_FREE_CHANGE_REPORTING_THRESHOLD =
"disk_free_change_reporting_threshold";
-
+
/**
* Prefix for new Google services published by the checkin
* server.
@@ -1726,24 +2503,39 @@ public final class Settings {
*/
public static final String SYNC_MAX_RETRY_DELAY_IN_SECONDS =
"sync_max_retry_delay_in_seconds";
-
+
/**
* Minimum percentage of free storage on the device that is used to determine if
- * the device is running low on storage.
+ * the device is running low on storage.
* Say this value is set to 10, the device is considered running low on storage
* if 90% or more of the device storage is filled up.
*/
- public static final String SYS_STORAGE_THRESHOLD_PERCENTAGE =
+ public static final String SYS_STORAGE_THRESHOLD_PERCENTAGE =
"sys_storage_threshold_percentage";
-
+
/**
- * The interval in minutes after which the amount of free storage left on the
+ * The interval in minutes after which the amount of free storage left on the
* device is logged to the event log
*/
- public static final String SYS_FREE_STORAGE_LOG_INTERVAL =
+ public static final String SYS_FREE_STORAGE_LOG_INTERVAL =
"sys_free_storage_log_interval";
/**
+ * The interval in milliseconds at which to check the number of SMS sent
+ * out without asking for use permit, to limit the un-authorized SMS
+ * usage.
+ */
+ public static final String SMS_OUTGOING_CEHCK_INTERVAL_MS =
+ "sms_outgoing_check_interval_ms";
+
+ /**
+ * The number of outgoing SMS sent without asking for user permit
+ * (of {@link #SMS_OUTGOING_CEHCK_INTERVAL_MS}
+ */
+ public static final String SMS_OUTGOING_CEHCK_MAX_COUNT =
+ "sms_outgoing_check_max_count";
+
+ /**
* The interval in milliseconds at which to check packet counts on the
* mobile data interface when screen is on, to detect possible data
* connection problems.
@@ -1757,22 +2549,22 @@ public final class Settings {
* connection problems.
*/
public static final String PDP_WATCHDOG_LONG_POLL_INTERVAL_MS =
- "pdp_watchdog_long_poll_interval_ms";
-
+ "pdp_watchdog_long_poll_interval_ms";
+
/**
* The interval in milliseconds at which to check packet counts on the
* mobile data interface after {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT}
* outgoing packets has been reached without incoming packets.
*/
- public static final String PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS =
+ public static final String PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS =
"pdp_watchdog_error_poll_interval_ms";
/**
* The number of outgoing packets sent without seeing an incoming packet
- * that triggers a countdown (of {@link #PDP_WATCHDOG_ERROR_POLL_COUNT}
+ * that triggers a countdown (of {@link #PDP_WATCHDOG_ERROR_POLL_COUNT}
* device is logged to the event log
*/
- public static final String PDP_WATCHDOG_TRIGGER_PACKET_COUNT =
+ public static final String PDP_WATCHDOG_TRIGGER_PACKET_COUNT =
"pdp_watchdog_trigger_packet_count";
/**
@@ -1780,50 +2572,44 @@ public final class Settings {
* after hitting {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT} before
* attempting data connection recovery.
*/
- public static final String PDP_WATCHDOG_ERROR_POLL_COUNT =
+ public static final String PDP_WATCHDOG_ERROR_POLL_COUNT =
"pdp_watchdog_error_poll_count";
/**
* The number of failed PDP reset attempts before moving to something more
* drastic: re-registering to the network.
*/
- public static final String PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT =
+ public static final String PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT =
"pdp_watchdog_max_pdp_reset_fail_count";
/**
* Address to ping as a last sanity check before attempting any recovery.
* Unset or set to "0.0.0.0" to skip this check.
*/
- public static final String PDP_WATCHDOG_PING_ADDRESS =
- "pdp_watchdog_ping_address";
+ public static final String PDP_WATCHDOG_PING_ADDRESS = "pdp_watchdog_ping_address";
/**
* The "-w deadline" parameter for the ping, ie, the max time in
* seconds to spend pinging.
*/
- public static final String PDP_WATCHDOG_PING_DEADLINE =
- "pdp_watchdog_ping_deadline";
+ public static final String PDP_WATCHDOG_PING_DEADLINE = "pdp_watchdog_ping_deadline";
/**
- * 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";
-
- /**
- * The interval in milliseconds at which we forcefully release the
- * transition-to-mobile-data wake lock.
+ * The interval in milliseconds at which to check gprs registration
+ * after the first registration mismatch of gprs and voice service,
+ * to detect possible data network registration problems.
+ *
*/
- public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS =
- "wifi_mobile_data_transition_wakelock_timeout_ms";
+ public static final String GPRS_REGISTER_CHECK_PERIOD_MS =
+ "gprs_register_check_period_ms";
/**
- * The maximum number of times we will retry a connection to an access
- * point for which we have failed in acquiring an IP address from DHCP.
- * A value of N means that we will make N+1 connection attempts in all.
+ * Screen timeout in milliseconds corresponding to the
+ * PowerManager's POKE_LOCK_SHORT_TIMEOUT flag (i.e. the fastest
+ * possible screen timeout behavior.)
*/
- public static final String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";
+ public static final String SHORT_KEYLIGHT_DELAY_MS =
+ "short_keylight_delay_ms";
/**
* @deprecated
diff --git a/core/java/android/provider/Sync.java b/core/java/android/provider/Sync.java
index b889293..2086a5d 100644
--- a/core/java/android/provider/Sync.java
+++ b/core/java/android/provider/Sync.java
@@ -489,9 +489,14 @@ public final class Sync {
*/
public static final Uri CONTENT_URI = Uri.parse("content://sync/settings");
- /** controls whether or not the devices listens for sync tickles */
+ /** 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_";
@@ -529,17 +534,28 @@ public final class Sync {
}
/**
- * A convenience method to set whether or not the tickle xmpp connection
- * should be established.
+ * A convenience method to set whether or not the device should listen to tickles.
*
* @param contentResolver the ContentResolver to use to access the settings table
- * @param flag true if the tickle xmpp connection should be established
+ * @param flag true if it should listen.
*/
static public void setListenForNetworkTickles(ContentResolver contentResolver,
boolean flag) {
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;
@@ -570,23 +586,42 @@ public final class Sync {
}
/**
- * Set whether or not the tickle xmpp connection should be established.
+ * Set whether or not the device should listen for tickles.
*
- * @param flag true if the tickle xmpp connection should be established
+ * @param flag true if it should listen.
*/
public void setListenForNetworkTickles(boolean flag) {
Settings.setListenForNetworkTickles(mContentResolver, flag);
}
/**
- * Check if the tickle xmpp connection should be established
- * @return true if it should be stablished
+ * Check if the device should listen to tickles.
+
+ * @return true if it should
*/
public boolean getListenForNetworkTickles() {
return getBoolean(SETTING_LISTEN_FOR_TICKLES, true);
}
/**
+ * 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 776a266..18c64ed 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -16,7 +16,6 @@
package android.provider;
-import com.android.internal.telephony.CallerInfo;
import com.google.android.mms.util.SqliteWrapper;
import android.annotation.SdkConstant;
@@ -27,8 +26,6 @@ import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
import android.telephony.gsm.SmsMessage;
import android.text.TextUtils;
import android.text.util.Regex;
@@ -264,49 +261,6 @@ public final class Telephony {
}
/**
- * Returns true if the address is an email address
- *
- * @param address the input address to be tested
- * @return true if address is an email address
- */
- public static boolean isEmailAddress(String address) {
- /*
- * The '@' char isn't a valid char in phone numbers. However, in SMS
- * messages sent by carrier, the originating-address can contain
- * non-dialable alphanumeric chars. For the purpose of thread id
- * grouping, we don't care about those. We only care about the
- * legitmate/dialable phone numbers (which we use the special phone
- * number comparison) and email addresses (which we do straight up
- * string comparison).
- */
- return (address != null) && (address.indexOf('@') != -1);
- }
-
- /**
- * Formats an address for displaying, doing a phone number lookup in the
- * Address Book, etc.
- *
- * @param context the context to use
- * @param address the address to format
- * @return a nicely formatted version of the sender to display
- */
- public static String getDisplayAddress(Context context, String address) {
- String result;
- int index;
- if (isEmailAddress(address)) {
- index = address.indexOf('@');
- if (index != -1) {
- result = address.substring(0, index);
- } else {
- result = address;
- }
- } else {
- result = CallerInfo.getCallerId(context, address);
- }
- return result;
- }
-
- /**
* Contains all text based SMS messages in the SMS app's inbox.
*/
public static final class Inbox implements BaseColumns, TextBasedSmsColumns {
@@ -1166,7 +1120,7 @@ public final class Telephony {
* name-addr = [display-name] angle-addr
* angle-addr = [CFWS] "<" addr-spec ">" [CFWS]
*/
- private static final Pattern NAME_ADDR_EMAIL_PATTERN =
+ public static final Pattern NAME_ADDR_EMAIL_PATTERN =
Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
/**
@@ -1174,7 +1128,7 @@ public final class Telephony {
* DQUOTE *([FWS] qcontent) [FWS] DQUOTE
* [CFWS]
*/
- private static final Pattern QUOTED_STRING_PATTERN =
+ public static final Pattern QUOTED_STRING_PATTERN =
Pattern.compile("\\s*\"([^\"]*)\"\\s*");
public static final Cursor query(
@@ -1232,81 +1186,6 @@ public final class Telephony {
}
/**
- * Formats an address for displaying, doing a phone number lookup in the
- * Address Book, etc.
- *
- * @param context the context to use
- * @param address the address to format
- * @return a nicely formatted version of the sender to display
- */
- public static String getDisplayAddress(Context context, String address) {
- if (address == null) {
- return "";
- }
-
- String localNumber = TelephonyManager.getDefault().getLine1Number();
- String[] values = address.split(";");
- String result = "";
- for (int i = 0; i < values.length; i++) {
- if (values[i].length() > 0) {
- if (PhoneNumberUtils.compare(values[i], localNumber)) {
- result = result + ";"
- + context.getString(com.android.internal.R.string.me);
- } else if (isEmailAddress(values[i])) {
- result = result + ";" + getDisplayName(context, values[i]);
- } else {
- result = result + ";" + CallerInfo.getCallerId(context, values[i]);
- }
- }
- }
-
- if (result.length() > 0) {
- // Skip the first ';'
- return result.substring(1);
- }
- return result;
- }
-
- private static String getEmailDisplayName(String displayString) {
- Matcher match = QUOTED_STRING_PATTERN.matcher(displayString);
- if (match.matches()) {
- return match.group(1);
- }
-
- return displayString;
- }
-
- private static String getDisplayName(Context context, String email) {
- Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(email);
- if (match.matches()) {
- // email has display name
- return getEmailDisplayName(match.group(1));
- }
-
- Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
- Contacts.ContactMethods.CONTENT_EMAIL_URI,
- new String[] { Contacts.ContactMethods.NAME },
- Contacts.ContactMethods.DATA + " = \'" + email + "\'",
- null, null);
-
- if (cursor != null) {
- try {
- int columnIndex = cursor.getColumnIndexOrThrow(
- Contacts.ContactMethods.NAME);
- while (cursor.moveToNext()) {
- String name = cursor.getString(columnIndex);
- if (!TextUtils.isEmpty(name)) {
- return name;
- }
- }
- } finally {
- cursor.close();
- }
- }
- return email;
- }
-
- /**
* Contains all MMS messages in the MMS app's inbox.
*/
public static final class Inbox implements BaseMmsColumns {
@@ -1647,6 +1526,7 @@ public final class Telephony {
public static final String TYPE = "type";
+ public static final String CURRENT = "current";
}
public static final class Intents {
diff --git a/core/java/android/security/Md5MessageDigest.java b/core/java/android/security/Md5MessageDigest.java
index a7221ae..4fe0cb0 100644
--- a/core/java/android/security/Md5MessageDigest.java
+++ b/core/java/android/security/Md5MessageDigest.java
@@ -17,8 +17,7 @@
package android.security;
/**
- * This is a temporary class to provide SHA-1 hash.
- * It's not meant to be correct, and eventually doesn't belong in java.security
+ * Provides the MD5 hash encryption.
*/
public class Md5MessageDigest extends MessageDigest
{
diff --git a/core/java/android/security/MessageDigest.java b/core/java/android/security/MessageDigest.java
index 93040b9..cf2d0fe 100644
--- a/core/java/android/security/MessageDigest.java
+++ b/core/java/android/security/MessageDigest.java
@@ -18,8 +18,22 @@ package android.security;
import java.security.NoSuchAlgorithmException;
+/**
+ * Base class for producing a message digest from different hash encryptions.
+ */
public abstract class MessageDigest
{
+ /**
+ * Returns a digest object of the specified type.
+ *
+ * @param algorithm The type of hash function to use. Valid values are
+ * <em>SHA-1</em> and <em>MD5</em>.
+ * @return The respective MessageDigest object. Either a
+ * {@link android.security.Sha1MessageDigest} or
+ * {@link android.security.Md5MessageDigest} object.
+ * @throws NoSuchAlgorithmException If an invalid <var>algorithm</var>
+ * is given.
+ */
public static MessageDigest getInstance(String algorithm)
throws NoSuchAlgorithmException
{
@@ -39,5 +53,12 @@ public abstract class MessageDigest
public abstract void update(byte[] input);
public abstract byte[] digest();
+
+ /**
+ * Produces a message digest for the given input.
+ *
+ * @param input The message to encrypt.
+ * @return The digest (hash sum).
+ */
public abstract byte[] digest(byte[] input);
}
diff --git a/core/java/android/security/Sha1MessageDigest.java b/core/java/android/security/Sha1MessageDigest.java
index 3b3fd6a..aa01fa6 100644
--- a/core/java/android/security/Sha1MessageDigest.java
+++ b/core/java/android/security/Sha1MessageDigest.java
@@ -17,8 +17,7 @@
package android.security;
/**
- * This is a temporary class to provide SHA-1 hash.
- * It's not meant to be correct, and eventually doesn't belong in java.security
+ * Provides the SHA-1 hash encyption.
*/
public class Sha1MessageDigest extends MessageDigest
{
diff --git a/core/java/android/security/package.html b/core/java/android/security/package.html
index 26b8a32..dfc6303 100644
--- a/core/java/android/security/package.html
+++ b/core/java/android/security/package.html
@@ -1,5 +1,6 @@
<HTML>
<BODY>
+Utilities for encrypting messages from hash functions.
{@hide}
</BODY>
-</HTML> \ No newline at end of file
+</HTML>
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
new file mode 100644
index 0000000..3cbb855
--- /dev/null
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -0,0 +1,315 @@
+/*
+ * 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.
+ */
+
+/**
+ * TODO: Move this to
+ * java/services/com/android/server/BluetoothA2dpService.java
+ * and make the contructor package private again.
+ * @hide
+ */
+
+package android.server;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothError;
+import android.bluetooth.BluetoothIntent;
+import android.bluetooth.IBluetoothA2dp;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.os.Binder;
+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;
+
+public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
+ private static final String TAG = "BluetoothDeviceService";
+ private static final boolean DBG = true;
+
+ public static final String BLUETOOTH_A2DP_SERVICE = "bluetooth_a2dp";
+
+ private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
+ private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+
+ private final Context mContext;
+ private final IntentFilter mIntentFilter;
+ private HashMap<String, SinkState> mAudioDevices;
+ private final AudioManager mAudioManager;
+
+ private class SinkState {
+ public String address;
+ public int state;
+ public SinkState(String a, int s) {address = a; state = s;}
+ }
+
+ public BluetoothA2dpService(Context context) {
+ mContext = context;
+
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+
+ BluetoothDevice device =
+ (BluetoothDevice)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
+ if (device == null) {
+ throw new RuntimeException("Platform does not support Bluetooth");
+ }
+
+ if (!initNative()) {
+ throw new RuntimeException("Could not init BluetoothA2dpService");
+ }
+
+ mIntentFilter = new IntentFilter(BluetoothIntent.ENABLED_ACTION);
+ mIntentFilter.addAction(BluetoothIntent.DISABLED_ACTION);
+ mContext.registerReceiver(mReceiver, mIntentFilter);
+
+ if (device.isEnabled()) {
+ onBluetoothEnable();
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ cleanupNative();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(BluetoothIntent.ENABLED_ACTION)) {
+ onBluetoothEnable();
+ } else if (action.equals(BluetoothIntent.DISABLED_ACTION)) {
+ onBluetoothDisable();
+ }
+ }
+ };
+
+ private synchronized void onBluetoothEnable() {
+ mAudioDevices = new HashMap<String, SinkState>();
+ String[] paths = (String[])listHeadsetsNative();
+ if (paths != null) {
+ for (String path : paths) {
+ mAudioDevices.put(path, new SinkState(getAddressNative(path),
+ isSinkConnectedNative(path) ? BluetoothA2dp.STATE_CONNECTED :
+ BluetoothA2dp.STATE_DISCONNECTED));
+ }
+ }
+ }
+
+ private synchronized void onBluetoothDisable() {
+ mAudioDevices = null;
+ }
+
+ public synchronized int connectSink(String address) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
+ if (DBG) log("connectSink(" + address + ")");
+ if (!BluetoothDevice.checkBluetoothAddress(address)) {
+ return BluetoothError.ERROR;
+ }
+ if (mAudioDevices == null) {
+ return BluetoothError.ERROR;
+ }
+ String path = lookupPath(address);
+ if (path == null) {
+ path = createHeadsetNative(address);
+ if (DBG) log("new bluez sink: " + address + " (" + path + ")");
+ }
+ if (path == null) {
+ return BluetoothError.ERROR;
+ }
+ if (!connectSinkNative(path)) {
+ return BluetoothError.ERROR;
+ } else {
+ updateState(path, BluetoothA2dp.STATE_CONNECTING);
+ return BluetoothError.SUCCESS;
+ }
+ }
+
+ public synchronized int disconnectSink(String address) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
+ if (DBG) log("disconnectSink(" + address + ")");
+ if (!BluetoothDevice.checkBluetoothAddress(address)) {
+ return BluetoothError.ERROR;
+ }
+ if (mAudioDevices == null) {
+ return BluetoothError.ERROR;
+ }
+ String path = lookupPath(address);
+ if (path == null) {
+ return BluetoothError.ERROR;
+ }
+ if (!disconnectSinkNative(path)) {
+ return BluetoothError.ERROR;
+ } else {
+ updateState(path, BluetoothA2dp.STATE_DISCONNECTING);
+ return BluetoothError.SUCCESS;
+ }
+ }
+
+ 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;
+ }
+
+ public synchronized int getSinkState(String address) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ if (!BluetoothDevice.checkBluetoothAddress(address)) {
+ return BluetoothError.ERROR;
+ }
+ if (mAudioDevices == null) {
+ return BluetoothA2dp.STATE_DISCONNECTED;
+ }
+ for (SinkState sink : mAudioDevices.values()) {
+ if (address.equals(sink.address)) {
+ return sink.state;
+ }
+ }
+ return BluetoothA2dp.STATE_DISCONNECTED;
+ }
+
+ public synchronized void onHeadsetCreated(String path) {
+ updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
+ }
+
+ public synchronized void onHeadsetRemoved(String path) {
+ if (mAudioDevices == null) return;
+ mAudioDevices.remove(path);
+ }
+
+ public synchronized void onSinkConnected(String path) {
+ if (mAudioDevices == null) return;
+ // 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()) {
+ if (path.equals(oldPath)) {
+ continue;
+ }
+ int state = mAudioDevices.get(oldPath).state;
+ if (state == BluetoothA2dp.STATE_CONNECTED || state == BluetoothA2dp.STATE_PLAYING) {
+ updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
+ }
+ }
+
+ mAudioManager.setBluetoothA2dpOn(true);
+ updateState(path, BluetoothA2dp.STATE_CONNECTED);
+ }
+
+ public synchronized void onSinkDisconnected(String path) {
+ mAudioManager.setBluetoothA2dpOn(false);
+ updateState(path, BluetoothA2dp.STATE_DISCONNECTED);
+ }
+
+ public synchronized void onSinkPlaying(String path) {
+ updateState(path, BluetoothA2dp.STATE_PLAYING);
+ }
+
+ public synchronized void onSinkStopped(String path) {
+ updateState(path, BluetoothA2dp.STATE_CONNECTED);
+ }
+
+ private synchronized final String lookupAddress(String path) {
+ if (mAudioDevices == null) return null;
+ String address = mAudioDevices.get(path).address;
+ if (address == null) Log.e(TAG, "Can't find address for " + path);
+ return address;
+ }
+
+ private synchronized final String lookupPath(String address) {
+ if (mAudioDevices == null) return null;
+
+ for (String path : mAudioDevices.keySet()) {
+ if (address.equals(mAudioDevices.get(path).address)) {
+ return path;
+ }
+ }
+ return null;
+ }
+
+ private synchronized void updateState(String path, int state) {
+ if (mAudioDevices == null) return;
+
+ SinkState s = mAudioDevices.get(path);
+ int prevState;
+ String address;
+ if (s == null) {
+ address = getAddressNative(path);
+ mAudioDevices.put(path, new SinkState(address, state));
+ prevState = BluetoothA2dp.STATE_DISCONNECTED;
+ } else {
+ address = lookupAddress(path);
+ prevState = s.state;
+ s.state = state;
+ }
+
+ if (DBG) log("state " + address + " (" + path + ") " + prevState + "->" + state);
+
+ Intent intent = new Intent(BluetoothA2dp.SINK_STATE_CHANGED_ACTION);
+ intent.putExtra(BluetoothIntent.ADDRESS, address);
+ intent.putExtra(BluetoothA2dp.SINK_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothA2dp.SINK_STATE, state);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+ }
+
+ @Override
+ protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mAudioDevices == null) return;
+ pw.println("Cached audio devices:");
+ for (String path : mAudioDevices.keySet()) {
+ SinkState sink = mAudioDevices.get(path);
+ pw.println(path + " " + sink.address + " " + BluetoothA2dp.stateToString(sink.state));
+ }
+ }
+
+ private static void log(String msg) {
+ Log.d(TAG, msg);
+ }
+
+ private native boolean initNative();
+ private native void cleanupNative();
+ private synchronized native String[] listHeadsetsNative();
+ private synchronized native String createHeadsetNative(String address);
+ private synchronized native boolean removeHeadsetNative(String path);
+ private synchronized native String getAddressNative(String path);
+ private synchronized native boolean connectSinkNative(String path);
+ private synchronized native boolean disconnectSinkNative(String path);
+ private synchronized native boolean isSinkConnectedNative(String path);
+
+}
diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java
index 10f9f7c..9bdab9f 100644
--- a/core/java/android/server/BluetoothDeviceService.java
+++ b/core/java/android/server/BluetoothDeviceService.java
@@ -54,13 +54,17 @@ import java.util.HashMap;
public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private static final String TAG = "BluetoothDeviceService";
private int mNativeData;
- private Context mContext;
private BluetoothEventLoop mEventLoop;
private IntentFilter mIntentFilter;
private boolean mIsAirplaneSensitive;
private volatile boolean mIsEnabled; // local cache of isEnabledNative()
private boolean mIsDiscovering;
+ private final Context mContext;
+
+ private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
+ private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+
static {
classInitNative();
}
@@ -97,7 +101,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private native void cleanupNativeDataNative();
public boolean isEnabled() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mIsEnabled;
}
private native int isEnabledNative();
@@ -106,7 +110,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* Disable bluetooth. Returns true on success.
*/
public synchronized boolean disable() {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
if (mEnableThread != null && mEnableThread.isAlive()) {
return false;
@@ -117,9 +122,10 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
mEventLoop.stop();
disableNative();
mIsEnabled = false;
+ Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON, 0);
mIsDiscovering = false;
Intent intent = new Intent(BluetoothIntent.DISABLED_ACTION);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
return true;
}
@@ -131,7 +137,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* notified when complete.
*/
public synchronized boolean enable(IBluetoothDeviceCallback callback) {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
// Airplane mode can prevent Bluetooth radio from being turned on.
if (mIsAirplaneSensitive && isAirplaneModeOn()) {
@@ -164,6 +171,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
};
private EnableThread mEnableThread;
+ private String mOutgoingBondingDevAddress = null;
+
private class EnableThread extends Thread {
private final IBluetoothDeviceCallback mEnableCallback;
public EnableThread(IBluetoothDeviceCallback callback) {
@@ -185,9 +194,11 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
if (res) {
mIsEnabled = true;
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.BLUETOOTH_ON, 1);
mIsDiscovering = false;
Intent intent = new Intent(BluetoothIntent.ENABLED_ACTION);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
mHandler.sendMessageDelayed(mHandler.obtainMessage(REGISTER_SDP_RECORDS), 3000);
}
mEnableThread = null;
@@ -198,19 +209,20 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private native int disableNative();
public synchronized String getAddress() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return getAddressNative();
}
private native String getAddressNative();
public synchronized String getName() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return getNameNative();
}
private native String getNameNative();
public synchronized boolean setName(String name) {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
if (name == null) {
return false;
}
@@ -220,19 +232,19 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private native boolean setNameNative(String name);
public synchronized String[] listBondings() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return listBondingsNative();
}
private native String[] listBondingsNative();
public synchronized String getMajorClass() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return getMajorClassNative();
}
private native String getMajorClassNative();
public synchronized String getMinorClass() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return getMinorClassNative();
}
private native String getMinorClassNative();
@@ -248,7 +260,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @return The user-friendly name of the specified remote device.
*/
public synchronized String getRemoteName(String address) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
@@ -277,7 +289,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* false otherwise.
*/
public synchronized boolean startDiscovery(boolean resolveNames) {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
return startDiscoveryNative(resolveNames);
}
private native boolean startDiscoveryNative(boolean resolveNames);
@@ -289,13 +302,14 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* started.
*/
public synchronized boolean cancelDiscovery() {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
return cancelDiscoveryNative();
}
private native boolean cancelDiscoveryNative();
public synchronized boolean isDiscovering() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return mIsDiscovering;
}
@@ -304,19 +318,21 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
}
public synchronized boolean startPeriodicDiscovery() {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
return startPeriodicDiscoveryNative();
}
private native boolean startPeriodicDiscoveryNative();
public synchronized boolean stopPeriodicDiscovery() {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
return stopPeriodicDiscoveryNative();
}
private native boolean stopPeriodicDiscoveryNative();
public synchronized boolean isPeriodicDiscovery() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return isPeriodicDiscoveryNative();
}
private native boolean isPeriodicDiscoveryNative();
@@ -331,7 +347,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @param timeout_s The discoverable timeout in seconds.
*/
public synchronized boolean setDiscoverableTimeout(int timeout) {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
return setDiscoverableTimeoutNative(timeout);
}
private native boolean setDiscoverableTimeoutNative(int timeout_s);
@@ -345,13 +362,13 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* value indicates an error.
*/
public synchronized int getDiscoverableTimeout() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return getDiscoverableTimeoutNative();
}
private native int getDiscoverableTimeoutNative();
public synchronized boolean isAclConnected(String address) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
@@ -378,7 +395,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see #setMode
*/
public synchronized boolean isConnectable() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return isConnectableNative();
}
private native boolean isConnectableNative();
@@ -401,7 +418,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see #setMode
*/
public synchronized boolean isDiscoverable() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return isDiscoverableNative();
}
private native boolean isDiscoverableNative();
@@ -415,7 +432,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see #setMode
*/
public synchronized int getMode() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
String mode = getModeNative();
if (mode == null) {
return BluetoothDevice.MODE_UNKNOWN;
@@ -451,7 +468,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see #getMode
*/
public synchronized boolean setMode(int mode) {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
switch (mode) {
case BluetoothDevice.MODE_OFF:
return setModeNative("off");
@@ -477,7 +495,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @return The alias of the remote device.
*/
public synchronized String getRemoteAlias(String address) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
@@ -496,7 +514,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @param alias Alias for the remote device
*/
public synchronized boolean setRemoteAlias(String address, String alias) {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
if (alias == null || !BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
@@ -513,7 +532,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @param address Bluetooth address of remote device
*/
public synchronized boolean clearRemoteAlias(String address) {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
@@ -522,7 +542,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private native boolean clearRemoteAliasNative(String address);
public synchronized boolean disconnectRemoteDeviceAcl(String address) {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
@@ -546,7 +567,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see android.bluetooth.PasskeyAgent
*/
public synchronized boolean createBonding(String address, IBluetoothDeviceCallback callback) {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
@@ -568,9 +590,19 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
callbacks.remove(address);
return false;
}
+ mOutgoingBondingDevAddress = address;
return true;
}
+
private native boolean createBondingNative(String address, int timeout_ms);
+
+ /*package*/ String getOutgoingBondingDevAddress() {
+ return mOutgoingBondingDevAddress;
+ }
+
+ /*package*/ void setOutgoingBondingDevAddress(String outgoingBondingDevAddress) {
+ mOutgoingBondingDevAddress = outgoingBondingDevAddress;
+ }
/**
* This method cancels a pending bonding request.
@@ -593,7 +625,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see #listBondings
*/
public synchronized boolean cancelBondingProcess(String address) {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
@@ -618,7 +651,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see #listBondings
*/
public synchronized boolean removeBonding(String address) {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
@@ -627,7 +661,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private native boolean removeBondingNative(String address);
public synchronized boolean hasBonding(String address) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
@@ -636,7 +670,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private native boolean hasBondingNative(String address);
public synchronized String[] listAclConnections() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return listConnectionsNative();
}
private native String[] listConnectionsNative();
@@ -652,7 +686,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* remote devices that this adapter is aware of.
*/
public synchronized String[] listRemoteDevices() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return listRemoteDevicesNative();
}
private native String[] listRemoteDevicesNative();
@@ -666,7 +700,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* Bluetooth-chip version.
*/
public synchronized String getVersion() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return getVersionNative();
}
private native String getVersionNative();
@@ -683,7 +717,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @return The HCI revision of this adapter.
*/
public synchronized String getRevision() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return getRevisionNative();
}
private native String getRevisionNative();
@@ -697,7 +731,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @return Manufacturer name.
*/
public synchronized String getManufacturer() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return getManufacturerNative();
}
private native String getManufacturerNative();
@@ -716,7 +750,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @return company name
*/
public synchronized String getCompany() {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return getCompanyNative();
}
private native String getCompanyNative();
@@ -731,7 +765,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see #getVersion
*/
public synchronized String getRemoteVersion(String address) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
@@ -749,7 +783,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see #getRevision
*/
public synchronized String getRemoteRevision(String address) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
@@ -767,7 +801,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see #getManufacturer
*/
public synchronized String getRemoteManufacturer(String address) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
@@ -785,7 +819,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see #getCompany
*/
public synchronized String getRemoteCompany(String address) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
@@ -801,7 +835,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @return a String with the timestamp.
*/
public synchronized String lastSeen(String address) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
@@ -817,7 +851,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @return a String with the timestamp.
*/
public synchronized String lastUsed(String address) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
@@ -841,7 +875,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
*/
public synchronized String getRemoteMajorClass(String address) {
if (!BluetoothDevice.checkBluetoothAddress(address)) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return null;
}
return getRemoteMajorClassNative(address);
@@ -863,7 +897,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see #getRemoteClass
*/
public synchronized String getRemoteMinorClass(String address) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
@@ -880,7 +914,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see #getRemoteClass
*/
public synchronized String[] getRemoteServiceClasses(String address) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
@@ -904,7 +938,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
*/
public synchronized int getRemoteClass(String address) {
if (!BluetoothDevice.checkBluetoothAddress(address)) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
return -1;
}
return getRemoteClassNative(address);
@@ -919,7 +953,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @return byte array of features.
*/
public synchronized byte[] getRemoteFeatures(String address) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
@@ -944,7 +978,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see #getRemoteServiceRecord
*/
public synchronized int[] getRemoteServiceHandles(String address, String match) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
@@ -974,7 +1008,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
* @see #getRemoteServiceHandles
*/
public synchronized byte[] getRemoteServiceRecord(String address, int handle) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
@@ -985,7 +1019,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
// AIDL does not yet support short's
public synchronized boolean getRemoteServiceChannel(String address, int uuid16,
IBluetoothDeviceCallback callback) {
- checkPermissionBluetooth();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
@@ -1011,7 +1045,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private native boolean getRemoteServiceChannelNative(String address, short uuid16);
public synchronized boolean setPin(String address, byte[] pin) {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
if (pin == null || pin.length <= 0 || pin.length > 16 ||
!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
@@ -1036,7 +1071,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private native boolean setPinNative(String address, String pin, int nativeData);
public synchronized boolean cancelPin(String address) {
- checkPermissionBluetoothAdmin();
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
@@ -1061,7 +1097,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
// some random app is not sending this intent and disabling bluetooth
boolean enabled = !isAirplaneModeOn();
// If bluetooth is currently expected to be on, then enable or disable bluetooth
- if (Settings.System.getInt(resolver, Settings.System.BLUETOOTH_ON, 0) > 0) {
+ if (Settings.Secure.getInt(resolver, Settings.Secure.BLUETOOTH_ON, 0) > 0) {
if (enabled) {
enable(null);
} else {
@@ -1089,25 +1125,6 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
Settings.System.AIRPLANE_MODE_ON, 0) == 1;
}
- private static final String BLUETOOTH_ADMIN = android.Manifest.permission.BLUETOOTH_ADMIN;
- private static final String BLUETOOTH = android.Manifest.permission.BLUETOOTH;
-
- private void checkPermissionBluetoothAdmin() {
- if (mContext.checkCallingOrSelfPermission(BLUETOOTH_ADMIN) !=
- PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Requires BLUETOOTH_ADMIN permission");
- }
- }
-
- private void checkPermissionBluetooth() {
- if (mContext.checkCallingOrSelfPermission(BLUETOOTH_ADMIN) !=
- PackageManager.PERMISSION_GRANTED &&
- mContext.checkCallingOrSelfPermission(BLUETOOTH) !=
- PackageManager.PERMISSION_GRANTED ) {
- throw new SecurityException("Requires BLUETOOTH or BLUETOOTH_ADMIN permission");
- }
- }
-
private static final String DISABLE_ESCO_PATH = "/sys/module/sco/parameters/disable_esco";
private static void disableEsco() {
try {
@@ -1124,7 +1141,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
pw.println("\nBluetooth ENABLED: " + getAddress() + " (" + getName() + ")");
pw.println("\nisDiscovering() = " + isDiscovering());
- BluetoothHeadset headset = new BluetoothHeadset(mContext);
+ BluetoothHeadset headset = new BluetoothHeadset(mContext, null);
pw.println("\n--Bondings--");
String[] addresses = listBondings();
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 5722f51..2d8aacc 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.BluetoothClass.Device;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothIntent;
import android.bluetooth.IBluetoothDeviceCallback;
@@ -24,8 +25,6 @@ import android.content.Intent;
import android.os.RemoteException;
import android.util.Log;
-import java.io.IOException;
-import java.lang.Thread;
import java.util.HashMap;
/**
@@ -45,10 +44,13 @@ class BluetoothEventLoop {
private HashMap<String, IBluetoothDeviceCallback> mCreateBondingCallbacks;
private HashMap<String, Integer> mPasskeyAgentRequestData;
private HashMap<String, IBluetoothDeviceCallback> mGetRemoteServiceChannelCallbacks;
- private BluetoothDeviceService mBluetoothService;
-
+ private HashMap<String, Boolean> mDefaultPinData;
+ private BluetoothDeviceService mBluetoothService;
private Context mContext;
+ private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
+ private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+
static { classInitNative(); }
private static native void classInitNative();
@@ -58,6 +60,7 @@ class BluetoothEventLoop {
mCreateBondingCallbacks = new HashMap();
mPasskeyAgentRequestData = new HashMap();
mGetRemoteServiceChannelCallbacks = new HashMap();
+ mDefaultPinData = new HashMap();
initializeNativeDataNative();
}
private native void initializeNativeDataNative();
@@ -146,27 +149,28 @@ class BluetoothEventLoop {
intMode = BluetoothDevice.MODE_DISCOVERABLE;
}
intent.putExtra(BluetoothIntent.MODE, intMode);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
public void onDiscoveryStarted() {
mBluetoothService.setIsDiscovering(true);
Intent intent = new Intent(BluetoothIntent.DISCOVERY_STARTED_ACTION);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
public void onDiscoveryCompleted() {
mBluetoothService.setIsDiscovering(false);
Intent intent = new Intent(BluetoothIntent.DISCOVERY_COMPLETED_ACTION);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
public void onPairingRequest() {
Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
}
+
public void onPairingCancel() {
Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
}
public void onRemoteDeviceFound(String address, int deviceClass, short rssi) {
@@ -174,64 +178,65 @@ class BluetoothEventLoop {
intent.putExtra(BluetoothIntent.ADDRESS, address);
intent.putExtra(BluetoothIntent.CLASS, deviceClass);
intent.putExtra(BluetoothIntent.RSSI, rssi);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
public void onRemoteDeviceDisappeared(String address) {
Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
public 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);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
public void onRemoteDeviceConnected(String address) {
Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
public void onRemoteDeviceDisconnectRequested(String address) {
Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECT_REQUESTED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
public void onRemoteDeviceDisconnected(String address) {
Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
public 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);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
public void onRemoteNameFailed(String address) {
Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_FAILED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
public 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);
+ 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);
+ 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);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
private void onCreateBondingResult(String address, boolean result) {
+ mBluetoothService.setOutgoingBondingDevAddress(null);
IBluetoothDeviceCallback callback = mCreateBondingCallbacks.get(address);
if (callback != null) {
try {
@@ -240,39 +245,71 @@ class BluetoothEventLoop {
BluetoothDevice.RESULT_FAILURE);
} catch (RemoteException e) {}
mCreateBondingCallbacks.remove(address);
- }
+ }
}
+
public void onBondingCreated(String address) {
Intent intent = new Intent(BluetoothIntent.BONDING_CREATED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
+
public void onBondingRemoved(String address) {
Intent intent = new Intent(BluetoothIntent.BONDING_REMOVED_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
+
+ if (mDefaultPinData.containsKey(address)) {
+ mDefaultPinData.remove(address);
+ }
}
public void onNameChanged(String name) {
Intent intent = new Intent(BluetoothIntent.NAME_CHANGED_ACTION);
intent.putExtra(BluetoothIntent.NAME, name);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_PERM);
}
public void onPasskeyAgentRequest(String address, int nativeData) {
- mPasskeyAgentRequestData.put(address, new Integer(nativeData));
+ mPasskeyAgentRequestData.put(address, new Integer(nativeData));
+
+ if (address.equals(mBluetoothService.getOutgoingBondingDevAddress())) {
+ int btClass = mBluetoothService.getRemoteClass(address);
+ int remoteDeviceClass = Device.getDevice(btClass);
+ if (remoteDeviceClass == Device.AUDIO_VIDEO_WEARABLE_HEADSET ||
+ remoteDeviceClass == Device.AUDIO_VIDEO_HANDSFREE ||
+ remoteDeviceClass == Device.AUDIO_VIDEO_HEADPHONES ||
+ remoteDeviceClass == Device.AUDIO_VIDEO_PORTABLE_AUDIO ||
+ remoteDeviceClass == Device.AUDIO_VIDEO_CAR_AUDIO ||
+ remoteDeviceClass == Device.AUDIO_VIDEO_HIFI_AUDIO) {
+ if (!mDefaultPinData.containsKey(address)) {
+ mDefaultPinData.put(address, false);
+ }
+ if (!mDefaultPinData.get(address)) {
+ mDefaultPinData.remove(address);
+ mDefaultPinData.put(address, true);
+ mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000"));
+ return;
+ }
+ }
+ }
Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
}
+
public void onPasskeyAgentCancel(String address) {
mPasskeyAgentRequestData.remove(address);
-
+ if (mDefaultPinData.containsKey(address)) {
+ mDefaultPinData.remove(address);
+ mDefaultPinData.put(address, false);
+ }
Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION);
intent.putExtra(BluetoothIntent.ADDRESS, address);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
}
+
private void onGetRemoteServiceChannelResult(String address, int channel) {
IBluetoothDeviceCallback callback = mGetRemoteServiceChannelCallbacks.get(address);
if (callback != null) {
diff --git a/core/java/android/server/checkin/FallbackCheckinService.java b/core/java/android/server/checkin/FallbackCheckinService.java
index b450913..65921af 100644
--- a/core/java/android/server/checkin/FallbackCheckinService.java
+++ b/core/java/android/server/checkin/FallbackCheckinService.java
@@ -42,4 +42,8 @@ public final class FallbackCheckinService extends ICheckinService.Stub {
state.isEnabled = false;
p.onResult(state);
}
+
+ public void getParentalControlState(IParentalControlCallback p, String requestingApp)
+ throws android.os.RemoteException {
+ }
}
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java
index 5b9942e..6c8f554 100644
--- a/core/java/android/server/search/SearchableInfo.java
+++ b/core/java/android/server/search/SearchableInfo.java
@@ -402,7 +402,7 @@ public final class SearchableInfo implements Parcelable {
// initialize as an "unsearchable" object
mSearchable = false;
mSearchActivity = cName;
-
+
// to access another activity's resources, I need its context.
// BE SURE to release the cache sometime after construction - it's a large object to hold
mCacheActivityContext = getActivityContext(context);
@@ -415,6 +415,7 @@ public final class SearchableInfo implements Parcelable {
mIconId = a.getResourceId(com.android.internal.R.styleable.Searchable_icon, 0);
mSearchButtonText = a.getResourceId(
com.android.internal.R.styleable.Searchable_searchButtonText, 0);
+
setSearchModeFlags();
if (DBG_INHIBIT_SUGGESTIONS == 0) {
mSuggestAuthority = a.getString(
diff --git a/core/java/android/speech/recognition/AbstractEmbeddedGrammarListener.java b/core/java/android/speech/recognition/AbstractEmbeddedGrammarListener.java
deleted file mode 100644
index c25a7e3..0000000
--- a/core/java/android/speech/recognition/AbstractEmbeddedGrammarListener.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*---------------------------------------------------------------------------*
- * AbstractEmbeddedGrammarListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * An EmbeddedGrammarListener whose methods are empty. This class exists as
- * convenience for creating listener objects.
- */
-public abstract class AbstractEmbeddedGrammarListener implements EmbeddedGrammarListener
-{
- public void onCompileAllSlots()
- {
- }
-
- public void onError(Exception e)
- {
- }
-
- public void onLoaded()
- {
- }
-
- public void onResetAllSlots()
- {
- }
-
- public void onSaved(String path)
- {
- }
-
- public void onUnloaded()
- {
- }
-}
diff --git a/core/java/android/speech/recognition/AbstractGrammarListener.java b/core/java/android/speech/recognition/AbstractGrammarListener.java
deleted file mode 100644
index fe62290..0000000
--- a/core/java/android/speech/recognition/AbstractGrammarListener.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*---------------------------------------------------------------------------*
- * AbstractGrammarListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * A GrammarListener whose methods are empty. This class exists as convenience
- * for creating listener objects.
- */
-public abstract class AbstractGrammarListener implements GrammarListener
-{
- public void onError(Exception e)
- {
- }
-
- public void onLoaded()
- {
- }
-
- public void onUnloaded()
- {
- }
-}
diff --git a/core/java/android/speech/recognition/AbstractRecognizerListener.java b/core/java/android/speech/recognition/AbstractRecognizerListener.java
deleted file mode 100644
index ee2b8d1..0000000
--- a/core/java/android/speech/recognition/AbstractRecognizerListener.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*---------------------------------------------------------------------------*
- * AbstractRecognizerListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-import java.util.Hashtable;
-import java.util.Vector;
-
-/**
- * A RecognizerListener whose methods are empty. This class exists as
- * convenience for creating listener objects.
- */
-public abstract class AbstractRecognizerListener implements RecognizerListener
-{
- public void onBeginningOfSpeech()
- {
- }
-
- public void onEndOfSpeech()
- {
- }
-
- public void onRecognitionSuccess(RecognitionResult result)
- {
- }
-
- public void onRecognitionFailure(FailureReason reason)
- {
- }
-
- public void onError(Exception e)
- {
- }
-
- public void onParametersGetError(Vector<String> parameters, Exception e)
- {
- }
-
- public void onParametersSetError(Hashtable<String, String> parameters,
- Exception e)
- {
- }
-
- public void onParametersGet(Hashtable<String, String> parameters)
- {
- }
-
- public void onParametersSet(Hashtable<String, String> parameters)
- {
- }
-
- public void onStartOfSpeechTimeout()
- {
- }
-
- public void onAcousticStateReset()
- {
- }
-
- public void onStarted()
- {
- }
-
- public void onStopped()
- {
- }
-}
diff --git a/core/java/android/speech/recognition/AbstractSrecGrammarListener.java b/core/java/android/speech/recognition/AbstractSrecGrammarListener.java
deleted file mode 100644
index e62e4ba..0000000
--- a/core/java/android/speech/recognition/AbstractSrecGrammarListener.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*---------------------------------------------------------------------------*
- * AbstractSrecGrammarListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * An SrecGrammarListener whose methods are empty. This class exists as
- * convenience for creating listener objects.
- */
-public abstract class AbstractSrecGrammarListener implements SrecGrammarListener
-{
- public void onCompileAllSlots()
- {
- }
-
- public void onError(Exception e)
- {
- }
-
- public void onLoaded()
- {
- }
-
- public void onResetAllSlots()
- {
- }
-
- public void onSaved(String path)
- {
- }
-
- public void onUnloaded()
- {
- }
-
- public void onAddItemList()
- {
- }
-
- public void onAddItemListFailure(int index, Exception e)
- {
- }
-}
diff --git a/core/java/android/speech/recognition/AudioAlreadyInUseException.java b/core/java/android/speech/recognition/AudioAlreadyInUseException.java
deleted file mode 100644
index 90698a7..0000000
--- a/core/java/android/speech/recognition/AudioAlreadyInUseException.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*---------------------------------------------------------------------------*
- * AudioAlreadyInUseException.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Thrown when an AudioStream is passed into a component when another component
- * is already using it.
- */
-public class AudioAlreadyInUseException extends IllegalArgumentException
-{
- private static final long serialVersionUID = 0L;
-
- public AudioAlreadyInUseException(String msg)
- {
- super(msg);
- }
-}
diff --git a/core/java/android/speech/recognition/AudioDriverErrorException.java b/core/java/android/speech/recognition/AudioDriverErrorException.java
deleted file mode 100644
index a755e7f..0000000
--- a/core/java/android/speech/recognition/AudioDriverErrorException.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*---------------------------------------------------------------------------*
- * AudioDriverErrorException.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Thrown if an error occurs in the audio driver.
- */
-public class AudioDriverErrorException extends Exception
-{
- private static final long serialVersionUID = 0L;
-
- public AudioDriverErrorException(String msg)
- {
- super(msg);
- }
-}
diff --git a/core/java/android/speech/recognition/AudioSource.java b/core/java/android/speech/recognition/AudioSource.java
deleted file mode 100644
index c4cd802..0000000
--- a/core/java/android/speech/recognition/AudioSource.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*---------------------------------------------------------------------------*
- * AudioSource.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Generates audio data.
- */
-public interface AudioSource
-{
- /**
- * Returns an object that contains the audio samples. This object
- * is passed to other components that consumes it, such a Recognizer
- * or a DeviceSpeaker.
- *
- * @return an AudioStream instance
- */
- AudioStream createAudio();
-
- /**
- * Tells the audio source to start collecting audio samples.
- */
- void start();
-
- /**
- * Tells the audio source to stop collecting audio samples.
- */
- void stop();
-}
diff --git a/core/java/android/speech/recognition/AudioSourceListener.java b/core/java/android/speech/recognition/AudioSourceListener.java
deleted file mode 100644
index 42e8ebe..0000000
--- a/core/java/android/speech/recognition/AudioSourceListener.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*---------------------------------------------------------------------------*
- * AudioSourceListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Listens for Microphone events.
- */
-public interface AudioSourceListener
-{
- /**
- * Invoked after the microphone starts recording.
- */
- void onStarted();
-
- /**
- * Invoked after the microphone stops recording.
- */
- void onStopped();
-
- /**
- * Invoked when an unexpected error occurs. This is normally followed by
- * onStopped() if the component shuts down successfully.
- *
- * @param e the cause of the failure
- */
- void onError(Exception e);
-}
diff --git a/core/java/android/speech/recognition/AudioStream.java b/core/java/android/speech/recognition/AudioStream.java
deleted file mode 100644
index 36afe21..0000000
--- a/core/java/android/speech/recognition/AudioStream.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*---------------------------------------------------------------------------*
- * AudioStream.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Stream used to read audio data.
- */
-public interface AudioStream
-{
- /**
- * Releases resources associated with the object.
- *
- * @deprecated this method is deprecated and has no replacement. It will be
- * removed in a future release of the API.
- */
- @Deprecated
- void dispose();
-}
diff --git a/core/java/android/speech/recognition/Codec.java b/core/java/android/speech/recognition/Codec.java
deleted file mode 100644
index 18d9e15..0000000
--- a/core/java/android/speech/recognition/Codec.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*---------------------------------------------------------------------------*
- * Codec.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Audio formats.
- */
-public abstract class Codec
-{
- /**
- * PCM, 16 bits, 8KHz.
- */
- public static final Codec PCM_16BIT_8K = new Codec("PCM/16bit/8KHz")
- {
- @Override
- public byte getBitsPerSample()
- {
- return 16;
- }
-
- @Override
- public int getSampleRate()
- {
- return 8000;
- }
- };
- /**
- * PCM, 16 bits, 11KHz.
- */
- public static final Codec PCM_16BIT_11K = new Codec("PCM/16bit/11KHz")
- {
- @Override
- public byte getBitsPerSample()
- {
- return 16;
- }
-
- @Override
- public int getSampleRate()
- {
- return 11025;
- }
- };
- /**
- * PCM, 16 bits, 22KHz.
- */
- public static final Codec PCM_16BIT_22K = new Codec("PCM/16bit/22KHz")
- {
- @Override
- public byte getBitsPerSample()
- {
- return 16;
- }
-
- @Override
- public int getSampleRate()
- {
- return 22050;
- }
- };
- /**
- * ULAW, 8 bits, 8KHz.
- */
- public static final Codec ULAW_8BIT_8K = new Codec("ULAW/8bit/8KHz")
- {
- @Override
- public byte getBitsPerSample()
- {
- return 8;
- }
-
- @Override
- public int getSampleRate()
- {
- return 8000;
- }
- };
- private final String message;
-
- /**
- * Creates a new Codec.
- *
- * @param message the message to associate with the codec
- */
- private Codec(String message)
- {
- this.message = message;
- }
-
- @Override
- public String toString()
- {
- return message;
- }
-
- /**
- * Returns the codec sample-rate.
- *
- * @return the codec sample-rate
- */
- public abstract int getSampleRate();
-
- /**
- * Returns the codec bitrate.
- *
- * @return the codec bitrate
- */
- public abstract byte getBitsPerSample();
-}
diff --git a/core/java/android/speech/recognition/DeviceSpeaker.java b/core/java/android/speech/recognition/DeviceSpeaker.java
deleted file mode 100644
index bd18687..0000000
--- a/core/java/android/speech/recognition/DeviceSpeaker.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*---------------------------------------------------------------------------*
- * DeviceSpeaker.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-import android.speech.recognition.impl.DeviceSpeakerImpl;
-
-/**
- * A device for transforming electric signals into audible sound, most
- * frequently used to reproduce speech and music.
- */
-public abstract class DeviceSpeaker
-{
- private static DeviceSpeaker instance;
-
- /**
- * Returns the device speaker instance.
- *
- * @return an instance of a DeviceSpeaker class.
- */
- public static DeviceSpeaker getInstance()
- {
- instance = DeviceSpeakerImpl.getInstance();
- return instance;
- }
-
- /**
- * Starts the audio playback.
- *
- * @param source the audio to play
- * @throws IllegalStateException if the component is already started
- * @throws IllegalArgumentException if source audio is null, in-use by
- * another component or is empty.
- *
- */
- public abstract void start(AudioStream source) throws IllegalStateException,
- IllegalArgumentException;
-
- /**
- * Stops audio playback.
- */
- public abstract void stop();
-
- /**
- * Sets the playback codec. This must be called before start() is called.
- *
- * @param playbackCodec the codec to use for the playback operation.
- * @throws IllegalStateException if the component is already stopped
- * @throws IllegalArgumentException if the specified codec is not supported
- */
- public abstract void setCodec(Codec playbackCodec) throws IllegalStateException,
- IllegalArgumentException;
-
- /**
- * Sets the microphone listener.
- *
- * @param listener the device speaker listener.
- * @throws IllegalStateException if the component is started
- */
- public abstract void setListener(DeviceSpeakerListener listener) throws IllegalStateException;
-}
diff --git a/core/java/android/speech/recognition/DeviceSpeakerListener.java b/core/java/android/speech/recognition/DeviceSpeakerListener.java
deleted file mode 100644
index e2baa2e..0000000
--- a/core/java/android/speech/recognition/DeviceSpeakerListener.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*---------------------------------------------------------------------------*
- * DeviceSpeakerListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Listens for DeviceSpeaker events.
- */
-public interface DeviceSpeakerListener
-{
- /**
- * Invoked after playback begins.
- */
- void onStarted();
-
- /**
- * Invoked after playback terminates.
- */
- void onStopped();
-
- /**
- * Invoked when an unexpected error occurs. This is normally followed by
- * onStopped() if the component shuts down successfully.
- *
- * @param e the cause of the failure
- */
- void onError(Exception e);
-}
diff --git a/core/java/android/speech/recognition/EmbeddedGrammar.java b/core/java/android/speech/recognition/EmbeddedGrammar.java
deleted file mode 100644
index c6f037b..0000000
--- a/core/java/android/speech/recognition/EmbeddedGrammar.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*---------------------------------------------------------------------------*
- * EmbeddedGrammar.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Grammar on an embedded recognizer.
- */
-public interface EmbeddedGrammar extends Grammar
-{
- /**
- * Compiles items that were added to any of the grammar slots.
- */
- void compileAllSlots();
-
- /**
- * Removes all words added to all slots.
- */
- void resetAllSlots();
-
- /**
- * Saves the compiled grammar.
- *
- * @param url the url to save the grammar to
- */
- void save(String url);
-}
diff --git a/core/java/android/speech/recognition/EmbeddedGrammarListener.java b/core/java/android/speech/recognition/EmbeddedGrammarListener.java
deleted file mode 100644
index 5b8c1a4..0000000
--- a/core/java/android/speech/recognition/EmbeddedGrammarListener.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*---------------------------------------------------------------------------*
- * EmbeddedGrammarListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Listens for EmbeddedGrammar events.
- */
-public interface EmbeddedGrammarListener extends GrammarListener
-{
- /**
- * Invoked after the grammar is saved.
- *
- * @param path the path the grammar was saved to
- */
- void onSaved(String path);
-
- /**
- * Invoked when a grammar operation fails.
- *
- * @param e the cause of the failure.<br/>
- * {@link GrammarOverflowException} if the grammar slot is full and no
- * further items may be added to it.<br/>
- * {@link java.lang.UnsupportedOperationException} if different words with
- * the same pronunciation are added.<br/>
- * {@link java.lang.IllegalStateException} if reseting or compiling the
- * slots fails.<br/>
- * {@link java.io.IOException} if the grammar could not be loaded or
- * saved.</p>
- */
- void onError(Exception e);
-
- /**
- * Invokes after all grammar slots have been compiled.
- */
- void onCompileAllSlots();
-
- /**
- * Invokes after all grammar slots have been reset.
- */
- void onResetAllSlots();
-}
diff --git a/core/java/android/speech/recognition/EmbeddedRecognizer.java b/core/java/android/speech/recognition/EmbeddedRecognizer.java
deleted file mode 100644
index cd79edc..0000000
--- a/core/java/android/speech/recognition/EmbeddedRecognizer.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*---------------------------------------------------------------------------*
- * EmbeddedRecognizer.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import android.speech.recognition.impl.EmbeddedRecognizerImpl;
-
-/**
- * Embedded recognizer.
- */
-public abstract class EmbeddedRecognizer implements Recognizer
-{
- private static EmbeddedRecognizer instance;
-
- /**
- * Returns the embedded recognizer.
- *
- * @return the embedded recognizer
- */
- public static EmbeddedRecognizer getInstance()
- {
- instance = EmbeddedRecognizerImpl.getInstance();
- return instance;
- }
-
- /**
- * Configures the recognizer.
- *
- * @param config recognizer configuration file
- * @throws IllegalArgumentException if config is null or an empty string
- * @throws FileNotFoundException if the specified file could not be found
- * @throws IOException if the specified file could not be opened
- * @throws UnsatisfiedLinkError if the recognizer plugin could not be loaded
- * @throws ClassNotFoundException if the recognizer plugin could not be found
- */
- public abstract void configure(String config) throws IllegalArgumentException,
- FileNotFoundException, IOException, UnsatisfiedLinkError,
- ClassNotFoundException;
-
- /**
- * The recognition accuracy improves over time as the recognizer adapts to
- * the surrounding environment. This method enables developers to reset the
- * adaptation when the environment is known to have changed.
- *
- * @throws IllegalArgumentException if recognizer instance is null
- */
- public abstract void resetAcousticState() throws IllegalArgumentException;
-}
diff --git a/core/java/android/speech/recognition/Grammar.java b/core/java/android/speech/recognition/Grammar.java
deleted file mode 100644
index 9f1b624..0000000
--- a/core/java/android/speech/recognition/Grammar.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*---------------------------------------------------------------------------*
- * Grammar.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Speech recognition grammar.
- */
-public interface Grammar {
- /**
- * Load the grammar sets the grammar state to active, indicating that can be used in a recognition process.
- * Multiple grammars can be loaded, but only one at a time can be used by the recognizer.
- *
- */
- void load();
-
- /**
- * Unload the grammar sets the grammar state to inactive (inactive grammars can not be used as a parameter of a recognition).
- */
- void unload();
-
- /**
- * (Optional operation) Releases resources associated with the object. The
- * grammar may not be used past this point.
- */
- void dispose();
-}
diff --git a/core/java/android/speech/recognition/GrammarErrorException.java b/core/java/android/speech/recognition/GrammarErrorException.java
deleted file mode 100644
index 6070758..0000000
--- a/core/java/android/speech/recognition/GrammarErrorException.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*---------------------------------------------------------------------------*
- * GrammarErrorException.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Thrown if an error occurs in the audio driver.
- */
-public class GrammarErrorException extends Exception
-{
- private static final long serialVersionUID = 0L;
-
- public GrammarErrorException(String msg)
- {
- super(msg);
- }
-}
diff --git a/core/java/android/speech/recognition/GrammarListener.java b/core/java/android/speech/recognition/GrammarListener.java
deleted file mode 100644
index 871cbcb..0000000
--- a/core/java/android/speech/recognition/GrammarListener.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*---------------------------------------------------------------------------*
- * GrammarListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Listens for Grammar events.
- */
-public interface GrammarListener
-{
- /**
- * Invoked after the Grammar is loaded.
- */
- void onLoaded();
-
- /**
- * Invoked after the Grammar is unloaded.
- */
- void onUnloaded();
-
- /**
- * Invoked when a grammar operation fails.
- *
- * @param e the cause of the failure.<br/>
- * {@link java.io.IOException} if the grammar could not be loaded or
- * saved.</p>
- */
- void onError(Exception e);
-}
diff --git a/core/java/android/speech/recognition/GrammarOverflowException.java b/core/java/android/speech/recognition/GrammarOverflowException.java
deleted file mode 100644
index 227820b..0000000
--- a/core/java/android/speech/recognition/GrammarOverflowException.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*---------------------------------------------------------------------------*
- * GrammarOverflowException.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Thrown if a SlotItem is added into a grammar slot that is filled to capacity.
- */
-public class GrammarOverflowException extends Exception
-{
- private static final long serialVersionUID = 0L;
-
- public GrammarOverflowException(String message)
- {
- super(message);
- }
-}
diff --git a/core/java/android/speech/recognition/InvalidURLException.java b/core/java/android/speech/recognition/InvalidURLException.java
deleted file mode 100644
index fec9411..0000000
--- a/core/java/android/speech/recognition/InvalidURLException.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*---------------------------------------------------------------------------*
- * InvalidURLException.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- */
-public class InvalidURLException extends Exception {
-
- private static final long serialVersionUID = 0L;
-
- /** Creates a new instance of InvalidURLException */
- public InvalidURLException(String msg)
- {
- super(msg);
- }
-
-}
diff --git a/core/java/android/speech/recognition/Logger.java b/core/java/android/speech/recognition/Logger.java
deleted file mode 100644
index 8a09cb3..0000000
--- a/core/java/android/speech/recognition/Logger.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*---------------------------------------------------------------------------*
- * Logger.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-import android.speech.recognition.impl.LoggerImpl;
-
-/**
- * Logs debugging information.
- */
-public abstract class Logger
-{
- /**
- * Logging level
- */
- public static class LogLevel
- {
- /**
- * Does not log.
- */
- public static LogLevel LEVEL_NONE = new LogLevel("Do not log");
- /**
- * Logs fatal issues. This level only logs ERROR.
- */
- public static LogLevel LEVEL_ERROR = new LogLevel("log UAPI_ERROR logs");
- /**
- * Logs non-fatal issues. This level also logs ERROR.
- */
- public static LogLevel LEVEL_WARN =
- new LogLevel("log UAPI_ERROR, UAPI_WARN logs");
- /**
- * Logs debugging information, such as the values of variables. This level also logs ERROR, WARN.
- */
- public static LogLevel LEVEL_INFO =
- new LogLevel("log UAPI_ERROR, UAPI_WARN, UAPI_INFO logs");
- /**
- * Logs when loggers are created or destroyed. This level also logs INFO, WARN, ERROR.
- */
- public static LogLevel LEVEL_TRACE =
- new LogLevel("log UAPI_ERROR, UAPI_WARN, UAPI_INFO, UAPI_TRACE logs");
- private String message;
-
- /**
- * Creates a new LogLevel.
- *
- * @param message the message associated with the LogLevel.
- */
- private LogLevel(String message)
- {
- this.message = message;
- }
-
- @Override
- public String toString()
- {
- return message;
- }
- }
-
- /**
- * Returns the singleton instance.
- *
- * @return the singleton instance
- */
- public static Logger getInstance()
- {
- return LoggerImpl.getInstance();
- }
-
- /**
- * Sets the logging level.
- *
- * @param level the logging level
- */
- public abstract void setLoggingLevel(LogLevel level);
-
- /**
- * Sets the log path.
- *
- * @param path the path of the log file
- */
- public abstract void setPath(String path);
-
- /**
- * Logs an error message.
- *
- * @param message the message to log
- */
- public abstract void error(String message);
-
- /**
- * Logs a warning message.
- *
- * @param message the message to log
- */
- public abstract void warn(String message);
-
- /**
- * Logs an informational message.
- *
- * @param message the message to log
- */
- public abstract void info(String message);
-
- /**
- * Logs a method tracing message.
- *
- * @param message the message to log
- */
- public abstract void trace(String message);
-}
diff --git a/core/java/android/speech/recognition/MediaFileReader.java b/core/java/android/speech/recognition/MediaFileReader.java
deleted file mode 100644
index 216511f..0000000
--- a/core/java/android/speech/recognition/MediaFileReader.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*---------------------------------------------------------------------------*
- * MediaFileReader.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-import android.speech.recognition.impl.MediaFileReaderImpl;
-
-/**
- * Reads audio from a file.
- */
-public abstract class MediaFileReader implements AudioSource
-{
- /**
- * Reading mode
- */
- public static class Mode
- {
- /**
- * Read the file in "real time".
- */
- public static Mode REAL_TIME = new Mode("real-time");
- /**
- * Read the file all at once.
- */
- public static Mode ALL_AT_ONCE = new Mode("all at once");
- private String message;
-
- /**
- * Creates a new Mode.
- *
- * @param message the message associated with the reading mode.
- */
- private Mode(String message)
- {
- this.message = message;
- }
- }
-
- /**
- * Creates a new MediaFileReader to read audio samples from a file.
- *
- * @param filename the name of the file to read from Note: The file MUST be of type Microsoft WAVE RIFF
- * format (PCM 16 bits 8000 Hz or PCM 16 bits 11025 Hz).
- * @param listener listens for MediaFileReader events
- * @return a new MediaFileReader
- * @throws IllegalArgumentException if filename is null or is an empty string. Or if offset > file length. Or if codec is null or invalid
- */
- public static MediaFileReader create(String filename, AudioSourceListener listener) throws IllegalArgumentException
- {
- return new MediaFileReaderImpl(filename, listener);
- }
-
- /**
- * Sets the reading mode.
- *
- * @param mode the reading mode
- */
- public abstract void setMode(Mode mode);
-
- /**
- * Creates an audio source.
- */
- public abstract AudioStream createAudio();
-
- /**
- * Starts collecting audio samples.
- */
- public abstract void start();
-
- /**
- * Stops collecting audio samples.
- */
- public abstract void stop();
-}
diff --git a/core/java/android/speech/recognition/MediaFileReaderListener.java b/core/java/android/speech/recognition/MediaFileReaderListener.java
deleted file mode 100644
index f76e65f..0000000
--- a/core/java/android/speech/recognition/MediaFileReaderListener.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*---------------------------------------------------------------------------*
- * MediaFileReaderListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-import android.speech.recognition.AudioSourceListener;
-
-/**
- * Listens for MediaFileReader events.
- */
-public interface MediaFileReaderListener extends AudioSourceListener
-{
-}
diff --git a/core/java/android/speech/recognition/MediaFileWriter.java b/core/java/android/speech/recognition/MediaFileWriter.java
deleted file mode 100644
index b2d627c..0000000
--- a/core/java/android/speech/recognition/MediaFileWriter.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*---------------------------------------------------------------------------*
- * MediaFileWriter.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-import android.speech.recognition.impl.MediaFileWriterImpl;
-
-/**
- * Writes audio to a file.
- */
-public abstract class MediaFileWriter
-{
- /**
- * Creates a new MediaFileWriter to write audio samples into a file.
- *
- * @param listener listens for MediaFileWriter events
- * @return a new MediaFileWriter
- */
- public static MediaFileWriter create(MediaFileWriterListener listener)
- {
- return new MediaFileWriterImpl(listener);
- }
-
- /**
- * Saves audio to a file.
- *
- * @param source the audio stream to write
- * @param filename the file to write to
- * @throws IllegalArgumentException if source is null, in-use by another
- * component or contains no data. Or if filename is null or is empty.
- */
- public abstract void save(AudioStream source, String filename) throws IllegalArgumentException;
-}
diff --git a/core/java/android/speech/recognition/MediaFileWriterListener.java b/core/java/android/speech/recognition/MediaFileWriterListener.java
deleted file mode 100644
index e2104c8..0000000
--- a/core/java/android/speech/recognition/MediaFileWriterListener.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*---------------------------------------------------------------------------*
- * MediaFileWriterListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Listens for MediaFileWriter events.
- */
-public interface MediaFileWriterListener
-{
- /**
- * Invoked after the save() operation terminates
- */
- void onStopped();
-
- /**
- * Invoked when an unexpected error occurs. This is normally followed by
- * onStopped() if the component shuts down successfully.
- *
- * @param e the cause of the failure.<br/>
- * {@link java.io.IOException} if an error occured opening or writing to the file
- */
- void onError(Exception e);
-}
diff --git a/core/java/android/speech/recognition/Microphone.java b/core/java/android/speech/recognition/Microphone.java
deleted file mode 100644
index 1b713f5..0000000
--- a/core/java/android/speech/recognition/Microphone.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*---------------------------------------------------------------------------*
- * Microphone.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-import android.speech.recognition.impl.MicrophoneImpl;
-
-/**
- * Records live audio.
- */
-public abstract class Microphone implements AudioSource
-{
- private static Microphone instance;
-
- /**
- * Returns the microphone instance
- *
- * @return an instance of a Microphone class.
- */
- public static Microphone getInstance()
- {
- instance = MicrophoneImpl.getInstance();
- return instance;
- }
-
- /**
- * Sets the recording codec. This must be called before start() is called.
- *
- * @param recordingCodec the codec in which the samples will be recorded.
- * @throws IllegalStateException if Microphone is started
- * @throws IllegalArgumentException if codec is not supported
- */
- public abstract void setCodec(Codec recordingCodec) throws IllegalStateException,
- IllegalArgumentException;
-
- /**
- * Sets the microphone listener.
- *
- * @param listener the microphone listener.
- * @throws IllegalStateException if Microphone is started
- */
- public abstract void setListener(AudioSourceListener listener) throws IllegalStateException;
-
- /**
- * Creates an audio source
- */
- public abstract AudioStream createAudio();
-
- /**
- * Start recording audio.
- *
- * @throws IllegalStateException if Microphone is already started
- */
- public abstract void start() throws IllegalStateException;
-
- /**
- * Stops recording audio.
- */
- public abstract void stop();
-}
diff --git a/core/java/android/speech/recognition/MicrophoneListener.java b/core/java/android/speech/recognition/MicrophoneListener.java
deleted file mode 100644
index f43eff9..0000000
--- a/core/java/android/speech/recognition/MicrophoneListener.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*---------------------------------------------------------------------------*
- * MicrophoneListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-import android.speech.recognition.AudioSourceListener;
-
-/**
- * Listens for Microphone events.
- */
-public interface MicrophoneListener extends AudioSourceListener
-{
-}
diff --git a/core/java/android/speech/recognition/NBestRecognitionResult.java b/core/java/android/speech/recognition/NBestRecognitionResult.java
deleted file mode 100644
index e679c19..0000000
--- a/core/java/android/speech/recognition/NBestRecognitionResult.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*---------------------------------------------------------------------------*
- * NBestRecognitionResult.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-import java.util.Enumeration;
-
-/**
- * N-Best recognition results. Entries are sorted in decreasing order according
- * to their probability, from the most probable result to the least probable
- * result.
- */
-public interface NBestRecognitionResult extends RecognitionResult
-{
- /**
- * Recognition result entry
- */
- public static interface Entry
- {
- /**
- * Returns the semantic meaning of a recognition result (i.e.&nbsp;the application-specific value
- * associated with what the user said). In an example where a person's name is mapped
- * to a phone-number, the phone-number is the semantic meaning.
- *
- * @return the semantic meaning of a recognition result.
- * @throws IllegalStateException if the object has been disposed
- */
- String getSemanticMeaning() throws IllegalStateException;
-
- /**
- * The confidence score of a recognition result. Values range from 0 to 100
- * (inclusive).
- *
- * @return the confidence score of a recognition result.
- * @throws IllegalStateException if the object has been disposed
- */
- byte getConfidenceScore() throws IllegalStateException;
-
- /**
- * Returns the literal meaning of a recognition result (i.e.&nbsp;literally
- * what the user said). In an example where a person's name is mapped to a
- * phone-number, the person's name is the literal meaning.
- *
- * @return the literal meaning of a recognition result.
- * @throws IllegalStateException if the object has been disposed
- */
- String getLiteralMeaning() throws IllegalStateException;
-
- /**
- * Returns the value associated with the specified key.
- *
- * @param key the key to look up
- * @return the associated value or null if this entry does not contain
- * any mapping for the key
- */
- String get(String key);
-
- /**
- * Returns an enumeration of the keys in this Entry.
- *
- * @return an enumeration of the keys in this Entry.
- */
- Enumeration keys();
- }
-
- /**
- * Returns the number of entries in the n-best list.
- *
- * @return the number of entries in the n-best list
- */
- int getSize();
-
- /**
- * Returns the n-best entry that contains key-value pairs associated with the
- * recognition result.
- *
- * @param index the index of the n-best entry
- * @return null if all active GrammarConfiguration.grammarToMeaning() return
- * null
- * @throws ArrayIndexOutOfBoundsException if index is greater than size of
- * entries
- */
- Entry getEntry(int index) throws ArrayIndexOutOfBoundsException;
-
- /**
- * Creates a new VoicetagItem if the last recognition was an enrollment
- * operation.
- *
- * @param VoicetagId string voicetag unique id value.
- * @param listener listens for Voicetag events
- * @return the resulting VoicetagItem
- * @throws IllegalArgumentException if VoicetagId is null or an empty string.
- * @throws IllegalStateException if the last recognition was not an
- * enrollment operation
- */
- VoicetagItem createVoicetagItem(String VoicetagId, VoicetagItemListener listener) throws IllegalArgumentException,IllegalStateException;
-}
diff --git a/core/java/android/speech/recognition/ParameterErrorException.java b/core/java/android/speech/recognition/ParameterErrorException.java
deleted file mode 100644
index 042ed31..0000000
--- a/core/java/android/speech/recognition/ParameterErrorException.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*---------------------------------------------------------------------------*
- * ParameterErrorException.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Thrown if an error occurs in the audio driver.
- */
-public class ParameterErrorException extends Exception
-{
- private static final long serialVersionUID = 0L;
-
- public ParameterErrorException(String msg)
- {
- super(msg);
- }
-}
diff --git a/core/java/android/speech/recognition/ParametersListener.java b/core/java/android/speech/recognition/ParametersListener.java
deleted file mode 100644
index bdb551e..0000000
--- a/core/java/android/speech/recognition/ParametersListener.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*---------------------------------------------------------------------------*
- * ParametersListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-import java.util.Hashtable;
-import java.util.Vector;
-
-/**
- * Listens for parameter events.
- */
-public interface ParametersListener
-{
- /**
- * Invoked if retrieving parameters has failed.
- *
- * @param parameters the parameters that could not be retrieved
- * @param e the failure reason
- */
- void onParametersGetError(Vector<String> parameters, Exception e);
-
- /**
- * Invoked if setting parameters has failed.
- *
- * @param parameters the parameters that could not be set
- * @param e the failure reason
- */
- void onParametersSetError(Hashtable<String, String> parameters, Exception e);
-
- /**
- * This method is called when the parameters specified in setParameters have
- * successfully been set. This method is guaranteed to be invoked after
- * onParametersSetError, even if count==0.
- *
- * @param parameters the set parameters
- */
- void onParametersSet(Hashtable<String, String> parameters);
-
- /**
- * This method is called when the parameters specified in getParameters have
- * successfully been retrieved. This method is guaranteed to be invoked after
- * onParametersGetError, even if count==0.
- *
- * @param parameters the retrieved parameters
- */
- void onParametersGet(Hashtable<String, String> parameters);
-}
diff --git a/core/java/android/speech/recognition/ParseErrorException.java b/core/java/android/speech/recognition/ParseErrorException.java
deleted file mode 100644
index 2288a90..0000000
--- a/core/java/android/speech/recognition/ParseErrorException.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*---------------------------------------------------------------------------*
- * ParseErrorException.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Thrown if an error occurs in the audio driver.
- */
-public class ParseErrorException extends Exception
-{
- private static final long serialVersionUID = 0L;
-
- public ParseErrorException(String msg)
- {
- super(msg);
- }
-}
diff --git a/core/java/android/speech/recognition/RecognitionResult.java b/core/java/android/speech/recognition/RecognitionResult.java
deleted file mode 100644
index cbbc938..0000000
--- a/core/java/android/speech/recognition/RecognitionResult.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*---------------------------------------------------------------------------*
- * RecognitionResult.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Recognition result interface.
- */
-public interface RecognitionResult
-{
-}
diff --git a/core/java/android/speech/recognition/Recognizer.java b/core/java/android/speech/recognition/Recognizer.java
deleted file mode 100644
index ab7f8f4..0000000
--- a/core/java/android/speech/recognition/Recognizer.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*---------------------------------------------------------------------------*
- * Recognizer.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-import java.util.Hashtable;
-import java.util.Vector;
-
-/**
- * Speech recognizer interface.
- */
-public interface Recognizer
-{
- /**
- * Sets the recognizer event listener.
- *
- * @param listener listens for recognizer events
- */
- void setListener(RecognizerListener listener);
-
- /**
- * Creates an embedded grammar.
- *
- * @param value value of that grammarType. Could be a URL or an inline grammar.
- * @return a grammar
- * @throws IllegalArgumentException if value is null or listener is not of type
- * GrammarListener.
- */
- Grammar createGrammar(String value, GrammarListener listener) throws IllegalArgumentException;
-
- /**
- * Begins speech recognition.
- *
- * @param audio the audio stream to recognizer
- * @param grammars a collection of grammar sets to recognize against
- * @see #recognize(AudioStream, Grammar)
- * @throws IllegalStateException if any of the grammars are not loaded
- * @throws IllegalArgumentException if audio is null, in-use by another
- * component or empty. Or if grammars is null or grammars count is less than
- * one. Or if the audio codec differs from recognizer codec.
- * @throws UnsupportedOperationException if the recognizer does not support
- * the number of grammars specified.
- */
- void recognize(AudioStream audio,
- Vector<Grammar> grammars) throws IllegalStateException,
- IllegalArgumentException, UnsupportedOperationException;
-
- /**
- * This convenience method is equivalent to invoking
- * recognize(audio, grammars) with a single grammar.
- *
- * @param audio the audio to recognizer
- * @param grammar a grammar to recognize against
- * @see #recognize(AudioStream, Vector)
- * @throws IllegalStateException if grammar is not loaded
- * @throws IllegalArgumentException if audio is null, in-use by another
- * component or is empty. Or if grammar is null or if the audio codec differs
- * from the recognizer codec.
- */
- void recognize(AudioStream audio, Grammar grammar) throws IllegalStateException,
- IllegalArgumentException;
-
- /**
- * Terminates a recognition if one is in-progress.
- * This must not be called until the recognize method
- * returns; otherwise the result is not defined.
- *
- * @see RecognizerListener#onStopped
- */
- void stop();
-
- /**
- * Sets the values of recognition parameters.
- *
- * @param parameters the parameter key-value pairs to set
- */
- void setParameters(Hashtable<String, String> parameters);
-
- /**
- * Retrieves the values of recognition parameters.
- *
- * @param parameters the names of the parameters to retrieve
- */
- void getParameters(Vector<String> parameters);
-
-}
diff --git a/core/java/android/speech/recognition/RecognizerListener.java b/core/java/android/speech/recognition/RecognizerListener.java
deleted file mode 100644
index d7bbda9..0000000
--- a/core/java/android/speech/recognition/RecognizerListener.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*---------------------------------------------------------------------------*
- * RecognizerListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Listens for recognizer events.
- */
-public interface RecognizerListener extends ParametersListener
-{
- /**
- * Recognition failure.
- */
- public static class FailureReason
- {
- /**
- * The audio did not generate any results.
- */
- public static FailureReason NO_MATCH =
- new FailureReason("The audio did not generate any results");
- /**
- * Beginning of speech occured too soon.
- */
- public static FailureReason SPOKE_TOO_SOON =
- new FailureReason("Beginning of speech occurred too soon");
- /**
- * A timeout occured before the beginning of speech.
- */
- public static FailureReason BEGINNING_OF_SPEECH_TIMEOUT =
- new FailureReason("A timeout occurred before the beginning of " + "speech");
- /**
- * A timeout occured before the recognition could complete.
- */
- public static FailureReason RECOGNITION_TIMEOUT =
- new FailureReason("A timeout occurred before the recognition " +
- "could complete");
- /**
- * The recognizer encountered more audio than was acceptable according to
- * its configuration.
- */
- public static FailureReason TOO_MUCH_SPEECH =
- new FailureReason("The " +
- "recognizer encountered more audio than was acceptable according to " +
- "its configuration");
-
- public static FailureReason UNKNOWN =
- new FailureReason("unknown failure reason");
-
- private final String message;
-
- private FailureReason(String message)
- {
- this.message = message;
- }
-
- @Override
- public String toString()
- {
- return message;
- }
- }
-
- /**
- * Invoked after recognition begins.
- */
- void onStarted();
-
- /**
- * Invoked if the recognizer detects the beginning of speech.
- */
- void onBeginningOfSpeech();
-
- /**
- * Invoked if the recognizer detects the end of speech.
- */
- void onEndOfSpeech();
-
- /**
- * Invoked if the recognizer does not detect speech within the configured
- * timeout period.
- */
- void onStartOfSpeechTimeout();
-
- /**
- * Invoked when the recognizer acoustic state is reset.
- *
- * @see android.speech.recognition.EmbeddedRecognizer#resetAcousticState()
- */
- void onAcousticStateReset();
-
- /**
- * Invoked when a recognition result is generated.
- *
- * @param result the recognition result. The result object can not be
- * used outside of the scope of the onRecognitionSuccess() callback method.
- * To be able to do so, copy it's contents to an user-defined object.<BR>
- * An example of this object could be a vector of string arrays; where the
- * vector represents a list of recognition result entries and each entry
- * is an array of strings to hold the entry's values (the semantic
- * meaning, confidence score and literal meaning).
- */
- void onRecognitionSuccess(RecognitionResult result);
-
- /**
- * Invoked when a recognition failure occurs.
- *
- * @param reason the failure reason
- */
- void onRecognitionFailure(FailureReason reason);
-
- /**
- * Invoked when an unexpected error occurs. This is normally followed by
- * onStopped() if the component shuts down successfully.
- *
- * @param e the cause of the failure
- */
- void onError(Exception e);
-
- /**
- * Invoked when the recognizer stops (due to normal termination or an error).
- *
- * Invoking stop() on a recognizer that is already stopped will not result
- * in a onStopped() event.
- */
- void onStopped();
-}
diff --git a/core/java/android/speech/recognition/SlotItem.java b/core/java/android/speech/recognition/SlotItem.java
deleted file mode 100644
index 3abd27a..0000000
--- a/core/java/android/speech/recognition/SlotItem.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*---------------------------------------------------------------------------*
- * SlotItem.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Item that may be inserted into an embedded grammar slot.
- */
-public interface SlotItem
-{
-}
diff --git a/core/java/android/speech/recognition/SrecGrammar.java b/core/java/android/speech/recognition/SrecGrammar.java
deleted file mode 100644
index c591e05..0000000
--- a/core/java/android/speech/recognition/SrecGrammar.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*---------------------------------------------------------------------------*
- * SrecGrammar.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-import java.util.Vector;
-
-/**
- * Grammar on an SREC recognizer.
- */
-public interface SrecGrammar extends EmbeddedGrammar
-{
- /**
- * SrecGrammar Item
- */
- public class Item
- {
- public SlotItem _item;
- public int _weight;
- public String _semanticMeaning;
-
- /**
- * Creates a grammar item.
- *
- * @param item the Slotitem.
- * @param weight the weight of the item. Smaller values are more likely to get recognized. This should be >= 0.
- * @param semanticMeaning the value that will be returned if this item is recognized.
- * @throws IllegalArgumentException if item or semanticMeaning are null; if semanticMeaning is empty."
- */
- public Item(SlotItem item, int weight, String semanticMeaning)
- throws IllegalArgumentException
- {
- if (item == null)
- throw new IllegalArgumentException("Item(): item can't be null.");
- if (semanticMeaning == null || semanticMeaning.length()==0)
- throw new IllegalArgumentException("Item(): semanticMeaning is null or empty.");
- _item = item;
- _weight = weight;
- _semanticMeaning = semanticMeaning;
-
- }
- }
-
- /**
- * Adds an item to a slot.
- *
- * @param slotName the name of the slot
- * @param item the item to add to the slot.
- * @param weight the weight of the item. Smaller values are more likely to get recognized. This should be >= 0.
- * @param semanticMeaning the value that will be returned if this item is recognized.
- * @throws IllegalArgumentException if slotName, item or semanticMeaning are null; if semanticMeaning is not of the format "V=&#039;Jen_Parker&#039;"
- */
- public void addItem(String slotName, SlotItem item, int weight,
- String semanticMeaning) throws IllegalArgumentException;
-
- /**
- * Add a list of item to a slot.
- *
- * @param slotName the name of the slot
- * @param items the vector of SrecGrammar.Item to add to the slot.
- * @throws IllegalArgumentException if slotName,items are null or any element in the items(_item, _semanticMeaning) is null; if any semanticMeaning of the list is not of the format "key=&#039;value&#039"
- */
- public void addItemList(String slotName, Vector<Item> items)
- throws IllegalArgumentException;
-
-}
diff --git a/core/java/android/speech/recognition/SrecGrammarListener.java b/core/java/android/speech/recognition/SrecGrammarListener.java
deleted file mode 100644
index e1f7d3f..0000000
--- a/core/java/android/speech/recognition/SrecGrammarListener.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*---------------------------------------------------------------------------*
- * SrecGrammarListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Listens for SrecGrammar events.
- */
-public interface SrecGrammarListener extends EmbeddedGrammarListener {
-
- /**
- * Invokes after all items of the list have been added.
- */
- void onAddItemList();
-
- /**
- * Invoked when adding a SlotItem from a list fails.
- * This callback will be trigger for each element in the list that fails to be
- * add in the slot, unless there is a grammar fail operation, which will be
- * reported in the onError callback.
- * @param index of the list that could not be added to the slot
- * @param e the cause of the failure.
- */
- void onAddItemListFailure(int index, Exception e);
-
-
- /**
- * Invoked when a grammar related operation fails.
- *
- * @param e the cause of the failure.<br/>
- * {@link GrammarOverflowException} if the grammar slot is full and no
- * further items may be added to it.<br/>
- * {@link java.lang.UnsupportedOperationException} if different words with
- * the same pronunciation are added.<br/>
- * {@link java.lang.IllegalStateException} if reseting or compiling the
- * slots fails.<br/>
- * {@link java.io.IOException} if the grammar could not be loaded or
- * saved.</p>
- */
- void onError(Exception e);
-
-}
diff --git a/core/java/android/speech/recognition/VoicetagItem.java b/core/java/android/speech/recognition/VoicetagItem.java
deleted file mode 100644
index 0b89639..0000000
--- a/core/java/android/speech/recognition/VoicetagItem.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*---------------------------------------------------------------------------*
- * VoicetagItem.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-import android.speech.recognition.impl.VoicetagItemImpl;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-/**
- * Voicetag that may be inserted into an embedded grammar slot.
- */
-public abstract class VoicetagItem implements SlotItem
-{
- /**
- * Creates a VoicetagItem from a file
- *
- * @param filename filename for Voicetag
- * @param listener listens for Voicetag events
- * @return the resulting VoicetagItem
- * @throws IllegalArgumentException if filename is null or an empty string.
- * @throws FileNotFoundException if the specified filename could not be found
- * @throws IOException if the specified filename could not be opened
- */
- public static VoicetagItem create(String filename, VoicetagItemListener listener) throws IllegalArgumentException,FileNotFoundException,IOException
- {
- return VoicetagItemImpl.create(filename,listener);
- }
- /**
- * Returns the audio used to construct the VoicetagItem.
- * The audio is in PCM format and is start-pointed and end-pointed. The audio
- * is only generated if the enableGetWaveform recognition parameter
- * is set prior to recognition.
- *
- * @throws IllegalStateException if the recognition parameter 'enableGetWaveform' is not set
- * @return the audio used to construct the VoicetagItem.
- */
- public abstract byte[] getAudio() throws IllegalStateException;
-
- /**
- * Sets the audio used to construct the Voicetag. The
- * audio is in PCM format and is start-pointed and end-pointed. The audio is
- * only generated if the enableGetWaveform recognition parameter is set
- * prior to recognition.
- *
- * @param waveform the endpointed waveform
- * @throws IllegalArgumentException if waveform is null or empty.
- * @throws IllegalStateException if the recognition parameter 'enableGetWaveform' is not set
- */
- public abstract void setAudio(byte[] waveform) throws IllegalArgumentException,IllegalStateException;
-
- /**
- * Save the Voicetag Item.
- *
- * @param path where the Voicetag will be saved. We strongly recommend to set the filename with the same value of the VoicetagId.
- * @throws IllegalArgumentException if path is null or an empty string.
- */
- public abstract void save(String path) throws IllegalArgumentException,IllegalStateException;
-
- /**
- * Load a Voicetag Item.
- *
- * @throws IllegalStateException if voicetag has not been created from a file.
- */
- public abstract void load() throws IllegalStateException;
-
-}
diff --git a/core/java/android/speech/recognition/VoicetagItemListener.java b/core/java/android/speech/recognition/VoicetagItemListener.java
deleted file mode 100644
index 610d1c7..0000000
--- a/core/java/android/speech/recognition/VoicetagItemListener.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*---------------------------------------------------------------------------*
- * VoicetagItemListener.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-/**
- * Listens for VoicetagItem events.
- */
-public interface VoicetagItemListener
-{
- /**
- * Invoked after the Voicetag is saved.
- *
- * @param path the path the Voicetag was saved to
- */
- void onSaved(String path);
-
- /**
- * Invoked after the Voicetag is loaded.
- */
- void onLoaded();
-
- /**
- * Invoked when a grammar operation fails.
- *
- * @param e the cause of the failure.<br/>
- * {@link java.io.IOException} if the Voicetag could not be loaded or
- * saved.</p>
- * {@link java.io.FileNotFoundException} if the specified file could not be found
- */
- void onError(Exception e);
-
-}
diff --git a/core/java/android/speech/recognition/WordItem.java b/core/java/android/speech/recognition/WordItem.java
deleted file mode 100644
index 5c21c98..0000000
--- a/core/java/android/speech/recognition/WordItem.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*---------------------------------------------------------------------------*
- * WordItem.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition;
-
-import android.speech.recognition.impl.WordItemImpl;
-
-/**
- * Word that may be inserted into an embedded grammar slot.
- */
-public abstract class WordItem implements SlotItem
-{
- /**
- * Creates a new WordItem.
- *
- * @param word the word to insert
- * @param pronunciations the pronunciations to associated with the item. If the list is
- * is empty (example:new String[0]) the recognizer will attempt to guess the pronunciations.
- * @return the WordItem
- * @throws IllegalArgumentException if word is null or if pronunciations is
- * null or pronunciations contains an element equal to null or empty string.
- */
- public static WordItem valueOf(String word, String[] pronunciations) throws IllegalArgumentException
- {
- return WordItemImpl.valueOf(word, pronunciations);
- }
-
- /**
- * Creates a new WordItem.
- *
- * @param word the word to insert
- * @param pronunciation the pronunciation to associate with the item. If it
- * is null the recognizer will attempt to guess the pronunciations.
- * @return the WordItem
- * @throws IllegalArgumentException if word is null or if pronunciation is
- * an empty string
- */
- public static WordItem valueOf(String word, String pronunciation) throws IllegalArgumentException
- {
- return WordItemImpl.valueOf(word, pronunciation);
- }
-}
diff --git a/core/java/android/speech/recognition/impl/AudioStreamImpl.java b/core/java/android/speech/recognition/impl/AudioStreamImpl.java
deleted file mode 100644
index 730e2d9..0000000
--- a/core/java/android/speech/recognition/impl/AudioStreamImpl.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*---------------------------------------------------------------------------*
- * AudioStreamImpl.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import android.speech.recognition.AudioStream;
-
-/**
- */
-public class AudioStreamImpl implements AudioStream, Runnable
-{
- /**
- * Reference to the native object.
- */
- private long nativeObject;
-
- /**
- * Creates a new AudioStreamImpl.
- *
- * @param nativeObj a reference to the native object
- */
- public AudioStreamImpl(long nativeObj)
- {
- nativeObject = nativeObj;
- }
-
- public synchronized void run()
- {
- dispose();
- }
-
- public long getNativeObject() {
- synchronized (AudioStreamImpl.class)
- {
- return nativeObject;
- }
- }
-
- /**
- * Releases the native resources associated with the object.
- */
- @SuppressWarnings("deprecation")
- public void dispose()
- {
- synchronized (AudioStreamImpl.class)
- {
- if (nativeObject != 0)
- {
- deleteNativeObject(nativeObject);
- nativeObject = 0;
- }
- }
- }
-
- @Override
- protected void finalize() throws Throwable
- {
- dispose();
- super.finalize();
- }
-
- /**
- * Deletes a native object.
- *
- * @param nativeObject pointer to the native object
- */
- private native void deleteNativeObject(long nativeObject);
-}
diff --git a/core/java/android/speech/recognition/impl/DeviceSpeakerImpl.java b/core/java/android/speech/recognition/impl/DeviceSpeakerImpl.java
deleted file mode 100644
index 5d72110..0000000
--- a/core/java/android/speech/recognition/impl/DeviceSpeakerImpl.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*---------------------------------------------------------------------------*
- * DeviceSpeakerImpl.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import android.speech.recognition.AudioStream;
-import android.speech.recognition.Codec;
-import android.speech.recognition.DeviceSpeaker;
-import android.speech.recognition.DeviceSpeakerListener;
-
-/**
- */
-public class DeviceSpeakerImpl extends DeviceSpeaker implements Runnable
-{
- private static DeviceSpeakerImpl instance;
- /**
- * Reference to the native object.
- */
- private long nativeObject;
- private DeviceSpeakerListener locallistener;
-
- /**
- * Private constructor
- */
- private DeviceSpeakerImpl()
- {
- System system = System.getInstance();
- nativeObject = initNativeObject();
- if (nativeObject != 0)
- system.register(this);
- }
-
- public void run()
- {
- dispose();
- }
-
- /**
- * Returns the singleton instance.
- *
- * @return the singleton instance
- */
- public static DeviceSpeakerImpl getInstance()
- {
- synchronized (DeviceSpeakerImpl.class)
- {
- if (instance == null)
- instance = new DeviceSpeakerImpl();
- return instance;
- }
- }
-
- /**
- * Start audio playback.
- *
- * @param source the audio to play
- */
- public void start(AudioStream source)
- {
- synchronized (DeviceSpeakerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- AudioStreamImpl src = (AudioStreamImpl)source;
- startProxy(nativeObject,src.getNativeObject());
- src = null;
- }
- }
-
- /**
- * Stops audio playback.
- */
- public void stop()
- {
- synchronized (DeviceSpeakerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- stopProxy(nativeObject);
- }
- }
-
- /**
- * Set the playback codec. This must be called before start is called.
- * @param playbackCodec the codec to use for the playback operation.
- */
- public void setCodec(Codec playbackCodec)
- {
- synchronized (DeviceSpeakerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- setCodecProxy(nativeObject,playbackCodec);
- }
- }
-
- /**
- * set the microphone listener.
- * @param listener the device speaker listener.
- */
- public void setListener(DeviceSpeakerListener listener)
- {
- synchronized (DeviceSpeakerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- locallistener = listener;
- setListenerProxy(nativeObject,listener);
- }
- }
-
- /**
- * Releases the native resources associated with the object.
- */
- private void dispose()
- {
- synchronized (DeviceSpeakerImpl.class)
- {
- if (nativeObject != 0)
- {
- deleteNativeObject(nativeObject);
- nativeObject = 0;
- instance = null;
- locallistener = null;
- System.getInstance().unregister(this);
- }
- }
- }
-
- @Override
- protected void finalize() throws Throwable
- {
- dispose();
- super.finalize();
- }
-
- private native long initNativeObject();
-
- private native void startProxy(long nativeObject, long audioNativeObject);
-
- private native void stopProxy(long nativeObject);
-
- private native void setCodecProxy(long nativeObject,Codec playbackCodec);
-
- private native void setListenerProxy(long nativeObject,DeviceSpeakerListener listener);
-
- private native void deleteNativeObject(long nativeObject);
-}
diff --git a/core/java/android/speech/recognition/impl/EmbeddedGrammarImpl.java b/core/java/android/speech/recognition/impl/EmbeddedGrammarImpl.java
deleted file mode 100644
index 0b88cb2..0000000
--- a/core/java/android/speech/recognition/impl/EmbeddedGrammarImpl.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*---------------------------------------------------------------------------*
- * EmbeddedGrammarImpl.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import android.speech.recognition.EmbeddedGrammar;
-
-/**
- */
-public class EmbeddedGrammarImpl extends GrammarImpl implements EmbeddedGrammar
-{
- /**
- * Creates a new EmbeddedGrammarImpl.
- *
- * @param nativeObject a reference to the native object
- */
- public EmbeddedGrammarImpl(long nativeObject)
- {
- super(nativeObject);
- }
-
- public void compileAllSlots()
- {
- synchronized (GrammarImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- compileAllSlotsProxy(nativeObject);
- }
- }
-
- public void resetAllSlots()
- {
- synchronized (GrammarImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- resetAllSlotsProxy(nativeObject);
- }
- }
-
- public void save(String url)
- {
- synchronized (GrammarImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- saveProxy(nativeObject, url.toString());
- }
- }
-
- private native void compileAllSlotsProxy(long nativeObject);
-
- private native void resetAllSlotsProxy(long nativeObject);
-
- private native void saveProxy(long nativeObject, String url);
-}
diff --git a/core/java/android/speech/recognition/impl/EmbeddedRecognizerImpl.java b/core/java/android/speech/recognition/impl/EmbeddedRecognizerImpl.java
deleted file mode 100644
index f04bfe4..0000000
--- a/core/java/android/speech/recognition/impl/EmbeddedRecognizerImpl.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*---------------------------------------------------------------------------*
- * EmbeddedRecognizerImpl.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Hashtable;
-import java.util.Vector;
-import android.speech.recognition.EmbeddedRecognizer;
-import android.speech.recognition.Grammar;
-import android.speech.recognition.AudioStream;
-import android.speech.recognition.Grammar;
-import android.speech.recognition.RecognizerListener;
-import android.speech.recognition.GrammarListener;
-
-/**
- */
-public class EmbeddedRecognizerImpl extends EmbeddedRecognizer implements Runnable
-{
- /**
- * Reference to the native object.
- */
- private long nativeObject;
- /**
- * The singleton instance.
- */
- private static EmbeddedRecognizerImpl instance;
-
- /**
- * Creates a new instance.
- */
- EmbeddedRecognizerImpl()
- {
- System system = System.getInstance();
- nativeObject = getInstanceProxy();
- if (nativeObject != 0)
- system.register(this);
- }
-
- /**
- * Returns the singleton instance.
- *
- * @return the singleton instance
- */
- public synchronized static EmbeddedRecognizerImpl getInstance()
- {
- synchronized (EmbeddedRecognizerImpl.class)
- {
- if (instance == null)
- instance = new EmbeddedRecognizerImpl();
- return instance;
- }
- }
-
- public void run()
- {
- dispose();
- }
-
- /**
- * Releases the native resources associated with the object.
- */
- private void dispose()
- {
- synchronized (EmbeddedRecognizerImpl.class)
- {
- if (instance != null)
- {
- deleteNativeObject(nativeObject);
- nativeObject = 0;
- instance = null;
- System.getInstance().unregister(this);
- }
- }
- }
-
- public void configure(String config) throws IllegalArgumentException,
- FileNotFoundException, IOException, UnsatisfiedLinkError,
- ClassNotFoundException
- {
- synchronized (EmbeddedRecognizerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- if (config == null)
- throw new IllegalArgumentException("Configuration Is Null.");
- configureProxy(nativeObject,config);
- }
- }
-
- public void setListener(RecognizerListener listener)
- {
- synchronized (EmbeddedRecognizerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- setListenerProxy(nativeObject,listener);
- }
- }
-
- public Grammar createGrammar(String value, GrammarListener listener)
- throws IllegalArgumentException
- {
- synchronized (EmbeddedRecognizerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- long nativeGrammar = createEmbeddedGrammarProxy(nativeObject,value.toString(), listener);
- return new SrecGrammarImpl(nativeGrammar);
- }
- }
-
- public void resetAcousticState()
- {
- synchronized (EmbeddedRecognizerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- resetAcousticStateProxy(nativeObject);
- }
- }
-
- public void recognize(AudioStream audio,
- Vector<Grammar> grammars)
- {
- synchronized (EmbeddedRecognizerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
-
- if (audio == null)
- throw new IllegalArgumentException("AudioStream cannot be null.");
-
- if (grammars == null || grammars.isEmpty() == true)
- throw new IllegalArgumentException("Grammars are null or empty.");
- int grammarCount = grammars.size();
-
- long[] nativeGrammars = new long[grammarCount];
-
- for (int i = 0; i < grammarCount; ++i)
- nativeGrammars[i] = ((GrammarImpl) grammars.get(i)).getNativeObject();
-
- recognizeProxy(nativeObject,((AudioStreamImpl)audio).getNativeObject(), nativeGrammars);
- }
- }
-
- public void recognize(AudioStream audio,
- Grammar grammar)
- {
- synchronized (EmbeddedRecognizerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- }
- Vector<Grammar> grammars = new Vector<Grammar>();
- grammars.add(grammar);
- recognize(audio, grammars);
- }
-
- public void stop()
- {
- synchronized (EmbeddedRecognizerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- stopProxy(nativeObject);
- }
- }
-
- public void setParameters(Hashtable<String, String> params)
- {
- synchronized (EmbeddedRecognizerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- setParametersProxy(nativeObject,params);
- }
- }
-
- public void getParameters(Vector<String> params)
- {
- synchronized (EmbeddedRecognizerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- getParametersProxy(nativeObject,params);
- }
- }
-
- /**
- * Returns the native EmbeddedRecognizer.
- *
- * @return a reference to the native object
- */
- private native long getInstanceProxy();
-
- /**
- * Configures the recognizer instance.
- *
- * @param config the recognizer configuration file
- */
- private native void configureProxy(long nativeObject, String config) throws IllegalArgumentException,
- FileNotFoundException, IOException, UnsatisfiedLinkError,
- ClassNotFoundException;
-
- /**
- * Sets the recognizer listener.
- *
- * @param listener listens for recognizer events
- */
- private native void setListenerProxy(long nativeObject, RecognizerListener listener);
-
- private native void recognizeProxy(long nativeObject, long audioNativeObject,
- long[] pGrammars);
-
- private native long createEmbeddedGrammarProxy(long nativeObject, String url,
- GrammarListener listener);
-
- private native void stopProxy(long nativeObject);
-
- private native void deleteNativeObject(long nativeObject);
-
- private native void setParametersProxy(long nativeObject, Hashtable<String, String> params);
-
- private native void getParametersProxy(long nativeObject, Vector<String> params);
-
- private native void resetAcousticStateProxy(long nativeObject);
-
-}
diff --git a/core/java/android/speech/recognition/impl/EntryImpl.java b/core/java/android/speech/recognition/impl/EntryImpl.java
deleted file mode 100644
index 91b2b78..0000000
--- a/core/java/android/speech/recognition/impl/EntryImpl.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*---------------------------------------------------------------------------*
- * EntryImpl.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import android.speech.recognition.NBestRecognitionResult;
-import java.util.Enumeration;
-
-/**
- */
-public class EntryImpl implements NBestRecognitionResult.Entry, Runnable
-{
- private long nativeObject;
-
- /**
- * This implementation is a work-around to solve Q bug with
- * nested classes.
- *
- * @param nativeObject the native NBestRecognitionResult.Entry object
- */
- public EntryImpl(long nativeObject)
- {
- this.nativeObject = nativeObject;
- }
-
- public void run()
- {
- dispose();
- }
-
- public byte getConfidenceScore() throws IllegalStateException
- {
- synchronized (EntryImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- return getConfidenceScoreProxy(nativeObject);
- }
- }
-
- public String getLiteralMeaning() throws IllegalStateException
- {
- synchronized (EntryImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- return getLiteralMeaningProxy(nativeObject);
- }
- }
-
- public String getSemanticMeaning() throws IllegalStateException
- {
- synchronized (EntryImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- return getSemanticMeaningProxy(nativeObject);
- }
- }
-
- public String get(String key)
- {
- synchronized (EntryImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- return getProxy(nativeObject,key);
- }
- }
-
- public Enumeration keys()
- {
- synchronized (EntryImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
-
- return new Enumeration()
- {
- private String[] keys = keysProxy(nativeObject);
- private int indexOfNextRead = 0;
-
- public boolean hasMoreElements()
- {
- return indexOfNextRead <= keys.length-1;
- }
-
- public Object nextElement()
- {
- return keys[indexOfNextRead++];
- }
- };
- }
- }
-
-
- /**
- * Releases the native resources associated with the object.
- */
- private void dispose()
- {
- synchronized (EntryImpl.class)
- {
- if (nativeObject != 0)
- {
- deleteNativeObject(nativeObject);
- nativeObject = 0;
- }
- }
- }
-
- @Override
- protected void finalize() throws Throwable
- {
- dispose();
- super.finalize();
- }
-
- private native void deleteNativeObject(long nativeObject);
-
- private native String getLiteralMeaningProxy(long nativeObject);
-
- private native String getSemanticMeaningProxy(long nativeObject);
-
- private native byte getConfidenceScoreProxy(long nativeObject);
-
- private native String getProxy(long nativeObject,String key);
-
- private native String[] keysProxy(long nativeObject);
-
-}
diff --git a/core/java/android/speech/recognition/impl/GrammarImpl.java b/core/java/android/speech/recognition/impl/GrammarImpl.java
deleted file mode 100644
index 563d5d9..0000000
--- a/core/java/android/speech/recognition/impl/GrammarImpl.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*---------------------------------------------------------------------------*
- * GrammarImpl.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import android.speech.recognition.Grammar;
-
-/**
- */
-public class GrammarImpl implements Grammar, Runnable
-{
- /**
- * Reference to the native object.
- */
- protected long nativeObject;
-
- /**
- * Creates a new GrammarImpl.
- *
- * @param nativeObj a reference to the native object
- */
- public GrammarImpl(long nativeObj)
- {
- nativeObject = nativeObj;
- }
-
- public void run()
- {
- dispose();
- }
-
- public long getNativeObject()
- {
- synchronized (GrammarImpl.class)
- {
- return nativeObject;
- }
- }
-
- /**
- * Indicates that the grammar will be used in the near future.
- */
- public void load()
- {
- synchronized (GrammarImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- loadProxy(nativeObject);
- }
- }
-
- /**
- * The grammar will be removed from use.
- */
- public void unload()
- {
- synchronized (GrammarImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- unloadProxy(nativeObject);
- }
- }
-
- /**
- * Releases the native resources associated with the object.
- */
- public void dispose()
- {
- synchronized (GrammarImpl.class)
- {
- if (nativeObject != 0)
- {
- deleteNativeObject(nativeObject);
- nativeObject = 0;
- }
- }
- }
-
- @Override
- protected void finalize() throws Throwable
- {
- dispose();
- super.finalize();
- }
-
- /**
- * Deletes a native object.
- *
- * @param nativeObject pointer to the native object
- */
- private native void deleteNativeObject(long nativeObject);
-
- private native void loadProxy(long nativeObject);
-
- private native void unloadProxy(long nativeObject);
-}
diff --git a/core/java/android/speech/recognition/impl/LoggerImpl.java b/core/java/android/speech/recognition/impl/LoggerImpl.java
deleted file mode 100644
index 9933c56..0000000
--- a/core/java/android/speech/recognition/impl/LoggerImpl.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*---------------------------------------------------------------------------*
- * LoggerImpl.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import android.speech.recognition.Logger;
-
-/**
- */
-public class LoggerImpl extends Logger implements Runnable
-{
- private static LoggerImpl instance;
- /**
- * Reference to the native object.
- */
- private long nativeObject;
-
- /**
- * Creates a new instance of LoggerImpl.
- *
- * @param function the name of the enclosing function
- */
- private LoggerImpl()
- {
- System system = System.getInstance();
- nativeObject = initNativeObject();
- if (nativeObject!=0)
- system.register(this);
- }
-
- public void run()
- {
- dispose();
- }
-
- /**
- * Returns the singleton instance.
- *
- * @return the singleton instance
- */
- public static LoggerImpl getInstance()
- {
- synchronized (LoggerImpl.class)
- {
- if (instance == null)
- instance = new LoggerImpl();
- return instance;
- }
- }
-
- public void setLoggingLevel(LogLevel level)
- {
- synchronized (LoggerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- setLoggingLevelProxy(nativeObject,level);
- }
- }
-
- public void setPath(String path)
- {
- synchronized (LoggerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- setPathProxy(nativeObject,path);
- }
- }
-
- public void error(String message)
- {
- synchronized (LoggerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- errorProxy(nativeObject,message);
- }
- }
-
- public void warn(String message)
- {
- synchronized (LoggerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- warnProxy(nativeObject,message);
- }
- }
-
- public void info(String message)
- {
- synchronized (LoggerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- infoProxy(nativeObject,message);
- }
- }
-
- public void trace(String message)
- {
- synchronized (LoggerImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- traceProxy(nativeObject,message);
- }
- }
-
- /**
- * Releases the native resources associated with the object.
- */
- private void dispose()
- {
- synchronized (LoggerImpl.class)
- {
- if (nativeObject!=0)
- {
- deleteNativeObject(nativeObject);
- System.getInstance().unregister(this);
- }
- nativeObject = 0;
- instance = null;
- }
- }
-
- @Override
- protected void finalize() throws Throwable
- {
- dispose();
- super.finalize();
- }
-
- private native long initNativeObject();
-
- private native void setLoggingLevelProxy(long nativeObject, LogLevel level);
-
- private native void setPathProxy(long nativeObject, String filename);
-
- private native void errorProxy(long nativeObject, String message);
-
- private native void warnProxy(long nativeObject, String message);
-
- private native void infoProxy(long nativeObject, String message);
-
- private native void traceProxy(long nativeObject,String message);
-
- private native void deleteNativeObject(long nativeObject);
-}
diff --git a/core/java/android/speech/recognition/impl/MediaFileReaderImpl.java b/core/java/android/speech/recognition/impl/MediaFileReaderImpl.java
deleted file mode 100644
index 8ce643d..0000000
--- a/core/java/android/speech/recognition/impl/MediaFileReaderImpl.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*---------------------------------------------------------------------------*
- * MediaFileReaderImpl.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import android.speech.recognition.MediaFileReader;
-import android.speech.recognition.AudioStream;
-import android.speech.recognition.Codec;
-import android.speech.recognition.AudioSourceListener;
-
-/**
- */
-public class MediaFileReaderImpl extends MediaFileReader implements Runnable
-{
- /**
- * Reference to the native object.
- */
- private long nativeObject;
-
- /**
- * Creates a new MediaFileReaderImpl.
- *
- * @param filename the name of the file to read from
- * @param listener listens for MediaFileReader events
- */
- public MediaFileReaderImpl(String filename, AudioSourceListener listener)
- {
- System system = System.getInstance();
- nativeObject =
- createMediaFileReaderProxy(filename, listener);
- if (nativeObject != 0)
- system.register(this);
- }
-
- public void run()
- {
- dispose();
- }
-
- /**
- * Set the reading mode
- */
- public void setMode(Mode mode)
- {
- synchronized (MediaFileReaderImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- setModeProxy(nativeObject,mode);
- }
- }
-
- /**
- * Creates an audioStream source
- */
- public AudioStream createAudio()
- {
- synchronized (MediaFileReaderImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- return new AudioStreamImpl(createAudioProxy(nativeObject));
- }
- }
-
- /**
- * Tells the audio source to start collecting audio samples.
- */
- public void start()
- {
- synchronized (MediaFileReaderImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- startProxy(nativeObject);
- }
- }
-
- /**
- * Stops this source from collecting audio samples.
- */
- public void stop()
- {
- synchronized (MediaFileReaderImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- stopProxy(nativeObject);
- }
- }
-
- /**
- * Releases the native resources associated with the object.
- */
- public void dispose()
- {
- synchronized (MediaFileReaderImpl.class)
- {
- if (nativeObject != 0)
- {
- deleteNativeObject(nativeObject);
- nativeObject = 0;
- System.getInstance().unregister(this);
- }
- }
- }
-
- @Override
- protected void finalize() throws Throwable
- {
- dispose();
- super.finalize();
- }
-
- /**
- * Deletes a native object.
- *
- * @param nativeObject pointer to the native object
- */
- private native void deleteNativeObject(long nativeObject);
-
- /**
- * Creates a native MediaFileReader.
- *
- * @param filename the name of the file to read from
- * @param offset the offset to begin reading from
- * @param codec the file audio format
- * @param listener listens for MediaFileReader events
- * @return a reference to the native object
- */
- private native long createMediaFileReaderProxy(String filename, AudioSourceListener listener);
-
- private native void setModeProxy(long nativeObject,Mode mode);
-
- private native long createAudioProxy(long nativeObject);
-
- private native void startProxy(long nativeObject);
-
- private native void stopProxy(long nativeObject);
-}
diff --git a/core/java/android/speech/recognition/impl/MediaFileWriterImpl.java b/core/java/android/speech/recognition/impl/MediaFileWriterImpl.java
deleted file mode 100644
index c4bd836..0000000
--- a/core/java/android/speech/recognition/impl/MediaFileWriterImpl.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*---------------------------------------------------------------------------*
- * MediaFileWriterImpl.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import android.speech.recognition.AudioStream;
-import android.speech.recognition.MediaFileWriter;
-import android.speech.recognition.MediaFileWriterListener;
-
-/**
- */
-public class MediaFileWriterImpl extends MediaFileWriter implements Runnable
-{
- /**
- * Reference to the native object.
- */
- private long nativeObject;
-
- /**
- * Creates a new MediaFileWriterImpl.
- *
- * @param listener listens for MediaFileWriter events
- */
- public MediaFileWriterImpl(MediaFileWriterListener listener)
- {
- System system = System.getInstance();
- nativeObject = createMediaFileWriterProxy(listener);
- if (nativeObject != 0)
- system.register(this);
- }
-
- public void run()
- {
- dispose();
- }
-
- public void save(AudioStream source, String filename)
- {
- synchronized (MediaFileWriterImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- saveProxy(nativeObject,((AudioStreamImpl)source).getNativeObject(), filename);
- }
- }
-
- /**
- * Releases the native resources associated with the object.
- */
- public synchronized void dispose()
- {
- synchronized (MediaFileWriterImpl.class)
- {
- if (nativeObject != 0)
- {
- deleteNativeObject(nativeObject);
- nativeObject = 0;
- System.getInstance().unregister(this);
- }
- }
- }
-
- @Override
- protected void finalize() throws Throwable
- {
- dispose();
- super.finalize();
- }
-
- /**
- * Creates a native MediaFileWriter.
- *
- * @param listener listens for MediaFileReader events
- * @return a reference to the native object
- */
- private native long createMediaFileWriterProxy(MediaFileWriterListener listener);
-
- /**
- * Deletes a native object.
- *
- * @param nativeObject pointer to the native object
- */
- private native void deleteNativeObject(long nativeObject);
-
- private native void saveProxy(long nativeObject, long audioNativeObject, String filename);
-}
diff --git a/core/java/android/speech/recognition/impl/MicrophoneImpl.java b/core/java/android/speech/recognition/impl/MicrophoneImpl.java
deleted file mode 100644
index a915484..0000000
--- a/core/java/android/speech/recognition/impl/MicrophoneImpl.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*---------------------------------------------------------------------------*
- * MicrophoneImpl.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import android.speech.recognition.AudioStream;
-import android.speech.recognition.Codec;
-import android.speech.recognition.Microphone;
-import android.speech.recognition.AudioSourceListener;
-
-/**
- */
-public class MicrophoneImpl extends Microphone implements Runnable
-{
- private static MicrophoneImpl instance;
- /**
- * Reference to the native object.
- */
- private long nativeObject;
-
- /**
- * Creates a new MicrophoneImpl.
- *
- * @param nativeObj a reference to the native object
- */
- private MicrophoneImpl()
- {
- System system = System.getInstance();
- nativeObject = initNativeObject();
- if (nativeObject != 0)
- system.register(this);
- }
-
- public void run()
- {
- dispose();
- }
-
- /**
- * Returns the singleton instance.
- *
- * @return the singleton instance
- */
- public static MicrophoneImpl getInstance()
- {
- synchronized (MicrophoneImpl.class)
- {
- if (instance == null)
- instance = new MicrophoneImpl();
- return instance;
- }
- }
-
- /**
- * set the recording codec. This must be called before Start is called.
- * @param recordingCodec the codec in which the samples will be recorded.
- */
- public void setCodec(Codec recordingCodec)
- {
- synchronized (MicrophoneImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- setCodecProxy(nativeObject,recordingCodec);
- }
- }
-
- /**
- * set the microphone listener.
- * @param listener the microphone listener.
- */
- public void setListener(AudioSourceListener listener)
- {
- synchronized (MicrophoneImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- setListenerProxy(nativeObject,listener);
- }
- }
-
- public AudioStream createAudio()
- {
- synchronized (MicrophoneImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- return new AudioStreamImpl(createAudioProxy(nativeObject));
- }
- }
-
- public void start()
- {
- synchronized (MicrophoneImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- startProxy(nativeObject);
- }
- }
-
- public void stop()
- {
- synchronized (MicrophoneImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- stopProxy(nativeObject);
- }
- }
-
- /**
- * Releases the native resources associated with the object.
- */
- private void dispose()
- {
- synchronized (MicrophoneImpl.class)
- {
- if (nativeObject != 0)
- {
- deleteNativeObject(nativeObject);
- nativeObject = 0;
- instance = null;
- System.getInstance().unregister(this);
- }
- }
- }
-
- @Override
- protected void finalize() throws Throwable
- {
- dispose();
- super.finalize();
- }
-
- private native long initNativeObject();
-
- private native void setCodecProxy(long nativeObject,Codec recordingCodec);
-
- private native void setListenerProxy(long nativeObject, AudioSourceListener listener);
-
- private native long createAudioProxy(long nativeObject);
-
- private native void startProxy(long nativeObject);
-
- private native void stopProxy(long nativeObject);
-
- private native void deleteNativeObject(long nativeObject);
-}
diff --git a/core/java/android/speech/recognition/impl/NBestRecognitionResultImpl.java b/core/java/android/speech/recognition/impl/NBestRecognitionResultImpl.java
deleted file mode 100644
index 4d2e00a..0000000
--- a/core/java/android/speech/recognition/impl/NBestRecognitionResultImpl.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*---------------------------------------------------------------------------*
- * NBestRecognitionResultImpl.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import android.speech.recognition.NBestRecognitionResult;
-import android.speech.recognition.VoicetagItem;
-import android.speech.recognition.VoicetagItemListener;
-/**
- */
-public class NBestRecognitionResultImpl implements NBestRecognitionResult
-{
- /**
- * Reference to the native object.
- */
- private long nativeObject;
-
- /**
- * Creates a new NBestRecognitionResultImpl.
- *
- * @param nativeObject a reference to the native object
- */
- public NBestRecognitionResultImpl(long nativeObject)
- {
- this.nativeObject = nativeObject;
- }
-
- public int getSize()
- {
- synchronized (NBestRecognitionResultImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- return getSizeProxy(nativeObject);
- }
- }
-
- public Entry getEntry(int index)
- {
- synchronized (NBestRecognitionResultImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- long nativeEntryObject = getEntryProxy(nativeObject,index);
- if (nativeEntryObject==0)
- return null;
- else
- return new EntryImpl(nativeEntryObject);
- }
- }
-
- public VoicetagItem createVoicetagItem(String VoicetagId, VoicetagItemListener listener)
- {
- synchronized (NBestRecognitionResultImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
- if ((VoicetagId == null) || (VoicetagId.length() == 0))
- throw new IllegalArgumentException("VoicetagId may not be null or empty string.");
- return new VoicetagItemImpl(createVoicetagItemProxy(nativeObject,VoicetagId,listener),false);
- }
- }
-
- /**
- * Releases the native resources associated with the object.
- */
- private void dispose()
- {
- synchronized (NBestRecognitionResultImpl.class)
- {
- nativeObject = 0;
- }
- }
-
- @Override
- protected void finalize() throws Throwable
- {
- dispose();
- super.finalize();
- }
-
- /**
- * Returns a reference to the native VoicetagItem.
- */
- private native long createVoicetagItemProxy(long nativeObject, String VoicetagId, VoicetagItemListener listener);
-
- private native long getEntryProxy(long nativeObject, int index);
-
- private native int getSizeProxy(long nativeObject);
-}
diff --git a/core/java/android/speech/recognition/impl/SrecGrammarImpl.java b/core/java/android/speech/recognition/impl/SrecGrammarImpl.java
deleted file mode 100644
index cb6f4c6..0000000
--- a/core/java/android/speech/recognition/impl/SrecGrammarImpl.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*---------------------------------------------------------------------------*
- * SrecGrammarImpl.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import android.speech.recognition.SrecGrammar;
-import android.speech.recognition.SlotItem;
-import android.speech.recognition.VoicetagItem;
-import android.speech.recognition.WordItem;
-
-import java.util.Vector;
-
-/**
- */
-public class SrecGrammarImpl extends EmbeddedGrammarImpl implements SrecGrammar
-{
- /**
- * Creates a new SrecGrammarImpl.
- *
- * @param nativeObject the native object
- */
- public SrecGrammarImpl(long nativeObject)
- {
- super(nativeObject);
- }
-
- public void addItem(String slotName, SlotItem item, int weight,
- String semanticValue)
- {
- synchronized (GrammarImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
-
- if (slotName == null || slotName.length()==0)
- throw new IllegalArgumentException("addItem() - Slot name is null or empty.");
- if (item == null)
- throw new IllegalArgumentException("addItem() - item can't be null.");
- if (semanticValue == null || semanticValue.length()==0)
- throw new IllegalArgumentException("addItem() - semanticValue is null or empty.");
-
- long itemNativeObject = 0;
- if (item instanceof VoicetagItem)
- itemNativeObject = ((VoicetagItemImpl)item).getNativeObject();
- else if (item instanceof WordItem)
- itemNativeObject = ((WordItemImpl)item).getNativeObject();
- else
- throw new IllegalArgumentException("SlotItem - should be a WordItem or a VoicetagItem object.");
-
- addItemProxy(nativeObject, slotName, itemNativeObject, weight, semanticValue);
- }
- }
-
- public void addItemList(String slotName, Vector<Item> items)
- {
- synchronized (GrammarImpl.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object has been disposed");
-
- if (slotName == null || slotName.length()==0)
- throw new IllegalArgumentException("addItemList - Slot name is null or empty.");
- if (items == null || items.isEmpty() == true)
- throw new IllegalArgumentException("addItemList - Items is null or empty.");
-
- int itemsCount = items.size();
-
- long[] nativeSlots = new long[itemsCount];
- int[] nativeWeights = new int[itemsCount];
- String[] nativeSemantic = new String[itemsCount];
-
- Item element = null;
- long itemNativeObject = 0;
- SlotItem item = null;
- for (int i = 0; i < itemsCount; ++i)
- {
- element = items.get(i);
-
- item = element._item;
- if (item instanceof VoicetagItem)
- itemNativeObject = ((VoicetagItemImpl)item).getNativeObject();
- else if (item instanceof WordItem)
- itemNativeObject = ((WordItemImpl)item).getNativeObject();
- else
- {
- throw new IllegalArgumentException("SlotItem ["+i+"] - should be a WordItem or a VoicetagItem object.");
- }
- nativeSlots[i] = itemNativeObject;
- nativeWeights[i] = element._weight;
- nativeSemantic[i]= element._semanticMeaning;
- itemNativeObject = 0;
- item = null;
- }
- addItemListProxy(nativeObject, slotName,nativeSlots,nativeWeights,nativeSemantic);
- }
- }
-
- private native void addItemProxy(long nativeObject, String slotName, long item, int weight,
- String semanticValue);
-
- private native void addItemListProxy(long nativeObject, String slotName, long[] items,
- int[] weights, String[] semanticValues);
-
-}
diff --git a/core/java/android/speech/recognition/impl/System.java b/core/java/android/speech/recognition/impl/System.java
deleted file mode 100644
index 23418fe..0000000
--- a/core/java/android/speech/recognition/impl/System.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*---------------------------------------------------------------------------*
- * System.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import java.lang.ref.WeakReference;
-import java.util.WeakHashMap;
-
-
-/**
- */
-public class System
-{
- private static boolean libraryLoaded;
- private static System instance;
- private static WeakHashMap<Object, WeakReference> registerMap;
- /**
- * Reference to the native object.
- */
- private long nativeObject;
- private boolean shutdownRequested;
-
- /**
- * Creates a new instance of System
- */
- private System()
- {
- shutdownRequested = false;
- registerMap =
- new WeakHashMap<Object, WeakReference>();
- initLibrary();
- nativeObject = initNativeObject();
- Runtime.getRuntime().
- addShutdownHook(new Thread()
- {
- @Override
- public void run()
- {
- try
- {
- dispose();
- }
- catch (Exception e)
- {
- e.printStackTrace();
- }
- }
- });
-
- }
-
- /**
- * Returns the singleton instance.
- *
- * @return the singleton instance
- */
- public static System getInstance()
- {
- synchronized (System.class)
- {
- if (instance == null)
- instance = new System();
- return instance;
- }
- }
-
- /**
- * Loads the native library if necessary.
- */
- private void initLibrary()
- {
- if (!libraryLoaded)
- {
- java.lang.System.loadLibrary("UAPI_jni");
- libraryLoaded = true;
- }
- }
-
- /**
- * Registers an object for shutdown when System.dispose() is invoked.
- *
- * @param r the code to run on shutdown
- * @throws IllegalStateException if the System is shutting down
- */
- public void register(Runnable r) throws IllegalStateException
- {
- synchronized (System.class)
- {
- if (shutdownRequested)
- throw new IllegalStateException("System is shutting down");
- registerMap.put(r,
- new WeakReference<Runnable>(r));
- }
- }
-
- /**
- * Registers an object for shutdown when System.dispose() is invoked.
- *
- * @param r the code to run on shutdown
- */
- public void unregister(Runnable r)
- {
- synchronized (System.class)
- {
- if (shutdownRequested)
- {
- // System.dispose() will end up removing all entries
- return;
- }
- if (r!=null) registerMap.remove(r);
- }
- }
-
- /**
- * Releases the native resources associated with the object.
- *
- * @throws java.util.concurrent.TimeoutException if the operation timeouts
- * @throws IllegalThreadStateException if a native thread error occurs
- */
- public void dispose() throws java.util.concurrent.TimeoutException,
- IllegalThreadStateException
- {
- synchronized (System.class)
- {
- if (nativeObject == 0)
- return;
- shutdownRequested = true;
- }
-
- // Traverse the list of WeakReferences
- // cast to a Runnable object if the weakrerefence is not null
- // then call the run method.
- for (Object o: registerMap.keySet())
- {
- WeakReference weakReference = registerMap.get(o);
- Runnable r = (Runnable) weakReference.get();
- if (r != null)
- r.run();
- }
- registerMap.clear();
-
- // Call the native dispose method
- disposeProxy();
- synchronized (System.class)
- {
- nativeObject = 0;
- instance = null;
- }
- }
-
- @Override
- protected void finalize() throws Throwable
- {
- dispose();
- super.finalize();
- }
-
- public static native String getAPIVersion();
-
- private static native long initNativeObject();
-
- private static native void disposeProxy();
-}
diff --git a/core/java/android/speech/recognition/impl/VoicetagItemImpl.java b/core/java/android/speech/recognition/impl/VoicetagItemImpl.java
deleted file mode 100644
index f9db399..0000000
--- a/core/java/android/speech/recognition/impl/VoicetagItemImpl.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*---------------------------------------------------------------------------*
- * VoicetagItemImpl.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import android.speech.recognition.VoicetagItem;
-import android.speech.recognition.VoicetagItemListener;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-/**
- */
-public class VoicetagItemImpl extends VoicetagItem implements Runnable
-{
- /**
- * Reference to the native object.
- */
- private long nativeObject;
- /**
- * Voicetag has a filename need to be loaded before use it.
- */
- private boolean needToBeLoaded;
-
- /**
- * Creates a new VoicetagItemImpl.
- *
- * @param nativeObject the pointer to the native object
- */
- public VoicetagItemImpl(long nativeObject, boolean fromfile)
- {
- this.nativeObject = nativeObject;
- needToBeLoaded = fromfile;
- }
-
- public void run()
- {
- dispose();
- }
-
- /**
- * Creates a VoicetagItem from a file
- *
- * @param filename filename for Voicetag
- * @param listener listens for Voicetag events
- * @return the resulting VoicetagItem
- * @throws IllegalArgumentException if filename is null or an empty string.
- * @throws FileNotFoundException if the specified filename could not be found
- * @throws IOException if the specified filename could not be opened
- */
- public static VoicetagItem create(String filename, VoicetagItemListener listener) throws IllegalArgumentException,FileNotFoundException,IOException
- {
- if ((filename == null) || (filename.length() == 0))
- throw new IllegalArgumentException("Filename may not be null or empty string.");
-
- VoicetagItemImpl voicetag = null;
- long nativeVoicetag = createVoicetagProxy(filename,listener);
- if (nativeVoicetag!=0)
- {
- voicetag = new VoicetagItemImpl(nativeVoicetag,true);
- }
- return voicetag;
- }
- /**
- * Returns the audio used to construct the VoicetagItem.
- */
- public byte[] getAudio() throws IllegalStateException
- {
- synchronized (VoicetagItem.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
-
- return getAudioProxy(nativeObject);
- }
- }
-
- /**
- * Sets the audio used to construct the Voicetag.
- */
- public void setAudio(byte[] waveform) throws IllegalArgumentException,IllegalStateException
- {
- synchronized (VoicetagItem.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
-
- if ((waveform == null) || (waveform.length == 0))
- throw new IllegalArgumentException("Waveform may not be null or empty.");
- setAudioProxy(nativeObject,waveform);
- }
- }
-
- /**
- * Save the Voicetag.
- */
- public void save(String path) throws IllegalArgumentException
- {
- synchronized (VoicetagItem.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- if ((path == null) || (path.length() == 0))
- throw new IllegalArgumentException("Path may not be null or empty string.");
- saveVoicetagProxy(nativeObject,path);
- }
- }
-
- /**
- * Load a Voicetag.
- */
- public void load() throws IllegalStateException
- {
- synchronized (VoicetagItem.class)
- {
- if (nativeObject == 0)
- throw new IllegalStateException("Object was destroyed.");
- if (!needToBeLoaded)
- throw new IllegalStateException("This Voicetag was not created from a file, does not need to be loaded.");
- loadVoicetagProxy(nativeObject);
- }
- }
-
- public long getNativeObject()
- {
- synchronized (VoicetagItem.class)
- {
- return nativeObject;
- }
- }
-
- /**
- * Releases the native resources associated with the object.
- */
- private void dispose()
- {
- synchronized (VoicetagItem.class)
- {
- if (nativeObject != 0)
- {
- deleteNativeObject(nativeObject);
- nativeObject = 0;
- }
- }
- }
-
- @Override
- protected void finalize() throws Throwable
- {
- dispose();
- super.finalize();
- }
-
-
- private static native long createVoicetagProxy(String filename, VoicetagItemListener listener);
- /**
- * (Optional operation) Returns the audio used to construct the Voicetag. The
- * audio is in PCM format and is start-pointed and end-pointed. The audio is
- * only generated if the enableGetWaveform recognition parameter is set
- * prior to recognition.
- *
- * @see RecognizerParameters.enableGetWaveform
- */
- private native byte[] getAudioProxy(long nativeObject);
-
- /**
- * (Optional operation) Sets the audio used to construct the Voicetag. The
- * audio is in PCM format and is start-pointed and end-pointed. The audio is
- * only generated if the enableGetWaveform recognition parameter is set
- * prior to recognition.
- *
- * @param waveform the endpointed waveform
- */
- private native void setAudioProxy(long nativeObject, byte[] waveform);
-
- /**
- * Save the Voicetag Item.
- */
- private native void saveVoicetagProxy(long nativeObject, String path);
-
- /**
- * Load a Voicetag Item.
- */
- private native void loadVoicetagProxy(long nativeObject);
-
- /**
- * Deletes a native object.
- *
- * @param nativeObject pointer to the native object
- */
- private native void deleteNativeObject(long nativeObject);
-}
diff --git a/core/java/android/speech/recognition/impl/WordItemImpl.java b/core/java/android/speech/recognition/impl/WordItemImpl.java
deleted file mode 100644
index f0daa34..0000000
--- a/core/java/android/speech/recognition/impl/WordItemImpl.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*---------------------------------------------------------------------------*
- * WordItemImpl.java *
- * *
- * Copyright 2007, 2008 Nuance Communciations, 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 android.speech.recognition.impl;
-
-import android.speech.recognition.WordItem;
-
-/**
- */
-public class WordItemImpl extends WordItem implements Runnable
-{
- /**
- * Empty array that gets reused whenever the code requests that the underlying
- * recognizer guess the pronunciations.
- */
- private static final String[] guessPronunciations = new String[0];
- /**
- * Reference to the native object.
- */
- private long nativeObject;
-
- /**
- * Creates a new WordItem.
- *
- * @param word the word to insert
- * @throws IllegalArgumentException if word or pronunciations are null
- */
- private WordItemImpl(String word, String[] pronunciations) throws IllegalArgumentException
- {
- initNativeObject(word, pronunciations);
- }
-
- public void run()
- {
- dispose();
- }
-
- /**
- * Creates a new WordItem.
- *
- * @param word the word to insert
- * @param pronunciations the pronunciations to associated with the item. If the list is
- * is empty (example:new String[0]) the recognizer will attempt to guess the pronunciations.
- * @return the WordItem
- * @throws IllegalArgumentException if word is null or if pronunciations is
- * null or pronunciations contains an element equal to null or empty string.
- */
- public static WordItemImpl valueOf(String word, String[] pronunciations)
- throws IllegalArgumentException
- {
- if (word == null)
- throw new IllegalArgumentException("Word may not be null");
- else if (pronunciations == null)
- throw new IllegalArgumentException("Pronunciations may not be null");
- for (int i = 0, size = pronunciations.length; i < size; ++i)
- {
- if (pronunciations[i]==null)
- {
- throw new IllegalArgumentException(
- "Pronunciations element may not be null");
- }
- else
- {
- if (pronunciations[i].trim().equals(""))
- throw new IllegalArgumentException(
- "Pronunciations may not contain empty strings");
- }
- }
- return new WordItemImpl(word, pronunciations);
- }
-
- /**
- * Creates a new WordItem.
- *
- * @param word the word to insert
- * @param pronunciation the pronunciation to associate with the item. If it
- * is null the recognizer will attempt to guess the pronunciations.
- * @return the WordItem
- * @throws IllegalArgumentException if word is null or if pronunciation is
- * an empty string
- */
- public static WordItemImpl valueOf(String word, String pronunciation)
- throws IllegalArgumentException
- {
- String[] pronunciations;
- if (word == null)
- throw new IllegalArgumentException("Word may not be null");
- else if (pronunciation == null)
- pronunciations = guessPronunciations;
- else if (pronunciation.trim().equals(""))
- throw new IllegalArgumentException(
- "Pronunciation may not be an empty string");
- else
- pronunciations = new String[]{pronunciation};
- return new WordItemImpl(word, pronunciations);
- }
-
- /**
- * Allocates a reference to the native object.
- *
- * @param word the word to insert
- */
- private native void initNativeObject(String word, String[] pronunciations);
-
- public long getNativeObject()
- {
- synchronized (WordItemImpl.class)
- {
- return nativeObject;
- }
- }
-
- /**
- * Releases the native resources associated with the object.
- */
- private void dispose()
- {
- synchronized (WordItemImpl.class)
- {
- if (nativeObject != 0)
- {
- deleteNativeObject(nativeObject);
- nativeObject = 0;
- }
- }
- }
-
- @Override
- protected void finalize() throws Throwable
- {
- dispose();
- super.finalize();
- }
-
- /**
- * Deletes a native object.
- *
- * @param nativeObject pointer to the native object
- */
- private native void deleteNativeObject(long nativeObject);
-}
diff --git a/core/java/android/speech/recognition/impl/package.html b/core/java/android/speech/recognition/impl/package.html
deleted file mode 100755
index 1c9bf9d..0000000
--- a/core/java/android/speech/recognition/impl/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<html>
-<body>
- {@hide}
-</body>
-</html>
diff --git a/core/java/android/speech/recognition/package.html b/core/java/android/speech/recognition/package.html
deleted file mode 100644
index 3c59962..0000000
--- a/core/java/android/speech/recognition/package.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<HTML>
-<BODY>
-{@hide}
-Provides classes for speech recogntion.
-</BODY>
-</HTML>
diff --git a/core/java/android/speech/srec/MicrophoneInputStream.java b/core/java/android/speech/srec/MicrophoneInputStream.java
new file mode 100644
index 0000000..160a003
--- /dev/null
+++ b/core/java/android/speech/srec/MicrophoneInputStream.java
@@ -0,0 +1,106 @@
+/*---------------------------------------------------------------------------*
+ * MicrophoneInputStream.java *
+ * *
+ * Copyright 2007 Nuance Communciations, 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 android.speech.srec;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.IllegalStateException;
+
+
+/**
+ * PCM input stream from the microphone, 16 bits per sample.
+ */
+public final class MicrophoneInputStream extends InputStream {
+ static {
+ System.loadLibrary("srec_jni");
+ }
+
+ private final static String TAG = "MicrophoneInputStream";
+ private int mAudioRecord = 0;
+ private byte[] mOneByte = new byte[1];
+
+ /**
+ * MicrophoneInputStream constructor.
+ * @param sampleRate sample rate of the microphone, typically 11025 or 8000.
+ * @param fifoDepth depth of the real time fifo, measured in sampleRate clock ticks.
+ * This determines how long an application may delay before losing data.
+ */
+ public MicrophoneInputStream(int sampleRate, int fifoDepth) throws IOException {
+ mAudioRecord = AudioRecordNew(sampleRate, fifoDepth);
+ if (mAudioRecord == 0) throw new IllegalStateException("not open");
+ AudioRecordStart(mAudioRecord);
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (mAudioRecord == 0) throw new IllegalStateException("not open");
+ int rtn = AudioRecordRead(mAudioRecord, mOneByte, 0, 1);
+ return rtn == 1 ? ((int)mOneByte[0] & 0xff) : -1;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ if (mAudioRecord == 0) throw new IllegalStateException("not open");
+ return AudioRecordRead(mAudioRecord, b, 0, b.length);
+ }
+
+ @Override
+ public int read(byte[] b, int offset, int length) throws IOException {
+ if (mAudioRecord == 0) throw new IllegalStateException("not open");
+ // TODO: should we force all reads to be a multiple of the sample size?
+ return AudioRecordRead(mAudioRecord, b, offset, length);
+ }
+
+ /**
+ * Closes this stream.
+ */
+ @Override
+ public void close() throws IOException {
+ if (mAudioRecord != 0) {
+ try {
+ AudioRecordStop(mAudioRecord);
+ } finally {
+ try {
+ AudioRecordDelete(mAudioRecord);
+ } finally {
+ mAudioRecord = 0;
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (mAudioRecord != 0) {
+ close();
+ throw new IOException("someone forgot to close MicrophoneInputStream");
+ }
+ }
+
+ //
+ // AudioRecord JNI interface
+ //
+ private static native int AudioRecordNew(int sampleRate, int fifoDepth);
+ private static native void 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/Recognizer.java b/core/java/android/speech/srec/Recognizer.java
new file mode 100644
index 0000000..749c923
--- /dev/null
+++ b/core/java/android/speech/srec/Recognizer.java
@@ -0,0 +1,679 @@
+/*
+ * ---------------------------------------------------------------------------
+ * Recognizer.java
+ *
+ * Copyright 2007 Nuance Communciations, 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 android.speech.srec;
+
+import android.util.Config;
+import android.util.Log;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * Simple, synchronous speech recognizer, using the Nuance SREC package.
+ * Usages proceeds as follows:
+ *
+ * <ul>
+ * <li>Create a <code>Recognizer</code>.
+ * <li>Create a <code>Recognizer.Grammar</code>.
+ * <li>Setup the <code>Recognizer.Grammar</code>.
+ * <li>Reset the <code>Recognizer.Grammar</code> slots, if needed.
+ * <li>Fill the <code>Recognizer.Grammar</code> slots, if needed.
+ * <li>Compile the <code>Recognizer.Grammar</code>, if needed.
+ * <li>Save the filled <code>Recognizer.Grammar</code>, if needed.
+ * <li>Start the <code>Recognizer</code>.
+ * <li>Loop over <code>advance</code> and <code>putAudio</code> until recognition complete.
+ * <li>Fetch and process results, or notify of failure.
+ * <li>Stop the <code>Recognizer</code>.
+ * <li>Destroy the <code>Recognizer</code>.
+ * </ul>
+ *
+ * <p>Below is example code</p>
+ *
+ * <pre class="prettyprint">
+ *
+ * // create and start audio input
+ * InputStream audio = new MicrophoneInputStream(11025, 11025*5);
+ * // create a Recognizer
+ * String cdir = Recognizer.getConfigDir(null);
+ * Recognizer recognizer = new Recognizer(cdir + "/baseline11k.par");
+ * // create and load a Grammar
+ * Recognizer.Grammar grammar = recognizer.new Grammar(cdir + "/grammars/VoiceDialer.g2g");
+ * // setup the Grammar to work with the Recognizer
+ * grammar.setupRecognizer();
+ * // fill the Grammar slots with names and save, if required
+ * grammar.resetAllSlots();
+ * for (String name : names) grammar.addWordToSlot("@Names", name, null, 1, "V=1");
+ * grammar.compile();
+ * grammar.save(".../foo.g2g");
+ * // start the Recognizer
+ * recognizer.start();
+ * // loop over Recognizer events
+ * while (true) {
+ * switch (recognizer.advance()) {
+ * case Recognizer.EVENT_INCOMPLETE:
+ * case Recognizer.EVENT_STARTED:
+ * case Recognizer.EVENT_START_OF_VOICING:
+ * case Recognizer.EVENT_END_OF_VOICING:
+ * // let the Recognizer continue to run
+ * continue;
+ * case Recognizer.EVENT_RECOGNITION_RESULT:
+ * // success, so fetch results here!
+ * for (int i = 0; i < recognizer.getResultCount(); i++) {
+ * String result = recognizer.getResult(i, Recognizer.KEY_LITERAL);
+ * }
+ * break;
+ * case Recognizer.EVENT_NEED_MORE_AUDIO:
+ * // put more audio in the Recognizer
+ * recognizer.putAudio(audio);
+ * continue;
+ * default:
+ * notifyFailure();
+ * break;
+ * }
+ * break;
+ * }
+ * // stop the Recognizer
+ * recognizer.stop();
+ * // destroy the Recognizer
+ * recognizer.destroy();
+ * // stop the audio device
+ * audio.close();
+ *
+ * </pre>
+ */
+public final class Recognizer {
+ static {
+ System.loadLibrary("srec_jni");
+ }
+
+ private static String TAG = "Recognizer";
+
+ /**
+ * Result key corresponding to confidence score.
+ */
+ public static final String KEY_CONFIDENCE = "conf";
+
+ /**
+ * Result key corresponding to literal text.
+ */
+ public static final String KEY_LITERAL = "literal";
+
+ /**
+ * Result key corresponding to semantic meaning text.
+ */
+ public static final String KEY_MEANING = "meaning";
+
+ // handle to SR_Vocabulary object
+ private int mVocabulary = 0;
+
+ // handle to SR_Recognizer object
+ private int mRecognizer = 0;
+
+ // Grammar currently associated with Recognizer via SR_GrammarSetupRecognizer
+ private Grammar mActiveGrammar = null;
+
+ /**
+ * Get the pathname of the SREC configuration directory corresponding to the
+ * language indicated by the Locale.
+ * This directory contains dictionaries, speech models,
+ * configuration files, and other data needed by the Recognizer.
+ * @param locale <code>Locale</code> corresponding to the desired language,
+ * or null for default, currently <code>Locale.US</code>.
+ * @return Pathname of the configuration directory.
+ */
+ public static String getConfigDir(Locale locale) {
+ if (locale == null) locale = Locale.US;
+ String dir = "/system/usr/srec/config/" +
+ locale.toString().replace('_', '.').toLowerCase();
+ if ((new File(dir)).isDirectory()) return dir;
+ return null;
+ }
+
+ /**
+ * Create an instance of a SREC speech recognizer.
+ *
+ * @param configFile pathname of the baseline*.par configuration file,
+ * which in turn contains references to dictionaries, speech models,
+ * and other data needed to configure and operate the recognizer.
+ * A separate config file is needed for each audio sample rate.
+ * Two files, baseline11k.par and baseline8k.par, which correspond to
+ * 11025 and 8000 hz, are present in the directory indicated by
+ * {@link #getConfigDir}.
+ * @throws IOException
+ */
+ public Recognizer(String configFile) throws IOException {
+ PMemInit();
+ SR_SessionCreate(configFile);
+ mRecognizer = SR_RecognizerCreate();
+ SR_RecognizerSetup(mRecognizer);
+ mVocabulary = SR_VocabularyLoad();
+ }
+
+ /**
+ * Represents a grammar loaded into the Recognizer.
+ */
+ public class Grammar {
+ private int mGrammar = 0;
+
+ /**
+ * Create a <code>Grammar</code> instance.
+ * @param g2gFileName pathname of g2g file.
+ */
+ public Grammar(String g2gFileName) throws IOException {
+ mGrammar = SR_GrammarLoad(g2gFileName);
+ SR_GrammarSetupVocabulary(mGrammar, mVocabulary);
+ }
+
+ /**
+ * Reset all slots.
+ */
+ public void resetAllSlots() {
+ SR_GrammarResetAllSlots(mGrammar);
+ }
+
+ /**
+ * Add a word to a slot.
+ *
+ * @param slot slot name.
+ * @param word word to insert.
+ * @param pron pronunciation, or null to derive from word.
+ * @param weight weight to give the word. One is normal, 50 is low.
+ * @param tag semantic meaning tag string.
+ */
+ public void addWordToSlot(String slot, String word, String pron, int weight, String tag) {
+ SR_GrammarAddWordToSlot(mGrammar, slot, word, pron, weight, tag);
+ }
+
+ /**
+ * Compile all slots.
+ */
+ public void compile() {
+ SR_GrammarCompile(mGrammar);
+ }
+
+ /**
+ * Setup <code>Grammar</code> with <code>Recognizer</code>.
+ */
+ public void setupRecognizer() {
+ SR_GrammarSetupRecognizer(mGrammar, mRecognizer);
+ mActiveGrammar = this;
+ }
+
+ /**
+ * Save <code>Grammar</code> to g2g file.
+ *
+ * @param g2gFileName
+ * @throws IOException
+ */
+ public void save(String g2gFileName) throws IOException {
+ SR_GrammarSave(mGrammar, g2gFileName);
+ }
+
+ /**
+ * Release resources associated with this <code>Grammar</code>.
+ */
+ public void destroy() {
+ // TODO: need to do cleanup and disassociation with Recognizer
+ if (mGrammar != 0) {
+ SR_GrammarDestroy(mGrammar);
+ mGrammar = 0;
+ }
+ }
+
+ /**
+ * Clean up resources.
+ */
+ protected void finalize() {
+ if (mGrammar != 0) {
+ destroy();
+ throw new IllegalStateException("someone forgot to destroy Grammar");
+ }
+ }
+ }
+
+ /**
+ * Start recognition
+ */
+ public void start() {
+ // TODO: shouldn't be here?
+ SR_RecognizerActivateRule(mRecognizer, mActiveGrammar.mGrammar, "trash", 1);
+ SR_RecognizerStart(mRecognizer);
+ }
+
+ /**
+ * Process some audio and return the current status.
+ * @return recognition event, one of:
+ * <ul>
+ * <li><code>EVENT_INVALID</code>
+ * <li><code>EVENT_NO_MATCH</code>
+ * <li><code>EVENT_INCOMPLETE</code>
+ * <li><code>EVENT_STARTED</code>
+ * <li><code>EVENT_STOPPED</code>
+ * <li><code>EVENT_START_OF_VOICING</code>
+ * <li><code>EVENT_END_OF_VOICING</code>
+ * <li><code>EVENT_SPOKE_TOO_SOON</code>
+ * <li><code>EVENT_RECOGNITION_RESULT</code>
+ * <li><code>EVENT_START_OF_UTTERANCE_TIMEOUT</code>
+ * <li><code>EVENT_RECOGNITION_TIMEOUT</code>
+ * <li><code>EVENT_NEED_MORE_AUDIO</code>
+ * <li><code>EVENT_MAX_SPEECH</code>
+ * </ul>
+ */
+ public int advance() {
+ return SR_RecognizerAdvance(mRecognizer);
+ }
+
+ /**
+ * Put audio samples into the <code>Recognizer</code>.
+ * @param buf holds the audio samples.
+ * @param offset offset of the first sample.
+ * @param length number of bytes containing samples.
+ * @param isLast indicates no more audio data, normally false.
+ * @return number of bytes accepted.
+ */
+ public int putAudio(byte[] buf, int offset, int length, boolean isLast) {
+ return SR_RecognizerPutAudio(mRecognizer, buf, offset, length, isLast);
+ }
+
+ /**
+ * Read audio samples from an <code>InputStream</code> and put them in the
+ * <code>Recognizer</code>.
+ * @param audio <code>InputStream</code> containing PCM audio samples.
+ */
+ public void putAudio(InputStream audio) throws IOException {
+ // make sure the audio buffer is allocated
+ if (mPutAudioBuffer == null) mPutAudioBuffer = new byte[512];
+ // read some data
+ int nbytes = audio.read(mPutAudioBuffer);
+ // eof, so signal Recognizer
+ if (nbytes == -1) {
+ SR_RecognizerPutAudio(mRecognizer, mPutAudioBuffer, 0, 0, true);
+ }
+ // put it into the Recognizer
+ else if (nbytes != SR_RecognizerPutAudio(mRecognizer, mPutAudioBuffer, 0, nbytes, false)) {
+ throw new IOException("SR_RecognizerPutAudio failed nbytes=" + nbytes);
+ }
+ }
+
+ // audio buffer for putAudio(InputStream)
+ private byte[] mPutAudioBuffer = null;
+
+ /**
+ * Get the number of recognition results. Must be called after
+ * <code>EVENT_RECOGNITION_RESULT</code> is returned by
+ * <code>advance</code>, but before <code>stop</code>.
+ *
+ * @return number of results in nbest list.
+ */
+ public int getResultCount() {
+ return SR_RecognizerResultGetSize(mRecognizer);
+ }
+
+ /**
+ * Get a set of keys for the result. Must be called after
+ * <code>EVENT_RECOGNITION_RESULT</code> is returned by
+ * <code>advance</code>, but before <code>stop</code>.
+ *
+ * @param index index of result.
+ * @return array of keys.
+ */
+ public String[] getResultKeys(int index) {
+ return SR_RecognizerResultGetKeyList(mRecognizer, index);
+ }
+
+ /**
+ * Get a result value. Must be called after
+ * <code>EVENT_RECOGNITION_RESULT</code> is returned by
+ * <code>advance</code>, but before <code>stop</code>.
+ *
+ * @param index index of the result.
+ * @param key key of the result. This is typically one of
+ * <code>KEY_CONFIDENCE</code>, <code>KEY_LITERAL</code>, or
+ * <code>KEY_MEANING</code>, but the user can also define their own keys
+ * in a grxml file, or in the <code>tag</code> slot of
+ * <code>Grammar.addWordToSlot</code>.
+ * @return the result.
+ */
+ public String getResult(int index, String key) {
+ return SR_RecognizerResultGetValue(mRecognizer, index, key);
+ }
+
+ /**
+ * Stop the <code>Recognizer</code>.
+ */
+ public void stop() {
+ SR_RecognizerStop(mRecognizer);
+ SR_RecognizerDeactivateRule(mRecognizer, mActiveGrammar.mGrammar, "trash");
+ }
+
+ /**
+ * Clean up resources.
+ */
+ public void destroy() {
+ try {
+ if (mVocabulary != 0) SR_VocabularyDestroy(mVocabulary);
+ } finally {
+ mVocabulary = 0;
+ try {
+ if (mRecognizer != 0) SR_RecognizerUnsetup(mRecognizer);
+ } finally {
+ try {
+ if (mRecognizer != 0) SR_RecognizerDestroy(mRecognizer);
+ } finally {
+ mRecognizer = 0;
+ try {
+ SR_SessionDestroy();
+ } finally {
+ PMemShutdown();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Clean up resources.
+ */
+ protected void finalize() throws Throwable {
+ if (mVocabulary != 0 || mRecognizer != 0) {
+ destroy();
+ throw new IllegalStateException("someone forgot to destroy Recognizer");
+ }
+ }
+
+ /* an example session captured, for reference
+ void doall() {
+ if (PMemInit ( )
+ || lhs_audioinOpen ( WAVE_MAPPER, SREC_TEST_DEFAULT_AUDIO_FREQUENCY, &audio_in_handle )
+ || srec_test_init_application_data ( &applicationData, argc, argv )
+ || SR_SessionCreate ( "/system/usr/srec/config/en.us/baseline11k.par" )
+ || SR_RecognizerCreate ( &applicationData.recognizer )
+ || SR_RecognizerSetup ( applicationData.recognizer)
+ || ESR_SessionGetLCHAR ( L("cmdline.vocabulary"), filename, &flen )
+ || SR_VocabularyLoad ( filename, &applicationData.vocabulary )
+ || SR_VocabularyGetLanguage ( applicationData.vocabulary, &applicationData.locale )
+ || (applicationData.nametag = NULL)
+ || SR_NametagsCreate ( &applicationData.nametags )
+ || (LSTRCPY ( applicationData.grammars [0].grammar_path, "/system/usr/srec/config/en.us/grammars/VoiceDialer.g2g" ), 0)
+ || (LSTRCPY ( applicationData.grammars [0].grammarID, "BothTags" ), 0)
+ || (LSTRCPY ( applicationData.grammars [0].ruleName, "trash" ), 0)
+ || (applicationData.grammars [0].is_ve_grammar = ESR_FALSE, 0)
+ || SR_GrammarLoad (applicationData.grammars [0].grammar_path, &applicationData.grammars [applicationData.grammarCount].grammar )
+ || SR_GrammarSetupVocabulary ( applicationData.grammars [0].grammar, applicationData.vocabulary )
+ || SR_GrammarSetupRecognizer( applicationData.grammars [0].grammar, applicationData.recognizer )
+ || SR_GrammarSetDispatchFunction ( applicationData.grammars [0].grammar, L("myDSMCallback"), NULL, myDSMCallback )
+ || (applicationData.grammarCount++, 0)
+ || SR_RecognizerActivateRule ( applicationData.recognizer, applicationData.grammars [0].grammar,
+ applicationData.grammars [0].ruleName, 1 )
+ || (applicationData.active_grammar_num = 0, 0)
+ || lhs_audioinStart ( audio_in_handle )
+ || SR_RecognizerStart ( applicationData.recognizer )
+ || strl ( applicationData.grammars [0].grammar, &applicationData, audio_in_handle, &recognition_count )
+ || SR_RecognizerStop ( applicationData.recognizer )
+ || lhs_audioinStop ( audio_in_handle )
+ || SR_RecognizerDeactivateRule ( applicationData.recognizer, applicationData.grammars [0].grammar, applicationData.grammars [0].ruleName )
+ || (applicationData.active_grammar_num = -1, 0)
+ || SR_GrammarDestroy ( applicationData.grammars [0].grammar )
+ || (applicationData.grammarCount--, 0)
+ || SR_NametagsDestroy ( applicationData.nametags )
+ || (applicationData.nametags = NULL, 0)
+ || SR_VocabularyDestroy ( applicationData.vocabulary )
+ || (applicationData.vocabulary = NULL)
+ || SR_RecognizerUnsetup ( applicationData.recognizer) // releases acoustic models
+ || SR_RecognizerDestroy ( applicationData.recognizer )
+ || (applicationData.recognizer = NULL)
+ || SR_SessionDestroy ( )
+ || srec_test_shutdown_application_data ( &applicationData )
+ || lhs_audioinClose ( &audio_in_handle )
+ || PMemShutdown ( )
+ }
+ */
+
+
+ //
+ // PMem native methods
+ //
+ private static native void PMemInit();
+ private static native void PMemShutdown();
+
+
+ //
+ // SR_Session native methods
+ //
+ private static native void SR_SessionCreate(String filename);
+ private static native void SR_SessionDestroy();
+
+
+ //
+ // SR_Recognizer native methods
+ //
+
+ /**
+ * Reserved value.
+ */
+ public final static int EVENT_INVALID = 0;
+
+ /**
+ * <code>Recognizer</code> could not find a match for the utterance.
+ */
+ public final static int EVENT_NO_MATCH = 1;
+
+ /**
+ * <code>Recognizer</code> processed one frame of audio.
+ */
+ public final static int EVENT_INCOMPLETE = 2;
+
+ /**
+ * <code>Recognizer</code> has just been started.
+ */
+ public final static int EVENT_STARTED = 3;
+
+ /**
+ * <code>Recognizer</code> is stopped.
+ */
+ public final static int EVENT_STOPPED = 4;
+
+ /**
+ * Beginning of speech detected.
+ */
+ public final static int EVENT_START_OF_VOICING = 5;
+
+ /**
+ * End of speech detected.
+ */
+ public final static int EVENT_END_OF_VOICING = 6;
+
+ /**
+ * Beginning of utterance occured too soon.
+ */
+ public final static int EVENT_SPOKE_TOO_SOON = 7;
+
+ /**
+ * Recognition match detected.
+ */
+ public final static int EVENT_RECOGNITION_RESULT = 8;
+
+ /**
+ * Timeout occured before beginning of utterance.
+ */
+ public final static int EVENT_START_OF_UTTERANCE_TIMEOUT = 9;
+
+ /**
+ * Timeout occured before speech recognition could complete.
+ */
+ public final static int EVENT_RECOGNITION_TIMEOUT = 10;
+
+ /**
+ * Not enough samples to process one frame.
+ */
+ public final static int EVENT_NEED_MORE_AUDIO = 11;
+
+ /**
+ * More audio encountered than is allowed by 'swirec_max_speech_duration'.
+ */
+ public final static int EVENT_MAX_SPEECH = 12;
+
+ /**
+ * Produce a displayable string from an <code>advance</code> event.
+ * @param event
+ * @return String representing the event.
+ */
+ public static String eventToString(int event) {
+ switch (event) {
+ case EVENT_INVALID:
+ return "EVENT_INVALID";
+ case EVENT_NO_MATCH:
+ return "EVENT_NO_MATCH";
+ case EVENT_INCOMPLETE:
+ return "EVENT_INCOMPLETE";
+ case EVENT_STARTED:
+ return "EVENT_STARTED";
+ case EVENT_STOPPED:
+ return "EVENT_STOPPED";
+ case EVENT_START_OF_VOICING:
+ return "EVENT_START_OF_VOICING";
+ case EVENT_END_OF_VOICING:
+ return "EVENT_END_OF_VOICING";
+ case EVENT_SPOKE_TOO_SOON:
+ return "EVENT_SPOKE_TOO_SOON";
+ case EVENT_RECOGNITION_RESULT:
+ return "EVENT_RECOGNITION_RESULT";
+ case EVENT_START_OF_UTTERANCE_TIMEOUT:
+ return "EVENT_START_OF_UTTERANCE_TIMEOUT";
+ case EVENT_RECOGNITION_TIMEOUT:
+ return "EVENT_RECOGNITION_TIMEOUT";
+ case EVENT_NEED_MORE_AUDIO:
+ return "EVENT_NEED_MORE_AUDIO";
+ case EVENT_MAX_SPEECH:
+ return "EVENT_MAX_SPEECH";
+ }
+ return "EVENT_" + event;
+ }
+
+ private static native void SR_RecognizerStart(int recognizer);
+ private static native void SR_RecognizerStop(int recognizer);
+ private static native int SR_RecognizerCreate();
+ private static native void SR_RecognizerDestroy(int recognizer);
+ private static native void SR_RecognizerSetup(int recognizer);
+ private static native void SR_RecognizerUnsetup(int recognizer);
+ private static native boolean SR_RecognizerIsSetup(int recognizer);
+ private static native String SR_RecognizerGetParameter(int recognizer, String key);
+ private static native int SR_RecognizerGetSize_tParameter(int recognizer, String key);
+ private static native boolean SR_RecognizerGetBoolParameter(int recognizer, String key);
+ private static native void SR_RecognizerSetParameter(int recognizer, String key, String value);
+ private static native void SR_RecognizerSetSize_tParameter(int recognizer,
+ String key, int value);
+ private static native void SR_RecognizerSetBoolParameter(int recognizer, String key,
+ boolean value);
+ private static native void SR_RecognizerSetupRule(int recognizer, int grammar,
+ String ruleName);
+ private static native boolean SR_RecognizerHasSetupRules(int recognizer);
+ private static native void SR_RecognizerActivateRule(int recognizer, int grammar,
+ String ruleName, int weight);
+ private static native void SR_RecognizerDeactivateRule(int recognizer, int grammar,
+ String ruleName);
+ private static native void SR_RecognizerDeactivateAllRules(int recognizer);
+ private static native boolean SR_RecognizerIsActiveRule(int recognizer, int grammar,
+ String ruleName);
+ private static native boolean SR_RecognizerCheckGrammarConsistency(int recognizer,
+ int grammar);
+ private static native int SR_RecognizerPutAudio(int recognizer, byte[] buffer, int offset,
+ int length, boolean isLast);
+ private static native int SR_RecognizerAdvance(int recognizer);
+ // private static native void SR_RecognizerLoadUtterance(int recognizer,
+ // const LCHAR* filename);
+ // private static native void SR_RecognizerLoadWaveFile(int recognizer,
+ // const LCHAR* filename);
+ // private static native void SR_RecognizerSetLockFunction(int recognizer,
+ // SR_RecognizerLockFunction function, void* data);
+ private static native boolean SR_RecognizerIsSignalClipping(int recognizer);
+ private static native boolean SR_RecognizerIsSignalDCOffset(int recognizer);
+ private static native boolean SR_RecognizerIsSignalNoisy(int recognizer);
+ private static native boolean SR_RecognizerIsSignalTooQuiet(int recognizer);
+ private static native boolean SR_RecognizerIsSignalTooFewSamples(int recognizer);
+ private static native boolean SR_RecognizerIsSignalTooManySamples(int recognizer);
+ // private static native void SR_Recognizer_Change_Sample_Rate (size_t new_sample_rate);
+
+
+ //
+ // SR_Grammar native methods
+ //
+ private static native void SR_GrammarCompile(int grammar);
+ private static native void SR_GrammarAddWordToSlot(int grammar, String slot,
+ String word, String pronunciation, int weight, String tag);
+ private static native void SR_GrammarResetAllSlots(int grammar);
+ // private static native void SR_GrammarAddNametagToSlot(int grammar, String slot,
+ // const struct SR_Nametag_t* nametag, int weight, String tag);
+ private static native void SR_GrammarSetupVocabulary(int grammar, int vocabulary);
+ // private static native void SR_GrammarSetupModels(int grammar, SR_AcousticModels* models);
+ private static native void SR_GrammarSetupRecognizer(int grammar, int recognizer);
+ private static native void SR_GrammarUnsetupRecognizer(int grammar);
+ // private static native void SR_GrammarGetModels(int grammar,SR_AcousticModels** models);
+ private static native int SR_GrammarCreate();
+ private static native void SR_GrammarDestroy(int grammar);
+ private static native int SR_GrammarLoad(String filename);
+ private static native void SR_GrammarSave(int grammar, String filename);
+ // private static native void SR_GrammarSetDispatchFunction(int grammar,
+ // const LCHAR* name, void* userData, SR_GrammarDispatchFunction function);
+ // private static native void SR_GrammarSetParameter(int grammar, const
+ // LCHAR* key, void* value);
+ // private static native void SR_GrammarSetSize_tParameter(int grammar,
+ // const LCHAR* key, size_t value);
+ // private static native void SR_GrammarGetParameter(int grammar, const
+ // LCHAR* key, void** value);
+ // private static native void SR_GrammarGetSize_tParameter(int grammar,
+ // const LCHAR* key, size_t* value);
+ // private static native void SR_GrammarCheckParse(int grammar, const LCHAR*
+ // transcription, SR_SemanticResult** result, size_t* resultCount);
+ private static native void SR_GrammarAllowOnly(int grammar, String transcription);
+ private static native void SR_GrammarAllowAll(int grammar);
+
+
+ //
+ // SR_Vocabulary native methods
+ //
+ // private static native int SR_VocabularyCreate();
+ private static native int SR_VocabularyLoad();
+ // private static native void SR_VocabularySave(SR_Vocabulary* self,
+ // const LCHAR* filename);
+ // private static native void SR_VocabularyAddWord(SR_Vocabulary* self,
+ // const LCHAR* word);
+ // private static native void SR_VocabularyGetLanguage(SR_Vocabulary* self,
+ // ESR_Locale* locale);
+ private static native void SR_VocabularyDestroy(int vocabulary);
+ private static native String SR_VocabularyGetPronunciation(int vocabulary, String word);
+
+
+ //
+ // SR_RecognizerResult native methods
+ //
+ private static native byte[] SR_RecognizerResultGetWaveform(int recognizer);
+ private static native int SR_RecognizerResultGetSize(int recognizer);
+ private static native int SR_RecognizerResultGetKeyCount(int recognizer, int nbest);
+ private static native String[] SR_RecognizerResultGetKeyList(int recognizer, int nbest);
+ private static native String SR_RecognizerResultGetValue(int recognizer,
+ int nbest, String key);
+ // private static native void SR_RecognizerResultGetLocale(int recognizer, ESR_Locale* locale);
+}
diff --git a/core/java/android/speech/srec/Srec.java b/core/java/android/speech/srec/Srec.java
deleted file mode 100644
index a629214..0000000
--- a/core/java/android/speech/srec/Srec.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*---------------------------------------------------------------------------*
- * EmbeddedRecognizerImpl.java *
- * *
- * Copyright 2007 Nuance Communciations, 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 android.speech.srec;
-
-import java.io.IOException;
-
-/**
- * Simple, synchronous speech recognizer, using the SREC package.
- *
- * @hide
- */
-public class Srec
-{
- private int mNative;
-
-
- /**
- * Create an instance of a SREC speech recognizer.
- * @param configFile pathname of the baseline*.par configuration file.
- * @throws IOException
- */
- Srec(String configFile) throws IOException {
-
- }
-
- /**
- * Creates a Srec recognizer.
- * @param g2gFileName pathname of a g2g grammar file.
- * @return
- * @throws IOException
- */
- public Grammar loadGrammar(String g2gFileName) throws IOException {
- return null;
- }
-
- /**
- * Represents a grammar loaded into the recognizer.
- */
- public class Grammar {
- private int mId = -1;
-
- /**
- * Add a word to a slot
- * @param slot slot name
- * @param word word
- * @param pron pronunciation, or null to derive from word
- * @param weight weight to give the word
- * @param meaning meaning string
- */
- public void addToSlot(String slot, String word, String pron, int weight, String meaning) {
-
- }
-
- /**
- * Compile all slots.
- */
- public void compileSlots() {
-
- }
-
- /**
- * Reset all slots.
- */
- public void resetAllSlots() {
-
- }
-
- /**
- * Save grammar to g2g file.
- * @param g2gFileName
- * @throws IOException
- */
- public void save(String g2gFileName) throws IOException {
-
- }
-
- /**
- * Release resources associated with this grammar.
- */
- public void unload() {
-
- }
- }
-
- /**
- * Start recognition
- */
- public void start() {
-
- }
-
- /**
- * Process some audio and return the next state.
- * @return true if complete
- */
- public boolean process() {
- return false;
- }
-
- /**
- * Get the number of recognition results.
- * @return
- */
- public int getResultCount() {
- return 0;
- }
-
- /**
- * Get a set of keys for the result.
- * @param index index of result.
- * @return array of keys.
- */
- public String[] getResultKeys(int index) {
- return null;
- }
-
- /**
- * Get a result value
- * @param index index of the result.
- * @param key key of the result.
- * @return the result.
- */
- public String getResult(int index, String key) {
- return null;
- }
-
- /**
- * Reset the recognizer to the idle state.
- */
- public void reset() {
-
- }
-
- /**
- * Clean up resources.
- */
- public void dispose() {
-
- }
-
- protected void finalize() {
-
- }
-
-}
diff --git a/core/java/android/speech/srec/UlawEncoderInputStream.java b/core/java/android/speech/srec/UlawEncoderInputStream.java
new file mode 100644
index 0000000..132fe027
--- /dev/null
+++ b/core/java/android/speech/srec/UlawEncoderInputStream.java
@@ -0,0 +1,186 @@
+/*
+ * ---------------------------------------------------------------------------
+ * UlawEncoderInputStream.java
+ *
+ * Copyright 2008 Nuance Communciations, 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 android.speech.srec;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * InputStream which transforms 16 bit pcm data to ulaw data.
+ *
+ * @hide pending API council approval
+ */
+public final class UlawEncoderInputStream extends InputStream {
+ private final static String TAG = "UlawEncoderInputStream";
+
+ private final static int MAX_ULAW = 8192;
+ private final static int SCALE_BITS = 16;
+
+ private InputStream mIn;
+
+ private int mMax = 0;
+
+ private final byte[] mBuf = new byte[1024];
+ private int mBufCount = 0; // should be 0 or 1
+
+ private final byte[] mOneByte = new byte[1];
+
+
+ public static void encode(byte[] pcmBuf, int pcmOffset,
+ byte[] ulawBuf, int ulawOffset, int length, int max) {
+
+ // from 'ulaw' in wikipedia
+ // +8191 to +8159 0x80
+ // +8158 to +4063 in 16 intervals of 256 0x80 + interval number
+ // +4062 to +2015 in 16 intervals of 128 0x90 + interval number
+ // +2014 to +991 in 16 intervals of 64 0xA0 + interval number
+ // +990 to +479 in 16 intervals of 32 0xB0 + interval number
+ // +478 to +223 in 16 intervals of 16 0xC0 + interval number
+ // +222 to +95 in 16 intervals of 8 0xD0 + interval number
+ // +94 to +31 in 16 intervals of 4 0xE0 + interval number
+ // +30 to +1 in 15 intervals of 2 0xF0 + interval number
+ // 0 0xFF
+
+ // -1 0x7F
+ // -31 to -2 in 15 intervals of 2 0x70 + interval number
+ // -95 to -32 in 16 intervals of 4 0x60 + interval number
+ // -223 to -96 in 16 intervals of 8 0x50 + interval number
+ // -479 to -224 in 16 intervals of 16 0x40 + interval number
+ // -991 to -480 in 16 intervals of 32 0x30 + interval number
+ // -2015 to -992 in 16 intervals of 64 0x20 + interval number
+ // -4063 to -2016 in 16 intervals of 128 0x10 + interval number
+ // -8159 to -4064 in 16 intervals of 256 0x00 + interval number
+ // -8192 to -8160 0x00
+
+ // set scale factors
+ if (max <= 0) max = MAX_ULAW;
+
+ int coef = MAX_ULAW * (1 << SCALE_BITS) / max;
+
+ for (int i = 0; i < length; i++) {
+ int pcm = (0xff & pcmBuf[pcmOffset++]) + (pcmBuf[pcmOffset++] << 8);
+ pcm = (pcm * coef) >> SCALE_BITS;
+
+ int ulaw;
+ if (pcm >= 0) {
+ ulaw = pcm <= 0 ? 0xff :
+ pcm <= 30 ? 0xf0 + (( 30 - pcm) >> 1) :
+ pcm <= 94 ? 0xe0 + (( 94 - pcm) >> 2) :
+ pcm <= 222 ? 0xd0 + (( 222 - pcm) >> 3) :
+ pcm <= 478 ? 0xc0 + (( 478 - pcm) >> 4) :
+ pcm <= 990 ? 0xb0 + (( 990 - pcm) >> 5) :
+ pcm <= 2014 ? 0xa0 + ((2014 - pcm) >> 6) :
+ pcm <= 4062 ? 0x90 + ((4062 - pcm) >> 7) :
+ pcm <= 8158 ? 0x80 + ((8158 - pcm) >> 8) :
+ 0x80;
+ } else {
+ ulaw = -1 <= pcm ? 0x7f :
+ -31 <= pcm ? 0x70 + ((pcm - -31) >> 1) :
+ -95 <= pcm ? 0x60 + ((pcm - -95) >> 2) :
+ -223 <= pcm ? 0x50 + ((pcm - -223) >> 3) :
+ -479 <= pcm ? 0x40 + ((pcm - -479) >> 4) :
+ -991 <= pcm ? 0x30 + ((pcm - -991) >> 5) :
+ -2015 <= pcm ? 0x20 + ((pcm - -2015) >> 6) :
+ -4063 <= pcm ? 0x10 + ((pcm - -4063) >> 7) :
+ -8159 <= pcm ? 0x00 + ((pcm - -8159) >> 8) :
+ 0x00;
+ }
+ ulawBuf[ulawOffset++] = (byte)ulaw;
+ }
+ }
+
+ /**
+ * Compute the maximum of the absolute value of the pcm samples.
+ * The return value can be used to set ulaw encoder scaling.
+ * @param pcmBuf array containing 16 bit pcm data.
+ * @param offset offset of start of 16 bit pcm data.
+ * @param length number of pcm samples (not number of input bytes)
+ * @return maximum abs of pcm data values
+ */
+ public static int maxAbsPcm(byte[] pcmBuf, int offset, int length) {
+ int max = 0;
+ for (int i = 0; i < length; i++) {
+ int pcm = (0xff & pcmBuf[offset++]) + (pcmBuf[offset++] << 8);
+ if (pcm < 0) pcm = -pcm;
+ if (pcm > max) max = pcm;
+ }
+ return max;
+ }
+
+ /**
+ * Create an InputStream which takes 16 bit pcm data and produces ulaw data.
+ * @param in InputStream containing 16 bit pcm data.
+ * @param max pcm value corresponding to maximum ulaw value.
+ */
+ public UlawEncoderInputStream(InputStream in, int max) {
+ mIn = in;
+ mMax = max;
+ }
+
+ @Override
+ public int read(byte[] buf, int offset, int length) throws IOException {
+ if (mIn == null) throw new IllegalStateException("not open");
+
+ // return at least one byte, but try to fill 'length'
+ while (mBufCount < 2) {
+ int n = mIn.read(mBuf, mBufCount, Math.min(length * 2, mBuf.length - mBufCount));
+ if (n == -1) return -1;
+ mBufCount += n;
+ }
+
+ // compand data
+ int n = Math.min(mBufCount / 2, length);
+ encode(mBuf, 0, buf, offset, n, mMax);
+
+ // move data to bottom of mBuf
+ mBufCount -= n * 2;
+ for (int i = 0; i < mBufCount; i++) mBuf[i] = mBuf[i + n * 2];
+
+ return n;
+ }
+
+ @Override
+ public int read(byte[] buf) throws IOException {
+ return read(buf, 0, buf.length);
+ }
+
+ @Override
+ public int read() throws IOException {
+ int n = read(mOneByte, 0, 1);
+ if (n == -1) return -1;
+ return 0xff & (int)mOneByte[0];
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (mIn != null) {
+ InputStream in = mIn;
+ mIn = null;
+ in.close();
+ }
+ }
+
+ @Override
+ public int available() throws IOException {
+ return (mIn.available() + mBufCount) / 2;
+ }
+}
diff --git a/core/java/android/speech/srec/package.html b/core/java/android/speech/srec/package.html
new file mode 100644
index 0000000..723b30b
--- /dev/null
+++ b/core/java/android/speech/srec/package.html
@@ -0,0 +1,5 @@
+<HTML>
+<BODY>
+Simple, synchronous SREC speech recognition API.
+</BODY>
+</HTML>
diff --git a/core/java/android/test/InstrumentationTestCase.java b/core/java/android/test/InstrumentationTestCase.java
index 08a8ad1..82f2ef9 100644
--- a/core/java/android/test/InstrumentationTestCase.java
+++ b/core/java/android/test/InstrumentationTestCase.java
@@ -31,8 +31,7 @@ import java.lang.reflect.Modifier;
import java.lang.reflect.InvocationTargetException;
/**
- * A test case that has access to {@link Instrumentation}. See
- * <code>InstrumentationTestRunner</code>.
+ * A test case that has access to {@link Instrumentation}.
*/
public class InstrumentationTestCase extends TestCase {
@@ -57,7 +56,13 @@ public class InstrumentationTestCase extends TestCase {
}
/**
- * Utility method for launching an activity.
+ * Utility method for launching an activity.
+ *
+ * <p>The {@link Intent} used to launch the Activity is:
+ * action = {@link Intent#ACTION_MAIN}
+ * extras = null, unless a custom bundle is provided here
+ * All other fields are null or empty.
+ *
* @param pkg The package hosting the activity to be launched.
* @param activityCls The activity class to launch.
* @param extras Optional extra stuff to pass to the activity.
@@ -69,20 +74,61 @@ public class InstrumentationTestCase extends TestCase {
Class<T> activityCls,
Bundle extras) {
Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setClassName(pkg, activityCls.getName());
if (extras != null) {
intent.putExtras(extras);
}
+ return launchActivityWithIntent(pkg, activityCls, intent);
+ }
+
+ /**
+ * Utility method for launching an activity with a specific Intent.
+ * @param pkg The package hosting the activity to be launched.
+ * @param activityCls The activity class to launch.
+ * @param intent The intent to launch with
+ * @return The activity, or null if non launched.
+ */
+ @SuppressWarnings("unchecked")
+ public final <T extends Activity> T launchActivityWithIntent(
+ String pkg,
+ Class<T> activityCls,
+ Intent intent) {
+ intent.setClassName(pkg, activityCls.getName());
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
T activity = (T) getInstrumentation().startActivitySync(intent);
getInstrumentation().waitForIdleSync();
return activity;
}
+
+ /**
+ * Helper for running portions of a test on the UI thread.
+ *
+ * Note, in most cases it is simpler to annotate the test method with
+ * {@link android.test.UiThreadTest}, which will run the entire test method on the UI thread.
+ * Use this method if you need to switch in and out of the UI thread to perform your test.
+ *
+ * @param r runnable containing test code in the {@link Runnable#run()} method
+ */
+ public void runTestOnUiThread(final Runnable r) throws Throwable {
+ final Throwable[] exceptions = new Throwable[1];
+ getInstrumentation().runOnMainSync(new Runnable() {
+ public void run() {
+ try {
+ r.run();
+ } catch (Throwable throwable) {
+ exceptions[0] = throwable;
+ }
+ }
+ });
+ if (exceptions[0] != null) {
+ throw exceptions[0];
+ }
+ }
/**
* Runs the current unit test. If the unit test is annotated with
* {@link android.test.UiThreadTest}, the test is run on the UI thread.
*/
+ @Override
protected void runTest() throws Throwable {
String fName = getName();
assertNotNull(fName);
diff --git a/core/java/android/test/suitebuilder/annotation/LargeTest.java b/core/java/android/test/suitebuilder/annotation/LargeTest.java
new file mode 100644
index 0000000..a6269e7
--- /dev/null
+++ b/core/java/android/test/suitebuilder/annotation/LargeTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.test.suitebuilder.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a test that should run as part of the large tests.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface LargeTest {
+}
diff --git a/core/java/android/test/suitebuilder/annotation/MediumTest.java b/core/java/android/test/suitebuilder/annotation/MediumTest.java
new file mode 100644
index 0000000..8afeb91
--- /dev/null
+++ b/core/java/android/test/suitebuilder/annotation/MediumTest.java
@@ -0,0 +1,31 @@
+/*
+ * 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.test.suitebuilder.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a test that should run as part of the medium tests.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface MediumTest {
+}
diff --git a/core/java/android/test/suitebuilder/annotation/SmallTest.java b/core/java/android/test/suitebuilder/annotation/SmallTest.java
new file mode 100644
index 0000000..ad530e2
--- /dev/null
+++ b/core/java/android/test/suitebuilder/annotation/SmallTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.test.suitebuilder.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a test that should run as part of the small tests.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface SmallTest {
+}
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 2ee4f62..843754b 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -92,7 +92,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
int ellipsizedWidth) {
boolean trust;
- if (ellipsize == null) {
+ if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
replaceWith(source, paint, outerwidth, align, spacingmult,
spacingadd);
@@ -145,7 +145,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
boolean trust;
- if (ellipsize == null) {
+ if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
mEllipsizedWidth = outerwidth;
mEllipsizedStart = 0;
mEllipsizedCount = 0;
diff --git a/core/java/android/text/InputFilter.java b/core/java/android/text/InputFilter.java
index e1563ae..2f55677 100644
--- a/core/java/android/text/InputFilter.java
+++ b/core/java/android/text/InputFilter.java
@@ -33,6 +33,11 @@ public interface InputFilter
* as this is what happens when you delete text. Also beware that
* you should not attempt to make any changes to <code>dest</code>
* from this method; you may only examine it for context.
+ *
+ * Note: If <var>source</var> is an instance of {@link Spanned} or
+ * {@link Spannable}, the span objects in the <var>source</var> should be
+ * copied into the filtered result (i.e. the non-null return value).
+ * {@link TextUtils#copySpansFrom} can be used for convenience.
*/
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend);
diff --git a/core/java/android/text/InputType.java b/core/java/android/text/InputType.java
new file mode 100644
index 0000000..0ffe4ac
--- /dev/null
+++ b/core/java/android/text/InputType.java
@@ -0,0 +1,227 @@
+package android.text;
+
+import android.text.TextUtils;
+
+/**
+ * Bit definitions for an integer defining the basic content type of text
+ * held in an {@link Editable} object.
+ */
+public interface InputType {
+ /**
+ * Mask of bits that determine the overall class
+ * of text being given. Currently supported classes are:
+ * {@link #TYPE_CLASS_TEXT}, {@link #TYPE_CLASS_NUMBER},
+ * {@link #TYPE_CLASS_PHONE}, {@link #TYPE_CLASS_DATETIME}.
+ * If the class is not one you
+ * understand, assume {@link #TYPE_CLASS_TEXT} with NO variation
+ * or flags.
+ */
+ public static final int TYPE_MASK_CLASS = 0x0000000f;
+
+ /**
+ * Mask of bits that determine the variation of
+ * the base content class.
+ */
+ public static final int TYPE_MASK_VARIATION = 0x00000ff0;
+
+ /**
+ * Mask of bits that provide addition bit flags
+ * of options.
+ */
+ public static final int TYPE_MASK_FLAGS = 0x00fff000;
+
+ /**
+ * Special content type for when no explicit type has been specified.
+ * This should be interpreted to mean that the target input connection
+ * is not rich, it can not process and show things like candidate text nor
+ * retrieve the current text, so the input method will need to run in a
+ * limited "generate key events" mode.
+ */
+ public static final int TYPE_NULL = 0x00000000;
+
+ // ----------------------------------------------------------------------
+ // ----------------------------------------------------------------------
+ // ----------------------------------------------------------------------
+
+ /**
+ * Class for normal text. This class supports the following flags (only
+ * one of which should be set):
+ * {@link #TYPE_TEXT_FLAG_CAP_CHARACTERS},
+ * {@link #TYPE_TEXT_FLAG_CAP_WORDS}, and.
+ * {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}. It also supports the
+ * following variations:
+ * {@link #TYPE_TEXT_VARIATION_NORMAL}, and
+ * {@link #TYPE_TEXT_VARIATION_URI}. If you do not recognize the
+ * variation, normal should be assumed.
+ */
+ public static final int TYPE_CLASS_TEXT = 0x00000001;
+
+ /**
+ * Flag for {@link #TYPE_CLASS_TEXT}: capitalize all characters. Overrides
+ * {@link #TYPE_TEXT_FLAG_CAP_WORDS} and
+ * {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}. This value is explicitly defined
+ * to be the same as {@link TextUtils#CAP_MODE_CHARACTERS}.
+ */
+ public static final int TYPE_TEXT_FLAG_CAP_CHARACTERS = 0x00001000;
+
+ /**
+ * Flag for {@link #TYPE_CLASS_TEXT}: capitalize first character of
+ * all words. Overrides {@link #TYPE_TEXT_FLAG_CAP_SENTENCES}. This
+ * value is explicitly defined
+ * to be the same as {@link TextUtils#CAP_MODE_WORDS}.
+ */
+ public static final int TYPE_TEXT_FLAG_CAP_WORDS = 0x00002000;
+
+ /**
+ * Flag for {@link #TYPE_CLASS_TEXT}: capitalize first character of
+ * each sentence. This value is explicitly defined
+ * to be the same as {@link TextUtils#CAP_MODE_SENTENCES}.
+ */
+ public static final int TYPE_TEXT_FLAG_CAP_SENTENCES = 0x00004000;
+
+ /**
+ * Flag for {@link #TYPE_CLASS_TEXT}: the user is entering free-form
+ * text that should have auto-correction applied to it.
+ */
+ public static final int TYPE_TEXT_FLAG_AUTO_CORRECT = 0x00008000;
+
+ /**
+ * Flag for {@link #TYPE_CLASS_TEXT}: the text editor is performing
+ * auto-completion of the text being entered based on its own semantics,
+ * which it will present to the user as they type. This generally means
+ * that the input method should not be showing candidates itself, but can
+ * expect for the editor to supply its own completions/candidates from
+ * {@link android.view.inputmethod.InputMethodSession#displayCompletions
+ * InputMethodSession.displayCompletions()} as a result of the editor calling
+ * {@link android.view.inputmethod.InputMethodManager#displayCompletions
+ * InputMethodManager.displayCompletions()}.
+ */
+ public static final int TYPE_TEXT_FLAG_AUTO_COMPLETE = 0x00010000;
+
+ /**
+ * Flag for {@link #TYPE_CLASS_TEXT}: multiple lines of text can be
+ * entered into the field.
+ */
+ public static final int TYPE_TEXT_FLAG_MULTI_LINE = 0x00020000;
+
+ // ----------------------------------------------------------------------
+
+ /**
+ * Default variation of {@link #TYPE_CLASS_TEXT}: plain old normal text.
+ */
+ public static final int TYPE_TEXT_VARIATION_NORMAL = 0x00000000;
+
+ /**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering a URI.
+ */
+ public static final int TYPE_TEXT_VARIATION_URI = 0x00000010;
+
+ /**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering an e-mail address.
+ */
+ public static final int TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 0x00000020;
+
+ /**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering the subject line of
+ * an e-mail.
+ */
+ public static final int TYPE_TEXT_VARIATION_EMAIL_SUBJECT = 0x00000030;
+
+ /**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering the content of
+ * an e-mail.
+ */
+ public static final int TYPE_TEXT_VARIATION_EMAIL_CONTENT = 0x00000040;
+
+ /**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering the name of a person.
+ */
+ public static final int TYPE_TEXT_VARIATION_PERSON_NAME = 0x00000050;
+
+ /**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering a postal mailing
+ * address.
+ */
+ public static final int TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 0x00000060;
+
+ /**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering a password.
+ */
+ public static final int TYPE_TEXT_VARIATION_PASSWORD = 0x00000070;
+
+ /**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering a search string
+ * for a web search.
+ */
+ public static final int TYPE_TEXT_VARIATION_WEB_SEARCH = 0x00000080;
+
+ /**
+ * Variation of {@link #TYPE_CLASS_TEXT}: entering text inside of
+ * a web form.
+ */
+ public static final int TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x00000090;
+
+ // ----------------------------------------------------------------------
+ // ----------------------------------------------------------------------
+ // ----------------------------------------------------------------------
+
+ /**
+ * Class for numeric text. This class supports the following flag:
+ * {@link #TYPE_NUMBER_FLAG_SIGNED} and
+ * {@link #TYPE_NUMBER_FLAG_DECIMAL}.
+ */
+ public static final int TYPE_CLASS_NUMBER = 0x00000002;
+
+ /**
+ * Flag of {@link #TYPE_CLASS_NUMBER}: the number is signed, allowing
+ * a positive or negative sign at the start.
+ */
+ public static final int TYPE_NUMBER_FLAG_SIGNED = 0x00001000;
+
+ /**
+ * Flag of {@link #TYPE_CLASS_NUMBER}: the number is decimal, allowing
+ * a decimal point to provide fractional values.
+ */
+ public static final int TYPE_NUMBER_FLAG_DECIMAL = 0x00002000;
+
+ // ----------------------------------------------------------------------
+ // ----------------------------------------------------------------------
+ // ----------------------------------------------------------------------
+
+ /**
+ * Class for a phone number. This class currently supports no variations
+ * or flags.
+ */
+ public static final int TYPE_CLASS_PHONE = 0x00000003;
+
+ // ----------------------------------------------------------------------
+ // ----------------------------------------------------------------------
+ // ----------------------------------------------------------------------
+
+ /**
+ * Class for dates and times. It supports the
+ * following variations:
+ * {@link #TYPE_DATETIME_VARIATION_NORMAL}
+ * {@link #TYPE_DATETIME_VARIATION_DATE}, and
+ * {@link #TYPE_DATETIME_VARIATION_TIME},.
+ */
+ public static final int TYPE_CLASS_DATETIME = 0x00000004;
+
+ /**
+ * Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
+ * both a date and time.
+ */
+ public static final int TYPE_DATETIME_VARIATION_NORMAL = 0x00000000;
+
+ /**
+ * Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
+ * only a date.
+ */
+ public static final int TYPE_DATETIME_VARIATION_DATE = 0x00000010;
+
+ /**
+ * Default variation of {@link #TYPE_CLASS_DATETIME}: allows entering
+ * only a time.
+ */
+ public static final int TYPE_DATETIME_VARIATION_TIME = 0x00000020;
+}
diff --git a/core/java/android/text/LoginFilter.java b/core/java/android/text/LoginFilter.java
index dd2d77f..27c703f 100644
--- a/core/java/android/text/LoginFilter.java
+++ b/core/java/android/text/LoginFilter.java
@@ -83,8 +83,21 @@ public abstract class LoginFilter implements InputFilter {
}
onStop();
+
+ if (!changed) {
+ return null;
+ }
- return changed ? new String(out, 0, outidx) : null;
+ String s = new String(out, 0, outidx);
+
+ if (source instanceof Spanned) {
+ SpannableString sp = new SpannableString(s);
+ TextUtils.copySpansFrom((Spanned) source,
+ start, end, null, sp, 0);
+ return sp;
+ } else {
+ return s;
+ }
}
/**
diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java
index 0f4916a..44469ec 100644
--- a/core/java/android/text/Selection.java
+++ b/core/java/android/text/Selection.java
@@ -417,10 +417,13 @@ public class Selection {
}
}
+ private static final class START { };
+ private static final class END { };
+
/*
* Public constants
*/
- public static final Object SELECTION_START = new Object();
- public static final Object SELECTION_END = new Object();
+ public static final Object SELECTION_START = new START();
+ public static final Object SELECTION_END = new END();
}
diff --git a/core/java/android/text/Spanned.java b/core/java/android/text/Spanned.java
index 2b4b4d2..bd0a16b 100644
--- a/core/java/android/text/Spanned.java
+++ b/core/java/android/text/Spanned.java
@@ -26,6 +26,12 @@ public interface Spanned
extends CharSequence
{
/**
+ * Bitmask of bits that are relevent for controlling point/mark behavior
+ * of spans.
+ */
+ public static final int SPAN_POINT_MARK_MASK = 0x33;
+
+ /**
* 0-length spans with type SPAN_MARK_MARK behave like text marks:
* they remain at their original offset when text is inserted
* at that offset.
@@ -92,6 +98,14 @@ extends CharSequence
public static final int SPAN_EXCLUSIVE_INCLUSIVE = SPAN_POINT_POINT;
/**
+ * This flag is set on spans that are being used to apply temporary
+ * styling information on the composing text of an input method, so that
+ * they can be found and removed when the composing text is being
+ * replaced.
+ */
+ public static final int SPAN_COMPOSING = 0x100;
+
+ /**
* 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 2d18575..ceb9f4f 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -907,7 +907,8 @@ extends Layout
mLineDirections[j] = linedirs;
- if (ellipsize != null) {
+ // If ellipsize is in marquee mode, do not apply ellipsis on the first line
+ if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
calculateEllipsis(start, end, widths, widstart, widoff,
ellipsiswidth, ellipsize, j,
textwidth, paint);
@@ -950,7 +951,7 @@ extends Layout
ellipsisStart = 0;
ellipsisCount = i;
- } else if (where == TextUtils.TruncateAt.END) {
+ } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE) {
float sum = 0;
int i;
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index e791aaf..64356d5 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -22,6 +22,7 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.method.TextKeyListener.Capitalize;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.AlignmentSpan;
import android.text.style.BackgroundColorSpan;
@@ -42,13 +43,14 @@ import android.text.style.TextAppearanceSpan;
import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
+import android.util.Printer;
+
import com.android.internal.util.ArrayUtils;
import java.util.regex.Pattern;
import java.util.Iterator;
-public class TextUtils
-{
+public class TextUtils {
private TextUtils() { /* cannot be instantiated */ }
private static String[] EMPTY_STRING_ARRAY = new String[]{};
@@ -827,6 +829,30 @@ public class TextUtils
};
/**
+ * Debugging tool to print the spans in a CharSequence. The output will
+ * be printed one span per line. If the CharSequence is not a Spanned,
+ * then the entire string will be printed on a single line.
+ */
+ public static void dumpSpans(CharSequence cs, Printer printer, String prefix) {
+ if (cs instanceof Spanned) {
+ Spanned sp = (Spanned) cs;
+ Object[] os = sp.getSpans(0, cs.length(), Object.class);
+
+ for (int i = 0; i < os.length; i++) {
+ Object o = os[i];
+ printer.println(prefix + cs.subSequence(sp.getSpanStart(o),
+ sp.getSpanEnd(o)) + ": "
+ + Integer.toHexString(System.identityHashCode(o))
+ + " " + o.getClass().getCanonicalName()
+ + " (" + sp.getSpanStart(o) + "-" + sp.getSpanEnd(o)
+ + ") fl=#" + sp.getSpanFlags(o));
+ }
+ } else {
+ printer.println(prefix + cs + ": (no spans)");
+ }
+ }
+
+ /**
* Return a new CharSequence in which each of the source strings is
* replaced by the corresponding element of the destinations.
*/
@@ -1021,6 +1047,7 @@ public class TextUtils
START,
MIDDLE,
END,
+ MARQUEE,
}
public interface EllipsizeCallback {
@@ -1460,7 +1487,7 @@ public class TextUtils
case '&':
sb.append("&amp;"); //$NON-NLS-1$
break;
- case '\\':
+ case '\'':
sb.append("&apos;"); //$NON-NLS-1$
break;
case '"':
@@ -1565,6 +1592,132 @@ public class TextUtils
return true;
}
+ /**
+ * Capitalization mode for {@link #getCapsMode}: capitalize all
+ * characters. This value is explicitly defined to be the same as
+ * {@link InputType#TYPE_TEXT_FLAG_CAP_CHARACTERS}.
+ */
+ public static final int CAP_MODE_CHARACTERS
+ = InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
+
+ /**
+ * Capitalization mode for {@link #getCapsMode}: capitalize the first
+ * character of all words. This value is explicitly defined to be the same as
+ * {@link InputType#TYPE_TEXT_FLAG_CAP_WORDS}.
+ */
+ public static final int CAP_MODE_WORDS
+ = InputType.TYPE_TEXT_FLAG_CAP_WORDS;
+
+ /**
+ * Capitalization mode for {@link #getCapsMode}: capitalize the first
+ * character of each sentence. This value is explicitly defined to be the same as
+ * {@link InputType#TYPE_TEXT_FLAG_CAP_SENTENCES}.
+ */
+ public static final int CAP_MODE_SENTENCES
+ = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
+
+ /**
+ * Determine what caps mode should be in effect at the current offset in
+ * the text. Only the mode bits set in <var>reqModes</var> will be
+ * checked. Note that the caps mode flags here are explicitly defined
+ * to match those in {@link InputType}.
+ *
+ * @param cs The text that should be checked for caps modes.
+ * @param off Location in the text at which to check.
+ * @param reqModes The modes to be checked: may be any combination of
+ * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and
+ * {@link #CAP_MODE_SENTENCES}.
+ *
+ * @return Returns the actual capitalization modes that can be in effect
+ * at the current position, which is any combination of
+ * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and
+ * {@link #CAP_MODE_SENTENCES}.
+ */
+ public static int getCapsMode(CharSequence cs, int off, int reqModes) {
+ int i;
+ char c;
+ int mode = 0;
+
+ if ((reqModes&CAP_MODE_CHARACTERS) != 0) {
+ mode |= CAP_MODE_CHARACTERS;
+ }
+ if ((reqModes&(CAP_MODE_WORDS|CAP_MODE_SENTENCES)) == 0) {
+ return mode;
+ }
+
+ // Back over allowed opening punctuation.
+
+ for (i = off; i > 0; i--) {
+ c = cs.charAt(i - 1);
+
+ if (c != '"' && c != '\'' &&
+ Character.getType(c) != Character.START_PUNCTUATION) {
+ break;
+ }
+ }
+
+ // Start of paragraph, with optional whitespace.
+
+ int j = i;
+ while (j > 0 && ((c = cs.charAt(j - 1)) == ' ' || c == '\t')) {
+ j--;
+ }
+ if (j == 0 || cs.charAt(j - 1) == '\n') {
+ return mode | CAP_MODE_WORDS;
+ }
+
+ // Or start of word if we are that style.
+
+ if ((reqModes&CAP_MODE_SENTENCES) == 0) {
+ if (i != j) mode |= CAP_MODE_WORDS;
+ return mode;
+ }
+
+ // There must be a space if not the start of paragraph.
+
+ if (i == j) {
+ return mode;
+ }
+
+ // Back over allowed closing punctuation.
+
+ for (; j > 0; j--) {
+ c = cs.charAt(j - 1);
+
+ if (c != '"' && c != '\'' &&
+ Character.getType(c) != Character.END_PUNCTUATION) {
+ break;
+ }
+ }
+
+ if (j > 0) {
+ c = cs.charAt(j - 1);
+
+ if (c == '.' || c == '?' || c == '!') {
+ // Do not capitalize if the word ends with a period but
+ // also contains a period, in which case it is an abbreviation.
+
+ if (c == '.') {
+ for (int k = j - 2; k >= 0; k--) {
+ c = cs.charAt(k);
+
+ if (c == '.') {
+ return mode;
+ }
+
+ if (!Character.isLetter(c)) {
+ break;
+ }
+ }
+ }
+
+ return mode | CAP_MODE_SENTENCES;
+ }
+ }
+
+ return mode;
+ }
+
private static Object sLock = new Object();
private static char[] sTemp = null;
}
diff --git a/core/java/android/pim/DateFormat.java b/core/java/android/text/format/DateFormat.java
index 802e045..3437978 100644
--- a/core/java/android/pim/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -14,19 +14,21 @@
* limitations under the License.
*/
-package android.pim;
+package android.text.format;
import android.content.Context;
import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
+import com.android.internal.R;
+
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
+import java.text.SimpleDateFormat;
/**
Utility class for producing strings with formatted date/time.
@@ -187,12 +189,32 @@ public class DateFormat {
public static final char YEAR = 'y';
/**
- * @return true if the user has set the system to use a 24 hour time
- * format, else false.
+ * Returns true if user preference is set to 24-hour format.
+ * @param context the context to use for the content resolver
+ * @return true if 24 hour time format is selected, false otherwise.
*/
public static boolean is24HourFormat(Context context) {
String value = Settings.System.getString(context.getContentResolver(),
Settings.System.TIME_12_24);
+
+ if (value == null) {
+ java.text.DateFormat natural =
+ java.text.DateFormat.getTimeInstance(
+ java.text.DateFormat.LONG,
+ context.getResources().getConfiguration().locale);
+
+ if (natural instanceof SimpleDateFormat) {
+ SimpleDateFormat sdf = (SimpleDateFormat) natural;
+ String pattern = sdf.toPattern();
+
+ if (pattern.indexOf('H') >= 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
boolean b24 = !(value == null || value.equals("12"));
return b24;
}
@@ -205,7 +227,15 @@ public class DateFormat {
*/
public static final java.text.DateFormat getTimeFormat(Context context) {
boolean b24 = is24HourFormat(context);
- return new java.text.SimpleDateFormat(b24 ? "H:mm" : "h:mm a");
+ int res;
+
+ if (b24) {
+ res = R.string.twenty_four_hour_time_format;
+ } else {
+ res = R.string.twelve_hour_time_format;
+ }
+
+ return new java.text.SimpleDateFormat(context.getString(res));
}
/**
@@ -228,13 +258,29 @@ public class DateFormat {
public static final java.text.DateFormat getLongDateFormat(Context context) {
String value = getDateFormatString(context);
if (value.indexOf('M') < value.indexOf('d')) {
- value = "MMMM dd, yyyy";
+ value = context.getString(R.string.full_date_month_first);
} else {
- value = "dd MMMM, yyyy";
+ value = context.getString(R.string.full_date_day_first);
}
return new java.text.SimpleDateFormat(value);
}
-
+
+ /**
+ * Returns a {@link java.text.DateFormat} object that can format the date
+ * in medium form (such as Dec. 31, 1999) based on user preference.
+ * @param context the application context
+ * @return the {@link java.text.DateFormat} object that formats the date in long form.
+ */
+ public static final java.text.DateFormat getMediumDateFormat(Context context) {
+ String value = getDateFormatString(context);
+ if (value.indexOf('M') < value.indexOf('d')) {
+ value = context.getString(R.string.medium_date_month_first);
+ } else {
+ value = context.getString(R.string.medium_date_day_first);
+ }
+ return new java.text.SimpleDateFormat(value);
+ }
+
/**
* Gets the current date format stored as a char array. The array will contain
* 3 elements ({@link #DATE}, {@link #MONTH}, and {@link #YEAR}) in the order
@@ -274,15 +320,33 @@ public class DateFormat {
String value = Settings.System.getString(context.getContentResolver(),
Settings.System.DATE_FORMAT);
if (value == null || value.length() < 6) {
+ /*
+ * No need to localize -- this is an emergency fallback in case
+ * the setting is missing, but it should always be set.
+ */
value = "MM-dd-yyyy";
}
return value;
}
+ /**
+ * Given a format string and a time in milliseconds since Jan 1, 1970 GMT, returns a
+ * CharSequence containing the requested date.
+ * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
+ * @param inTimeInMillis in milliseconds since Jan 1, 1970 GMT
+ * @return a {@link CharSequence} containing the requested text
+ */
public static final CharSequence format(CharSequence inFormat, long inTimeInMillis) {
return format(inFormat, new Date(inTimeInMillis));
}
+ /**
+ * Given a format string and a {@link java.util.Date} object, returns a CharSequence containing
+ * the requested date.
+ * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
+ * @param inDate the date to format
+ * @return a {@link CharSequence} containing the requested text
+ */
public static final CharSequence format(CharSequence inFormat, Date inDate) {
Calendar c = new GregorianCalendar();
@@ -291,6 +355,13 @@ public class DateFormat {
return format(inFormat, c);
}
+ /**
+ * Given a format string and a {@link java.util.Calendar} object, returns a CharSequence
+ * containing the requested date.
+ * @param inFormat the format string, as described in {@link android.text.format.DateFormat}
+ * @param inDate the date to format
+ * @return a {@link CharSequence} containing the requested text
+ */
public static final CharSequence format(CharSequence inFormat, Calendar inDate) {
SpannableStringBuilder s = new SpannableStringBuilder(inFormat);
int c;
diff --git a/core/java/android/pim/DateUtils.java b/core/java/android/text/format/DateUtils.java
index 2a01f12..48f65c6 100644
--- a/core/java/android/pim/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -14,25 +14,26 @@
* limitations under the License.
*/
-package android.pim;
+package android.text.format;
+
+import com.android.internal.R;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.pim.DateException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
-import com.android.internal.R;
-
/**
+ * This class contains various date-related utilities for creating text for things like
+ * elapsed time and date ranges, strings for days of the week and months, and AM/PM text etc.
*/
public class DateUtils
{
- private static final String TAG = "DateUtils";
-
private static final Object sLock = new Object();
private static final int[] sDaysLong = new int[] {
com.android.internal.R.string.day_of_week_long_sunday,
@@ -125,9 +126,7 @@ public class DateUtils
com.android.internal.R.string.am,
com.android.internal.R.string.pm,
};
- private static int sFirstDay;
private static Configuration sLastConfig;
- private static String sStatusDateFormat;
private static String sStatusTimeFormat;
private static String sElapsedFormatMMSS;
private static String sElapsedFormatHMMSS;
@@ -153,17 +152,18 @@ public class DateUtils
public static final int FORMAT_NO_YEAR = 0x00008;
public static final int FORMAT_SHOW_DATE = 0x00010;
public static final int FORMAT_NO_MONTH_DAY = 0x00020;
- public static final int FORMAT_24HOUR = 0x00040;
- public static final int FORMAT_CAP_AMPM = 0x00080;
- public static final int FORMAT_NO_NOON = 0x00100;
- public static final int FORMAT_CAP_NOON = 0x00200;
- public static final int FORMAT_NO_MIDNIGHT = 0x00400;
- public static final int FORMAT_CAP_MIDNIGHT = 0x00800;
- public static final int FORMAT_UTC = 0x01000;
- public static final int FORMAT_ABBREV_TIME = 0x02000;
- public static final int FORMAT_ABBREV_WEEKDAY = 0x04000;
- public static final int FORMAT_ABBREV_MONTH = 0x08000;
- public static final int FORMAT_NUMERIC_DATE = 0x10000;
+ public static final int FORMAT_12HOUR = 0x00040;
+ public static final int FORMAT_24HOUR = 0x00080;
+ public static final int FORMAT_CAP_AMPM = 0x00100;
+ public static final int FORMAT_NO_NOON = 0x00200;
+ public static final int FORMAT_CAP_NOON = 0x00400;
+ public static final int FORMAT_NO_MIDNIGHT = 0x00800;
+ public static final int FORMAT_CAP_MIDNIGHT = 0x01000;
+ public static final int FORMAT_UTC = 0x02000;
+ public static final int FORMAT_ABBREV_TIME = 0x04000;
+ public static final int FORMAT_ABBREV_WEEKDAY = 0x08000;
+ public static final int FORMAT_ABBREV_MONTH = 0x10000;
+ public static final int FORMAT_NUMERIC_DATE = 0x20000;
public static final int FORMAT_ABBREV_ALL = (FORMAT_ABBREV_TIME
| FORMAT_ABBREV_WEEKDAY | FORMAT_ABBREV_MONTH);
public static final int FORMAT_CAP_NOON_MIDNIGHT = (FORMAT_CAP_NOON | FORMAT_CAP_MIDNIGHT);
@@ -172,10 +172,6 @@ public class DateUtils
// Date and time format strings that are constant and don't need to be
// translated.
public static final String HOUR_MINUTE_24 = "%H:%M";
- public static final String HOUR_MINUTE_AMPM = "%-l:%M%P";
- public static final String HOUR_MINUTE_CAP_AMPM = "%-l:%M%p";
- public static final String HOUR_AMPM = "%-l%P";
- public static final String HOUR_CAP_AMPM = "%-l%p";
public static final String MONTH_FORMAT = "%B";
public static final String ABBREV_MONTH_FORMAT = "%b";
public static final String NUMERIC_MONTH_FORMAT = "%m";
@@ -238,7 +234,7 @@ public class DateUtils
/**
* Request the full spelled-out name.
- * For use with the 'abbrev' parameter of {@link #getDayOfWeekStr} and {@link #getMonthStr}.
+ * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
* @more
* <p>e.g. "Sunday" or "January"
*/
@@ -246,7 +242,7 @@ public class DateUtils
/**
* Request an abbreviated version of the name.
- * For use with the 'abbrev' parameter of {@link #getDayOfWeekStr} and {@link #getMonthStr}.
+ * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
* @more
* <p>e.g. "Sun" or "Jan"
*/
@@ -254,7 +250,7 @@ public class DateUtils
/**
* Request a shorter abbreviated version of the name.
- * For use with the 'abbrev' parameter of {@link #getDayOfWeekStr} and {@link #getMonthStr}.
+ * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
* @more
* <p>e.g. "Su" or "Jan"
* <p>In some languages, the results returned for LENGTH_SHORT may be the same as
@@ -264,7 +260,7 @@ public class DateUtils
/**
* Request an even shorter abbreviated version of the name.
- * For use with the 'abbrev' parameter of {@link #getDayOfWeekStr} and {@link #getMonthStr}.
+ * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
* @more
* <p>e.g. "M", "Tu", "Th" or "J"
* <p>In some languages, the results returned for LENGTH_SHORTEST may be the same as
@@ -274,7 +270,7 @@ public class DateUtils
/**
* Request an even shorter abbreviated version of the name.
- * For use with the 'abbrev' parameter of {@link #getDayOfWeekStr} and {@link #getMonthStr}.
+ * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
* @more
* <p>e.g. "S", "T", "T" or "J"
* <p>In some languages, the results returned for LENGTH_SHORTEST may be the same as
@@ -282,11 +278,10 @@ public class DateUtils
*/
public static final int LENGTH_SHORTEST = 50;
-
/**
* Return a string for the day of the week.
- * @param dayOfWeek One of {@link #Calendar.SUNDAY Calendar.SUNDAY},
- * {@link #Calendar.MONDAY Calendar.MONDAY}, etc.
+ * @param dayOfWeek One of {@link Calendar#SUNDAY Calendar.SUNDAY},
+ * {@link Calendar#MONDAY Calendar.MONDAY}, etc.
* @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_SHORT}, {@link #LENGTH_SHORTER}
* or {@link #LENGTH_SHORTEST}. For forward compatibility, anything else
* will return the same as {#LENGTH_MEDIUM}.
@@ -308,9 +303,10 @@ public class DateUtils
}
/**
- * Return a string for AM or PM.
+ * Return a localized string for AM or PM.
* @param ampm Either {@link Calendar#AM Calendar.AM} or {@link Calendar#PM Calendar.PM}.
* @throws IndexOutOfBoundsException if the ampm is out of bounds.
+ * @return Localized version of "AM" or "PM".
*/
public static String getAMPMString(int ampm) {
Resources r = Resources.getSystem();
@@ -318,12 +314,13 @@ public class DateUtils
}
/**
- * Return a string for the day of the week.
- * @param month One of {@link #Calendar.JANUARY Calendar.JANUARY},
- * {@link #Calendar.FEBRUARY Calendar.FEBRUARY}, etc.
+ * Return a localized string for the day of the week.
+ * @param month One of {@link Calendar#JANUARY Calendar.JANUARY},
+ * {@link Calendar#FEBRUARY Calendar.FEBRUARY}, etc.
* @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_SHORT}, {@link #LENGTH_SHORTER}
* or {@link #LENGTH_SHORTEST}. For forward compatibility, anything else
* will return the same as {#LENGTH_MEDIUM}.
+ * @return Localized day of the week.
*/
public static String getMonthString(int month, int abbrev) {
// Note that here we use sMonthsMedium for MEDIUM, SHORT and SHORTER.
@@ -344,6 +341,12 @@ public class DateUtils
return r.getString(list[month - Calendar.JANUARY]);
}
+ /**
+ * Returns a string describing the elapsed time since startTime.
+ * @param startTime some time in the past.
+ * @return a String object containing the elapsed time.
+ * @see #getRelativeTimeSpanString(long, long, long)
+ */
public static CharSequence getRelativeTimeSpanString(long startTime) {
return getRelativeTimeSpanString(startTime, System.currentTimeMillis(), MINUTE_IN_MILLIS);
}
@@ -363,67 +366,50 @@ public class DateUtils
public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution) {
Resources r = Resources.getSystem();
- // TODO: Assembling strings by hand like this is bad style for i18n.
- boolean past = (now > time);
- String prefix = past ? null : r.getString(com.android.internal.R.string.in);
- String postfix = past ? r.getString(com.android.internal.R.string.ago) : null;
- return getRelativeTimeSpanString(time, now, minResolution, prefix, postfix);
- }
-
- public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution,
- String prefix, String postfix) {
- Resources r = Resources.getSystem();
-
+ boolean past = (now >= time);
long duration = Math.abs(now - time);
+ int resId;
+ long count;
if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) {
- long count = duration / SECOND_IN_MILLIS;
- String singular = r.getString(com.android.internal.R.string.second);
- String plural = r.getString(com.android.internal.R.string.seconds);
- return pluralizedSpan(count, singular, plural, prefix, postfix);
- }
-
- if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) {
- long count = duration / MINUTE_IN_MILLIS;
- String singular = r.getString(com.android.internal.R.string.minute);
- String plural = r.getString(com.android.internal.R.string.minutes);
- return pluralizedSpan(count, singular, plural, prefix, postfix);
- }
-
- if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) {
- long count = duration / HOUR_IN_MILLIS;
- String singular = r.getString(com.android.internal.R.string.hour);
- String plural = r.getString(com.android.internal.R.string.hours);
- return pluralizedSpan(count, singular, plural, prefix, postfix);
- }
-
- if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) {
- return getRelativeDayString(r, time, now);
- }
-
- return dateString(time);
- }
-
-
- private static final String pluralizedSpan(long count, String singular, String plural,
- String prefix, String postfix) {
- StringBuilder s = new StringBuilder();
-
- if (prefix != null) {
- s.append(prefix);
- s.append(" ");
+ count = duration / SECOND_IN_MILLIS;
+ if (past) {
+ resId = com.android.internal.R.plurals.num_seconds_ago;
+ } else {
+ resId = com.android.internal.R.plurals.in_num_seconds;
+ }
+ } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) {
+ count = duration / MINUTE_IN_MILLIS;
+ if (past) {
+ resId = com.android.internal.R.plurals.num_minutes_ago;
+ } else {
+ resId = com.android.internal.R.plurals.in_num_minutes;
+ }
+ } else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) {
+ count = duration / HOUR_IN_MILLIS;
+ if (past) {
+ resId = com.android.internal.R.plurals.num_hours_ago;
+ } else {
+ resId = com.android.internal.R.plurals.in_num_hours;
+ }
+ } else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) {
+ count = duration / DAY_IN_MILLIS;
+ if (past) {
+ resId = com.android.internal.R.plurals.num_days_ago;
+ } else {
+ resId = com.android.internal.R.plurals.in_num_days;
+ }
+ } else {
+ // Longer than a week ago, so just show the date.
+ int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_ABBREV_MONTH;
+
+ // We know that we won't be showing the time, so it is safe to pass
+ // in a null context.
+ return formatDateRange(null, time, time, flags);
}
- s.append(count);
- s.append(' ');
- s.append(count == 0 || count > 1 ? plural : singular);
-
- if (postfix != null) {
- s.append(" ");
- s.append(postfix);
- }
-
- return s.toString();
+ String format = r.getQuantityString(resId, (int) count);
+ return String.format(format, count);
}
/**
@@ -457,12 +443,16 @@ public class DateUtils
} else if (days == 0) {
return r.getString(com.android.internal.R.string.today);
}
-
- if (!past) {
- return r.getString(com.android.internal.R.string.daysDurationFuturePlural, days);
+
+ int resId;
+ if (past) {
+ resId = com.android.internal.R.plurals.num_days_ago;
} else {
- return r.getString(com.android.internal.R.string.daysDurationPastPlural, days);
+ resId = com.android.internal.R.plurals.in_num_days;
}
+
+ String format = r.getQuantityString(resId, days);
+ return String.format(format, days);
}
private static void initFormatStrings() {
@@ -472,7 +462,6 @@ public class DateUtils
if (sLastConfig == null || !sLastConfig.equals(cfg)) {
sLastConfig = cfg;
sStatusTimeFormat = r.getString(com.android.internal.R.string.status_bar_time_format);
- sStatusDateFormat = r.getString(com.android.internal.R.string.status_bar_date_format);
sElapsedFormatMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_mm_ss);
sElapsedFormatHMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_h_mm_ss);
}
@@ -488,20 +477,11 @@ public class DateUtils
initFormatStrings();
return DateFormat.format(sStatusTimeFormat, millis);
}
-
- /**
- * Format a date so it appears like it would in the status bar clock.
- * @deprecated use {@link #DateFormat.getDateFormat(Context)} instead.
- * @hide
- */
- public static final CharSequence dateString(long startTime) {
- initFormatStrings();
- return DateFormat.format(sStatusDateFormat, startTime);
- }
/**
- * Formats an elapsed time like MM:SS or H:MM:SS
+ * Formats an elapsed time in the form "MM:SS" or "H:MM:SS"
* for display on the call-in-progress screen.
+ * @param elapsedSeconds the elapsed time in seconds.
*/
public static String formatElapsedTime(long elapsedSeconds) {
initFormatStrings();
@@ -584,7 +564,7 @@ public class DateUtils
return (char) (digit + '0');
}
- /*
+ /**
* Format a date / time such that if the then is on the same day as now, it shows
* just the time and if it's a different day, it shows just the date.
*
@@ -623,7 +603,7 @@ public class DateUtils
/**
* @hide
- * @deprecated use {@link android.pim.Time}
+ * @deprecated use {@link android.text.format.Time}
*/
public static Calendar newCalendar(boolean zulu)
{
@@ -652,7 +632,7 @@ public class DateUtils
/**
* @hide
- * @deprecated use {@link android.pim.Time}
+ * @deprecated use {@link android.text.format.Time}
*/
private static final int ctoi(String str, int index)
throws DateException
@@ -667,7 +647,7 @@ public class DateUtils
/**
* @hide
- * @deprecated use {@link android.pim.Time}
+ * @deprecated use {@link android.text.format.Time}
*/
private static final int check(int lowerBound, int upperBound, int value)
throws DateException
@@ -681,7 +661,7 @@ public class DateUtils
/**
* @hide
- * @deprecated use {@link android.pim.Time}
+ * @deprecated use {@link android.text.format.Time}
* Return true if this date string is local time
*/
public static boolean isUTC(String s)
@@ -702,7 +682,7 @@ public class DateUtils
// Returns if the Z was present, meaning that the time is in UTC
/**
* @hide
- * @deprecated use {@link android.pim.Time}
+ * @deprecated use {@link android.text.format.Time}
*/
public static boolean parseDateTime(String str, Calendar cal)
throws DateException
@@ -748,7 +728,7 @@ public class DateUtils
* Given a timezone string which can be null, and a dateTime string,
* set that time into a calendar.
* @hide
- * @deprecated use {@link android.pim.Time}
+ * @deprecated use {@link android.text.format.Time}
*/
public static void parseDateTime(String tz, String dateTime, Calendar out)
throws DateException
@@ -778,7 +758,7 @@ public class DateUtils
*
* @param cal the date and time to write
* @hide
- * @deprecated use {@link android.pim.Time}
+ * @deprecated use {@link android.text.format.Time}
*/
public static String writeDateTime(Calendar cal)
{
@@ -796,7 +776,7 @@ public class DateUtils
* be written at the end as per RFC2445. Otherwise, the time is
* considered in localtime.
* @hide
- * @deprecated use {@link android.pim.Time}
+ * @deprecated use {@link android.text.format.Time}
*/
public static String writeDateTime(Calendar cal, boolean zulu)
{
@@ -819,7 +799,7 @@ public class DateUtils
* has already been called on sb to the appropriate length
* which is sb.setLength(zulu ? 16 : 15)
* @hide
- * @deprecated use {@link android.pim.Time}
+ * @deprecated use {@link android.text.format.Time}
*/
public static String writeDateTime(Calendar cal, StringBuilder sb)
{
@@ -866,7 +846,7 @@ public class DateUtils
/**
* @hide
- * @deprecated use {@link android.pim.Time}
+ * @deprecated use {@link android.text.format.Time}
*/
public static void assign(Calendar lval, Calendar rval)
{
@@ -876,8 +856,31 @@ public class DateUtils
}
/**
- * Creates a string describing a date/time range. The flags argument
- * is a bitmask of options from the following list:
+ * Formats a date or a time range according to the local conventions.
+ *
+ * <p>
+ * Example output strings (date formats in these examples are shown using
+ * the US date format convention but that may change depending on the
+ * local settings):
+ * <ul>
+ * <li>10:15am</li>
+ * <li>3:00pm - 4:00pm</li>
+ * <li>3pm - 4pm</li>
+ * <li>3PM - 4PM</li>
+ * <li>08:00 - 17:00</li>
+ * <li>Oct 9</li>
+ * <li>Tue, Oct 9</li>
+ * <li>October 9, 2007</li>
+ * <li>Oct 9 - 10</li>
+ * <li>Oct 9 - 10, 2007</li>
+ * <li>Oct 28 - Nov 3, 2007</li>
+ * <li>Dec 31, 2007 - Jan 1, 2008</li>
+ * <li>Oct 9, 8:00am - Oct 10, 5:00pm</li>
+ * <li>12/31/2007 - 01/01/2008</li>
+ * </ul>
+ *
+ * <p>
+ * The flags argument is a bitmask of options from the following list:
*
* <ul>
* <li>FORMAT_SHOW_TIME</li>
@@ -886,6 +889,7 @@ public class DateUtils
* <li>FORMAT_NO_YEAR</li>
* <li>FORMAT_SHOW_DATE</li>
* <li>FORMAT_NO_MONTH_DAY</li>
+ * <li>FORMAT_12HOUR</li>
* <li>FORMAT_24HOUR</li>
* <li>FORMAT_CAP_AMPM</li>
* <li>FORMAT_NO_NOON</li>
@@ -946,15 +950,25 @@ public class DateUtils
* shown instead of "midnight".
*
* <p>
+ * If FORMAT_12HOUR is set and the time is shown, then the time is
+ * shown in the 12-hour time format. You should not normally set this.
+ * Instead, let the time format be chosen automatically according to the
+ * system settings. If both FORMAT_12HOUR and FORMAT_24HOUR are set, then
+ * FORMAT_24HOUR takes precedence.
+ *
+ * <p>
* If FORMAT_24HOUR is set and the time is shown, then the time is
- * shown in the 24-hour time format.
+ * shown in the 24-hour time format. You should not normally set this.
+ * Instead, let the time format be chosen automatically according to the
+ * system settings. If both FORMAT_12HOUR and FORMAT_24HOUR are set, then
+ * FORMAT_24HOUR takes precedence.
*
* <p>
* If FORMAT_UTC is set, then the UTC timezone is used for the start
* and end milliseconds.
*
* <p>
- * If FORMAT_ABBREV_TIME is set and FORMAT_24HOUR is not set, then the
+ * If FORMAT_ABBREV_TIME is set and 12-hour time format is used, then the
* start and end times (if shown) are abbreviated by not showing the minutes
* if they are zero. For example, instead of "3:00pm" the time would be
* abbreviated to "3pm".
@@ -976,30 +990,15 @@ public class DateUtils
* instead of using the name of the month. For example, "12/31/2008"
* instead of "December 31, 2008".
*
- * <p>
- * Example output strings:
- * <ul>
- * <li>10:15am</li>
- * <li>3:00pm - 4:00pm</li>
- * <li>3pm - 4pm</li>
- * <li>3PM - 4PM</li>
- * <li>08:00 - 17:00</li>
- * <li>Oct 9</li>
- * <li>Tue, Oct 9</li>
- * <li>October 9, 2007</li>
- * <li>Oct 9 - 10</li>
- * <li>Oct 9 - 10, 2007</li>
- * <li>Oct 28 - Nov 3, 2007</li>
- * <li>Dec 31, 2007 - Jan 1, 2008</li>
- * <li>Oct 9, 8:00am - Oct 10, 5:00pm</li>
- * </ul>
+ * @param context the context is required only if the time is shown
* @param startMillis the start time in UTC milliseconds
* @param endMillis the end time in UTC milliseconds
* @param flags a bit mask of options
*
- * @return a string with the formatted date/time range.
+ * @return a string containing the formatted date/time range.
*/
- public static String formatDateRange(long startMillis, long endMillis, int flags) {
+ public static String formatDateRange(Context context, long startMillis,
+ long endMillis, int flags) {
Resources res = Resources.getSystem();
boolean showTime = (flags & FORMAT_SHOW_TIME) != 0;
boolean showWeekDay = (flags & FORMAT_SHOW_WEEKDAY) != 0;
@@ -1008,7 +1007,6 @@ public class DateUtils
boolean useUTC = (flags & FORMAT_UTC) != 0;
boolean abbrevWeekDay = (flags & FORMAT_ABBREV_WEEKDAY) != 0;
boolean abbrevMonth = (flags & FORMAT_ABBREV_MONTH) != 0;
- boolean use24Hour = (flags & FORMAT_24HOUR) != 0;
boolean noMonthDay = (flags & FORMAT_NO_MONTH_DAY) != 0;
boolean numericDate = (flags & FORMAT_NUMERIC_DATE) != 0;
@@ -1039,7 +1037,7 @@ public class DateUtils
// the end of Nov 11?).
// If we are not showing the time then also adjust the end date
// for multiple-day events. This is to allow us to display, for
- // example, "Nov 10 -11" for an event with an start date of Nov 10
+ // example, "Nov 10 -11" for an event with a start date of Nov 10
// 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.
@@ -1075,6 +1073,16 @@ public class DateUtils
if (showTime) {
String startTimeFormat = "";
String endTimeFormat = "";
+ boolean force24Hour = (flags & FORMAT_24HOUR) != 0;
+ boolean force12Hour = (flags & FORMAT_12HOUR) != 0;
+ boolean use24Hour;
+ if (force24Hour) {
+ use24Hour = true;
+ } else if (force12Hour) {
+ use24Hour = false;
+ } else {
+ use24Hour = DateFormat.is24HourFormat(context);
+ }
if (use24Hour) {
startTimeFormat = HOUR_MINUTE_24;
endTimeFormat = HOUR_MINUTE_24;
@@ -1090,28 +1098,28 @@ public class DateUtils
boolean endOnTheHour = endDate.minute == 0 && endDate.second == 0;
if (abbrevTime && startOnTheHour) {
if (capAMPM) {
- startTimeFormat = HOUR_CAP_AMPM;
+ startTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
} else {
- startTimeFormat = HOUR_AMPM;
+ startTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
}
} else {
if (capAMPM) {
- startTimeFormat = HOUR_MINUTE_CAP_AMPM;
+ startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
} else {
- startTimeFormat = HOUR_MINUTE_AMPM;
+ startTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
}
}
if (abbrevTime && endOnTheHour) {
if (capAMPM) {
- endTimeFormat = HOUR_CAP_AMPM;
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_cap_ampm);
} else {
- endTimeFormat = HOUR_AMPM;
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_ampm);
}
} else {
if (capAMPM) {
- endTimeFormat = HOUR_MINUTE_CAP_AMPM;
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_cap_ampm);
} else {
- endTimeFormat = HOUR_MINUTE_AMPM;
+ endTimeFormat = res.getString(com.android.internal.R.string.hour_minute_ampm);
}
}
@@ -1346,63 +1354,117 @@ public class DateUtils
}
/**
+ * Formats a date or a time according to the local conventions. There are
+ * lots of options that allow the caller to control, for example, if the
+ * time is shown, if the day of the week is shown, if the month name is
+ * abbreviated, if noon is shown instead of 12pm, and so on. For the
+ * complete list of options, see the documentation for
+ * {@link #formatDateRange}.
+ * <p>
+ * Example output strings (date formats in these examples are shown using
+ * the US date format convention but that may change depending on the
+ * local settings):
+ * <ul>
+ * <li>10:15am</li>
+ * <li>3:00pm</li>
+ * <li>3pm</li>
+ * <li>3PM</li>
+ * <li>08:00</li>
+ * <li>17:00</li>
+ * <li>noon</li>
+ * <li>Noon</li>
+ * <li>midnight</li>
+ * <li>Midnight</li>
+ * <li>Oct 31</li>
+ * <li>Oct 31, 2007</li>
+ * <li>October 31, 2007</li>
+ * <li>10am, Oct 31</li>
+ * <li>17:00, Oct 31</li>
+ * <li>Wed</li>
+ * <li>Wednesday</li>
+ * <li>10am, Wed, Oct 31</li>
+ * <li>Wed, Oct 31</li>
+ * <li>Wednesday, Oct 31</li>
+ * <li>Wed, Oct 31, 2007</li>
+ * <li>Wed, October 31</li>
+ * <li>10/31/2007</li>
+ * </ul>
+ *
+ * @param context the context is required only if the time is shown
+ * @param millis a point in time in UTC milliseconds
+ * @param flags a bit mask of formatting options
+ * @return a string containing the formatted date/time.
+ */
+ public static String formatDateTime(Context context, long millis, int flags) {
+ return formatDateRange(context, millis, millis, flags);
+ }
+
+ /**
* @return a relative time string to display the time expressed by millis. Times
* are counted starting at midnight, which means that assuming that the current
* time is March 31st, 0:30:
- * "millis=0:10 today" will be displayed as "0:10"
- * "millis=11:30pm the day before" will be displayed as "Mar 30"
- * A similar scheme is used to dates that are a week, a month or more than a year old.
+ * <ul>
+ * <li>"millis=0:10 today" will be displayed as "0:10"</li>
+ * <li>"millis=11:30pm the day before" will be displayed as "Mar 30"</li>
+ * </ul>
+ * If the given millis is in a different year, then the full date is
+ * returned in numeric format (e.g., "10/12/2008").
*
* @param withPreposition If true, the string returned will include the correct
- * preposition ("at 9:20am", "in 2008" or "on May 29").
+ * preposition ("at 9:20am", "on 10/12/2008" or "on May 29").
*/
public static CharSequence getRelativeTimeSpanString(Context c, long millis,
boolean withPreposition) {
- long span = System.currentTimeMillis() - millis;
+ long now = System.currentTimeMillis();
+ long span = now - millis;
Resources res = c.getResources();
if (sNowTime == null) {
sNowTime = new Time();
sThenTime = new Time();
- sMonthDayFormat = res.getString(com.android.internal.R.string.abbrev_month_day);
}
- sNowTime.setToNow();
+ sNowTime.set(now);
sThenTime.set(millis);
+ String result;
+ int prepositionId;
if (span < DAY_IN_MILLIS && sNowTime.weekDay == sThenTime.weekDay) {
// Same day
- return getPrepositionDate(res, sThenTime, R.string.preposition_for_time,
- HOUR_MINUTE_CAP_AMPM, withPreposition);
+ int flags = FORMAT_SHOW_TIME;
+ result = formatDateRange(c, millis, millis, flags);
+ prepositionId = R.string.preposition_for_time;
} else if (sNowTime.year != sThenTime.year) {
// Different years
- // TODO: take locale into account so that the display will adjust correctly.
- return getPrepositionDate(res, sThenTime, R.string.preposition_for_year,
- NUMERIC_MONTH_FORMAT + "/" + MONTH_DAY_FORMAT + "/" + YEAR_FORMAT_TWO_DIGITS,
- withPreposition);
+ int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE;
+ result = formatDateRange(c, millis, millis, flags);
+
+ // This is a date (like "10/31/2008" so use the date preposition)
+ prepositionId = R.string.preposition_for_date;
} else {
// Default
- return getPrepositionDate(res, sThenTime, R.string.preposition_for_date,
- sMonthDayFormat, withPreposition);
+ int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_MONTH;
+ result = formatDateRange(c, millis, millis, flags);
+ prepositionId = R.string.preposition_for_date;
}
+ if (withPreposition) {
+ result = res.getString(prepositionId, result);
+ }
+ return result;
}
/**
- * @return A date string suitable for display based on the format and including the
- * date preposition if withPreposition is true.
+ * Convenience function to return relative time string without preposition.
+ * @param c context for resources
+ * @param millis time in milliseconds
+ * @return {@link CharSequence} containing relative time.
+ * @see #getRelativeTimeSpanString(Context, long, boolean)
*/
- private static String getPrepositionDate(Resources res, Time thenTime, int id,
- String formatString, boolean withPreposition) {
- String result = thenTime.format(formatString);
- return withPreposition ? res.getString(id, result) : result;
- }
-
public static CharSequence getRelativeTimeSpanString(Context c, long millis) {
return getRelativeTimeSpanString(c, millis, false /* no preposition */);
}
private static Time sNowTime;
private static Time sThenTime;
- private static String sMonthDayFormat;
}
diff --git a/core/java/android/content/Formatter.java b/core/java/android/text/format/Formatter.java
index 8ad9f40..1b30aa0 100644
--- a/core/java/android/content/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-package android.content;
+package android.text.format;
+
+import android.content.Context;
/**
* Utility class to aid in formatting common values that are not covered
- * by the standard java.util.Formatter
- * @hide
+ * by the standard java.util.Formatter.
*/
public final class Formatter {
/**
- * Formats a content size to be in the form of bytes, kilobytes,
- * megabytes, etc
+ * Formats a content size to be in the form of bytes, kilobytes, megabytes, etc
*
* @param context Context to use to load the localized units
* @param number size value to be formated
@@ -63,4 +63,21 @@ public final class Formatter {
}
return String.format("%.0f%s", result, context.getText(suffix).toString());
}
+
+ /**
+ * Returns a string in the canonical IP format ###.###.###.### from a packed integer containing
+ * the IP address. The IP address is expected to be in little-endian format (LSB first). That
+ * is, 0x01020304 will return "4.3.2.1".
+ *
+ * @param addr the IP address as a packed integer with LSB first.
+ * @return string with canonical IP address format.
+ */
+ public static String formatIpAddress(int addr) {
+ StringBuffer buf = new StringBuffer();
+ buf.append(addr & 0xff).append('.').
+ append((addr >>>= 8) & 0xff).append('.').
+ append((addr >>>= 8) & 0xff).append('.').
+ append((addr >>>= 8) & 0xff);
+ return buf.toString();
+ }
}
diff --git a/core/java/android/pim/Time.java b/core/java/android/text/format/Time.java
index 59ba87b..5bf9b20 100644
--- a/core/java/android/pim/Time.java
+++ b/core/java/android/text/format/Time.java
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-package android.pim;
-
+package android.text.format;
+import android.content.res.Resources;
+import java.util.Locale;
import java.util.TimeZone;
/**
- * {@hide}
- *
* The Time class is a faster replacement for the java.util.Calendar and
* java.util.GregorianCalendar classes. An instance of the Time class represents
* a moment in time, specified with second precision. It is modelled after
@@ -30,6 +29,10 @@ import java.util.TimeZone;
* functionality.
*/
public class Time {
+ private static final String Y_M_D_T_H_M_S_000 = "%Y-%m-%dT%H:%M:%S.000";
+ private static final String Y_M_D_T_H_M_S_000_Z = "%Y-%m-%dT%H:%M:%S.000Z";
+ private static final String Y_M_D = "%Y-%m-%d";
+
public static final String TIMEZONE_UTC = "UTC";
/**
@@ -90,6 +93,7 @@ public class Time {
* <li><b>positive</b> - in dst</li>
* <li><b>0</b> - not in dst</li>
* <li><b>negative</b> - unknown</li>
+ * </ul>
*/
public int isDst;
@@ -125,9 +129,26 @@ public class Time {
public static final int FRIDAY = 5;
public static final int SATURDAY = 6;
+ /*
+ * The Locale for which date formatting strings have been loaded.
+ */
+ private static Locale sLocale;
+ private static String[] sShortMonths;
+ private static String[] sLongMonths;
+ private static String[] sShortWeekdays;
+ private static String[] sLongWeekdays;
+ private static String sTimeOnlyFormat;
+ private static String sDateOnlyFormat;
+ private static String sDateTimeFormat;
+ private static String sAm;
+ private static String sPm;
+ private static String sDateCommand = "%a %b %e %H:%M:%S %Z %Y";
+
/**
* Construct a Time object in the timezone named by the string
* argument "timezone". The time is initialized to Jan 1, 1970.
+ * @param timezone string containing the timezone to use.
+ * @see TimeZone
*/
public Time(String timezone) {
if (timezone == null) {
@@ -142,7 +163,7 @@ public class Time {
}
/**
- * Construct a Time object in the local timezone. The time is initialized to
+ * Construct a Time object in the default timezone. The time is initialized to
* Jan 1, 1970.
*/
public Time() {
@@ -191,6 +212,8 @@ public class Time {
* Return the maximum possible value for the given field given the value of
* the other fields. Requires that it be normalized for MONTH_DAY and
* YEAR_DAY.
+ * @param field one of the constants for HOUR, MINUTE, SECOND, etc.
+ * @return the maximum value for the field.
*/
public int getActualMaximum(int field) {
switch (field) {
@@ -230,6 +253,7 @@ public class Time {
/**
* Clears all values, setting the timezone to the given timezone. Sets isDst
* to a negative value to mean "unknown".
+ * @param timezone the timezone to use.
*/
public void clear(String timezone) {
if (timezone == null) {
@@ -259,8 +283,76 @@ public class Time {
* Print the current value given the format string provided. See man
* strftime for what means what. The final string must be less than 256
* characters.
- */
- native public String format(String format);
+ * @param format a string containing the desired format.
+ * @return a String containing the current time expressed in the current locale.
+ */
+ public String format(String format) {
+ synchronized (Time.class) {
+ Locale locale = Locale.getDefault();
+
+ if (sLocale == null || locale == null || !(locale.equals(sLocale))) {
+ Resources r = Resources.getSystem();
+
+ sShortMonths = new String[] {
+ r.getString(com.android.internal.R.string.month_medium_january),
+ r.getString(com.android.internal.R.string.month_medium_february),
+ r.getString(com.android.internal.R.string.month_medium_march),
+ r.getString(com.android.internal.R.string.month_medium_april),
+ r.getString(com.android.internal.R.string.month_medium_may),
+ r.getString(com.android.internal.R.string.month_medium_june),
+ r.getString(com.android.internal.R.string.month_medium_july),
+ r.getString(com.android.internal.R.string.month_medium_august),
+ r.getString(com.android.internal.R.string.month_medium_september),
+ r.getString(com.android.internal.R.string.month_medium_october),
+ r.getString(com.android.internal.R.string.month_medium_november),
+ r.getString(com.android.internal.R.string.month_medium_december),
+ };
+ sLongMonths = new String[] {
+ r.getString(com.android.internal.R.string.month_long_january),
+ r.getString(com.android.internal.R.string.month_long_february),
+ r.getString(com.android.internal.R.string.month_long_march),
+ r.getString(com.android.internal.R.string.month_long_april),
+ r.getString(com.android.internal.R.string.month_long_may),
+ r.getString(com.android.internal.R.string.month_long_june),
+ r.getString(com.android.internal.R.string.month_long_july),
+ r.getString(com.android.internal.R.string.month_long_august),
+ r.getString(com.android.internal.R.string.month_long_september),
+ r.getString(com.android.internal.R.string.month_long_october),
+ r.getString(com.android.internal.R.string.month_long_november),
+ r.getString(com.android.internal.R.string.month_long_december),
+ };
+ sShortWeekdays = new String[] {
+ r.getString(com.android.internal.R.string.day_of_week_medium_sunday),
+ r.getString(com.android.internal.R.string.day_of_week_medium_monday),
+ r.getString(com.android.internal.R.string.day_of_week_medium_tuesday),
+ r.getString(com.android.internal.R.string.day_of_week_medium_wednesday),
+ r.getString(com.android.internal.R.string.day_of_week_medium_thursday),
+ r.getString(com.android.internal.R.string.day_of_week_medium_friday),
+ r.getString(com.android.internal.R.string.day_of_week_medium_saturday),
+ };
+ sLongWeekdays = new String[] {
+ r.getString(com.android.internal.R.string.day_of_week_long_sunday),
+ r.getString(com.android.internal.R.string.day_of_week_long_monday),
+ r.getString(com.android.internal.R.string.day_of_week_long_tuesday),
+ r.getString(com.android.internal.R.string.day_of_week_long_wednesday),
+ r.getString(com.android.internal.R.string.day_of_week_long_thursday),
+ r.getString(com.android.internal.R.string.day_of_week_long_friday),
+ r.getString(com.android.internal.R.string.day_of_week_long_saturday),
+ };
+ sTimeOnlyFormat = r.getString(com.android.internal.R.string.time_of_day);
+ sDateOnlyFormat = r.getString(com.android.internal.R.string.month_day_year);
+ sDateTimeFormat = r.getString(com.android.internal.R.string.date_and_time);
+ sAm = r.getString(com.android.internal.R.string.am);
+ sPm = r.getString(com.android.internal.R.string.pm);
+
+ sLocale = locale;
+ }
+
+ return format1(format);
+ }
+ }
+
+ native private String format1(String format);
/**
* Return the current time in YYYYMMDDTHHMMSS<tz> format
@@ -269,33 +361,79 @@ public class Time {
native public String toString();
/**
- * Parse a time in the current zone in YYYYMMDDTHHMMSS format.
- */
- native public void parse(String s);
-
- /**
- * Parse a time in RFC 2445 format. Returns whether or not the time is in
- * UTC (ends with Z).
+ * Parses a date-time string in either the RFC 2445 format or an abbreviated
+ * format that does not include the "time" field. For example, all of the
+ * following strings are valid:
+ *
+ * <ul>
+ * <li>"20081013T160000Z"</li>
+ * <li>"20081013T160000"</li>
+ * <li>"20081013"</li>
+ * </ul>
+ *
+ * Returns whether or not the time is in UTC (ends with Z). If the string
+ * ends with "Z" then the timezone is set to UTC. If the date-time string
+ * included only a date and no time field, then the <code>allDay</code>
+ * field of this Time class is set to true and the <code>hour</code>,
+ * <code>minute</code>, and <code>second</code> fields are set to zero;
+ * otherwise (a time field was included in the date-time string)
+ * <code>allDay</code> is set to false. The fields <code>weekDay</code>,
+ * <code>yearDay</code>, and <code>gmtoff</code> are always set to zero,
+ * and the field <code>isDst</code> is set to -1 (unknown). To set those
+ * fields, call {@link #normalize(boolean)} after parsing.
+ *
+ * To parse a date-time string and convert it to UTC milliseconds, do
+ * something like this:
+ *
+ * <pre>
+ * Time time = new Time();
+ * String date = "20081013T160000Z";
+ * time.parse(date);
+ * long millis = time.normalize(false);
+ * </pre>
*
* @param s the string to parse
* @return true if the resulting time value is in UTC time
*/
- public boolean parse2445(String s) {
- if (nativeParse2445(s)) {
+ public boolean parse(String s) {
+ if (nativeParse(s)) {
timezone = TIMEZONE_UTC;
return true;
}
return false;
}
- native private boolean nativeParse2445(String s);
+ /**
+ * Parse a time in the current zone in YYYYMMDDTHHMMSS format.
+ */
+ native private boolean nativeParse(String s);
/**
* Parse a time in RFC 3339 format. This method also parses simple dates
- * (that is, strings that contain no time or time offset). If the string
- * contains a time and time offset, then the time offset will be used to
- * convert the time value to UTC.
+ * (that is, strings that contain no time or time offset). For example,
+ * all of the following strings are valid:
+ *
+ * <ul>
+ * <li>"2008-10-13T16:00:00.000Z"</li>
+ * <li>"2008-10-13T16:00:00.000+07:00"</li>
+ * <li>"2008-10-13T16:00:00.000-07:00"</li>
+ * <li>"2008-10-13"</li>
+ * </ul>
+ *
+ * <p>
+ * If the string contains a time and time offset, then the time offset will
+ * be used to convert the time value to UTC.
+ * </p>
+ *
+ * <p>
+ * If the given string contains just a date (with no time field), then
+ * the {@link #allDay} field is set to true and the {@link #hour},
+ * {@link #minute}, and {@link #second} fields are set to zero.
+ * </p>
+ *
+ * <p>
* Returns true if the resulting time value is in UTC time.
+ * </p>
*
* @param s the string to parse
* @return true if the resulting time value is in UTC time
@@ -408,8 +546,8 @@ public class Time {
}
/**
- * Set the fields. Sets weekDay, yearDay and gmtoff to 0. Call
- * normalize() if you need those.
+ * Sets the fields. Sets weekDay, yearDay and gmtoff to 0, and isDst to -1.
+ * Call {@link #normalize(boolean)} if you need those.
*/
public void set(int second, int minute, int hour, int monthDay, int month, int year) {
this.allDay = false;
@@ -425,6 +563,15 @@ public class Time {
this.gmtoff = 0;
}
+ /**
+ * Sets the date from the given fields. Also sets allDay to true.
+ * Sets weekDay, yearDay and gmtoff to 0, and isDst to -1.
+ * Call {@link #normalize(boolean)} if you need those.
+ *
+ * @param monthDay the day of the month (in the range [1,31])
+ * @param month the zero-based month number (in the range [0,11])
+ * @param year the year
+ */
public void set(int monthDay, int month, int year) {
this.allDay = true;
this.second = 0;
@@ -439,10 +586,25 @@ public class Time {
this.gmtoff = 0;
}
+ /**
+ * Returns true if the time represented by this Time object occurs before
+ * the given time.
+ *
+ * @param that a given Time object to compare against
+ * @return true if this time is less than the given time
+ */
public boolean before(Time that) {
return Time.compare(this, that) < 0;
}
+
+ /**
+ * Returns true if the time represented by this Time object occurs after
+ * the given time.
+ *
+ * @param that a given Time object to compare against
+ * @return true if this time is greater than the given time
+ */
public boolean after(Time that) {
return Time.compare(this, that) > 0;
}
@@ -459,14 +621,18 @@ public class Time {
* object must already be normalized because this method uses the
* yearDay and weekDay fields.
*
+ * <p>
* In IS0 8601, weeks start on Monday.
* The first week of the year (week 1) is defined by ISO 8601 as the
* first week with four or more of its days in the starting year.
* Or equivalently, the week containing January 4. Or equivalently,
* the week with the year's first Thursday in it.
+ * </p>
*
+ * <p>
* The week number can be calculated by counting Thursdays. Week N
* contains the Nth Thursday of the year.
+ * </p>
*
* @return the ISO week number.
*/
@@ -486,13 +652,24 @@ public class Time {
return temp.yearDay / 7 + 1;
}
+ /**
+ * Return a string in the RFC 3339 format.
+ * <p>
+ * If allDay is true, expresses the time as Y-M-D</p>
+ * <p>
+ * Otherwise, if the timezone is UTC, expresses the time as Y-M-D-T-H-M-S UTC</p>
+ * <p>
+ * Otherwise the time is expressed the time as Y-M-D-T-H-M-S +- GMT</p>
+ * @param allDay
+ * @return string in the RFC 3339 format.
+ */
public String format3339(boolean allDay) {
if (allDay) {
- return format("%Y-%m-%d");
+ return format(Y_M_D);
} else if (TIMEZONE_UTC.equals(timezone)) {
- return format("%Y-%m-%dT%H:%M:%S.000Z");
+ return format(Y_M_D_T_H_M_S_000_Z);
} else {
- String base = format("%Y-%m-%dT%H:%M:%S.000");
+ String base = format(Y_M_D_T_H_M_S_000);
String sign = (gmtoff < 0) ? "-" : "+";
int offset = (int)Math.abs(gmtoff);
int minutes = (offset % 3600) / 60;
@@ -502,6 +679,13 @@ public class Time {
}
}
+ /**
+ * Returns true if the day of the given time is the epoch on the Julian Calendar
+ * (January 1, 1970 on the Gregorian calendar).
+ *
+ * @param time the time to test
+ * @return true if epoch.
+ */
public static boolean isEpoch(Time time) {
long millis = time.toMillis(true);
return getJulianDay(millis, 0) == EPOCH_JULIAN_DAY;
@@ -536,6 +720,7 @@ public class Time {
* GMT offset than whatever is currently stored in this Time object anyway.
* After this method returns all the fields will be normalized and the time
* will be set to 12am at the beginning of the given Julian day.
+ * </p>
*
* <p>
* The only exception to this is if 12am does not exist for that day because
@@ -543,6 +728,7 @@ public class Time {
* hour at 12am on April 25, 2008 and there are a few other places that
* also change daylight saving time at 12am. In those cases, the time
* will be set to 1am.
+ * </p>
*
* @param julianDay the Julian day in the timezone for this Time object
* @return the UTC milliseconds for the beginning of the Julian day
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index ac2e499..652413e 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -155,34 +155,11 @@ implements MovementMethod
return false;
}
- public boolean onTrackballEvent(TextView widget, Spannable buffer,
- MotionEvent event) {
- boolean handled = false;
- int x = (int) event.getX();
- int y = (int) event.getY();
-
- for (; y < 0; y++) {
- handled |= up(widget, buffer);
- }
- for (; y > 0; y--) {
- handled |= down(widget, buffer);
- }
-
- for (; x < 0; x++) {
- handled |= left(widget, buffer);
- }
- for (; x > 0; x--) {
- handled |= right(widget, buffer);
- }
-
- if (handled) {
- MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
- MetaKeyKeyListener.resetLockedMeta(buffer);
- }
-
- return handled;
+ public boolean onTrackballEvent(TextView widget, Spannable text,
+ MotionEvent event) {
+ return false;
}
-
+
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
boolean handled = Touch.onTouchEvent(widget, buffer, event);
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index 3e92b7b..a875368 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -18,9 +18,9 @@ package android.text.method;
import android.view.KeyEvent;
import android.view.View;
-import android.os.Message;
-import android.util.Log;
+import android.text.InputType;
import android.text.*;
+import android.text.method.TextKeyListener.Capitalize;
import android.widget.TextView;
public abstract class BaseKeyListener
@@ -99,6 +99,25 @@ implements KeyListener {
return true;
}
+ static int makeTextContentType(Capitalize caps, boolean autoText) {
+ int contentType = InputType.TYPE_CLASS_TEXT;
+ switch (caps) {
+ case CHARACTERS:
+ contentType |= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
+ break;
+ case WORDS:
+ contentType |= InputType.TYPE_TEXT_FLAG_CAP_WORDS;
+ break;
+ case SENTENCES:
+ contentType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
+ break;
+ }
+ if (autoText) {
+ contentType |= InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
+ }
+ return contentType;
+ }
+
public boolean onKeyDown(View view, Editable content,
int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DEL) {
diff --git a/core/java/android/text/method/CharacterPickerDialog.java b/core/java/android/text/method/CharacterPickerDialog.java
index d787132..3c406751 100644
--- a/core/java/android/text/method/CharacterPickerDialog.java
+++ b/core/java/android/text/method/CharacterPickerDialog.java
@@ -69,7 +69,7 @@ public class CharacterPickerDialog extends Dialog
WindowManager.LayoutParams params = getWindow().getAttributes();
params.token = mView.getApplicationWindowToken();
- params.type = params.TYPE_APPLICATION_PANEL;
+ params.type = params.TYPE_APPLICATION_ATTACHED_DIALOG;
setTitle(R.string.select_character);
setContentView(R.layout.character_picker);
diff --git a/core/java/android/text/method/DateKeyListener.java b/core/java/android/text/method/DateKeyListener.java
index 0ca0332..7c11434 100644
--- a/core/java/android/text/method/DateKeyListener.java
+++ b/core/java/android/text/method/DateKeyListener.java
@@ -17,12 +17,18 @@
package android.text.method;
import android.view.KeyEvent;
+import android.text.InputType;
/**
* For entering dates in a text field.
*/
public class DateKeyListener extends NumberKeyListener
{
+ public int getInputType() {
+ return InputType.TYPE_CLASS_DATETIME
+ | InputType.TYPE_DATETIME_VARIATION_DATE;
+ }
+
@Override
protected char[] getAcceptedChars()
{
diff --git a/core/java/android/text/method/DateTimeKeyListener.java b/core/java/android/text/method/DateTimeKeyListener.java
index 304d326..f8ebc40 100644
--- a/core/java/android/text/method/DateTimeKeyListener.java
+++ b/core/java/android/text/method/DateTimeKeyListener.java
@@ -16,6 +16,7 @@
package android.text.method;
+import android.text.InputType;
import android.view.KeyEvent;
/**
@@ -23,6 +24,11 @@ import android.view.KeyEvent;
*/
public class DateTimeKeyListener extends NumberKeyListener
{
+ public int getInputType() {
+ return InputType.TYPE_CLASS_DATETIME
+ | InputType.TYPE_DATETIME_VARIATION_NORMAL;
+ }
+
@Override
protected char[] getAcceptedChars()
{
diff --git a/core/java/android/text/method/DialerKeyListener.java b/core/java/android/text/method/DialerKeyListener.java
index e805ad7..b121e60 100644
--- a/core/java/android/text/method/DialerKeyListener.java
+++ b/core/java/android/text/method/DialerKeyListener.java
@@ -18,7 +18,7 @@ package android.text.method;
import android.view.KeyEvent;
import android.view.KeyCharacterMap.KeyData;
-import android.util.SparseIntArray;
+import android.text.InputType;
import android.text.Spannable;
/**
@@ -40,6 +40,10 @@ public class DialerKeyListener extends NumberKeyListener
return sInstance;
}
+ public int getInputType() {
+ return InputType.TYPE_CLASS_PHONE;
+ }
+
/**
* Overrides the superclass's lookup method to prefer the number field
* from the KeyEvent.
diff --git a/core/java/android/text/method/DigitsKeyListener.java b/core/java/android/text/method/DigitsKeyListener.java
index 99a3f1a..f0f072c 100644
--- a/core/java/android/text/method/DigitsKeyListener.java
+++ b/core/java/android/text/method/DigitsKeyListener.java
@@ -16,6 +16,7 @@
package android.text.method;
+import android.text.InputType;
import android.text.Spanned;
import android.text.SpannableStringBuilder;
import android.view.KeyEvent;
@@ -109,6 +110,17 @@ public class DigitsKeyListener extends NumberKeyListener
return dim;
}
+ public int getInputType() {
+ int contentType = InputType.TYPE_CLASS_NUMBER;
+ if (mSign) {
+ contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
+ }
+ if (mDecimal) {
+ contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
+ }
+ return contentType;
+ }
+
@Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
diff --git a/core/java/android/text/method/KeyListener.java b/core/java/android/text/method/KeyListener.java
index 05ab72d..4ae6191 100644
--- a/core/java/android/text/method/KeyListener.java
+++ b/core/java/android/text/method/KeyListener.java
@@ -16,14 +16,39 @@
package android.text.method;
+import android.text.Editable;
import android.view.KeyEvent;
import android.view.View;
-import android.os.Message;
-import android.text.*;
-import android.widget.TextView;
-public interface KeyListener
-{
+/**
+ * Interface for converting text key events into edit operations on an
+ * Editable class. Note that for must cases this interface has been
+ * superceded by general soft input methods as defined by
+ * {@link android.view.inputmethod.InputMethod}; it should only be used
+ * for cases where an application has its own on-screen keypad and also wants
+ * to process hard keyboard events to match it.
+ */
+public interface KeyListener {
+ /**
+ * Return the type of text that this key listener is manipulating,
+ * as per {@link android.text.InputType}. This is used to
+ * determine the mode of the soft keyboard that is shown for the editor.
+ *
+ * <p>If you return
+ * {@link android.text.InputType#TYPE_NULL}
+ * then <em>no</em> soft keyboard will provided. In other words, you
+ * must be providing your own key pad for on-screen input and the key
+ * listener will be used to handle input from a hard keyboard.
+ *
+ * <p>If you
+ * return any other value, a soft input method will be created when the
+ * user puts focus in the editor, which will provide a keypad and also
+ * consume hard key events. This means that the key listener will generally
+ * not be used, instead the soft input method will take care of managing
+ * key input as per the content type returned here.
+ */
+ public int getInputType();
+
/**
* If the key listener wants to handle this key, return true,
* otherwise return false and the caller (i.e. the widget host)
@@ -39,4 +64,9 @@ public interface KeyListener
*/
public boolean onKeyUp(View view, Editable text,
int keyCode, 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/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java
index 2d75b87..f0305d9 100644
--- a/core/java/android/text/method/MetaKeyKeyListener.java
+++ b/core/java/android/text/method/MetaKeyKeyListener.java
@@ -221,6 +221,12 @@ public abstract class MetaKeyKeyListener {
content.setSpan(what, 0, 0, RELEASED);
}
+ 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);
+ }
+
/**
* The meta key has been pressed but has not yet been used.
*/
diff --git a/core/java/android/text/method/MultiTapKeyListener.java b/core/java/android/text/method/MultiTapKeyListener.java
index 7137d40..6d94788 100644
--- a/core/java/android/text/method/MultiTapKeyListener.java
+++ b/core/java/android/text/method/MultiTapKeyListener.java
@@ -18,14 +18,11 @@ package android.text.method;
import android.view.KeyEvent;
import android.view.View;
-import android.os.Message;
import android.os.Handler;
import android.os.SystemClock;
import android.text.*;
import android.text.method.TextKeyListener.Capitalize;
-import android.widget.TextView;
import android.util.SparseArray;
-import android.util.SparseIntArray;
/**
* This is the standard key listener for alphabetic input on 12-key
@@ -77,6 +74,10 @@ public class MultiTapKeyListener extends BaseKeyListener
return sInstance[off];
}
+ public int getInputType() {
+ return makeTextContentType(mCapitalize, mAutoText);
+ }
+
public boolean onKeyDown(View view, Editable content,
int keyCode, KeyEvent event) {
int selStart, selEnd;
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index ae7ba8f..863b2e2 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -58,6 +58,10 @@ public class QwertyKeyListener extends BaseKeyListener {
return sInstance[off];
}
+ public int getInputType() {
+ return makeTextContentType(mAutoCap, mAutoText);
+ }
+
public boolean onKeyDown(View view, Editable content,
int keyCode, KeyEvent event) {
int selStart, selEnd;
@@ -219,7 +223,7 @@ public class QwertyKeyListener extends BaseKeyListener {
if ((pref & TextKeyListener.AUTO_TEXT) != 0 && mAutoText &&
(i == ' ' || i == '\t' || i == '\n' ||
i == ',' || i == '.' || i == '!' || i == '?' ||
- i == '"' || i == ')' || i == ']') &&
+ i == '"' || Character.getType(i) == Character.END_PUNCTUATION) &&
content.getSpanEnd(TextKeyListener.INHIBIT_REPLACEMENT)
!= oldStart) {
int x;
@@ -257,7 +261,16 @@ public class QwertyKeyListener extends BaseKeyListener {
content.charAt(selEnd - 2) == ' ') {
char c = content.charAt(selEnd - 3);
- if (Character.isLetter(c)) {
+ for (int j = selEnd - 3; j > 0; j--) {
+ if (c == '"' ||
+ Character.getType(c) == Character.END_PUNCTUATION) {
+ c = content.charAt(j - 1);
+ } else {
+ break;
+ }
+ }
+
+ if (Character.isLetter(c) || Character.isDigit(c)) {
content.replace(selEnd - 2, selEnd - 1, ".");
}
}
diff --git a/core/java/android/text/method/ScrollingMovementMethod.java b/core/java/android/text/method/ScrollingMovementMethod.java
index 0438e1e..db470be 100644
--- a/core/java/android/text/method/ScrollingMovementMethod.java
+++ b/core/java/android/text/method/ScrollingMovementMethod.java
@@ -171,34 +171,16 @@ implements MovementMethod
return false;
}
+ public boolean onTrackballEvent(TextView widget, Spannable text,
+ MotionEvent event) {
+ return false;
+ }
+
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
return Touch.onTouchEvent(widget, buffer, event);
}
- public boolean onTrackballEvent(TextView widget, Spannable buffer,
- MotionEvent event) {
- boolean handled = false;
- int x = (int) event.getX();
- int y = (int) event.getY();
-
- for (; y < 0; y++) {
- handled |= up(widget, buffer);
- }
- for (; y > 0; y--) {
- handled |= down(widget, buffer);
- }
-
- for (; x < 0; x++) {
- handled |= left(widget, buffer);
- }
- for (; x > 0; x--) {
- handled |= right(widget, buffer);
- }
-
- return handled;
- }
-
public void initialize(TextView widget, Spannable text) { }
public boolean canSelectArbitrarily() {
diff --git a/core/java/android/text/method/TextKeyListener.java b/core/java/android/text/method/TextKeyListener.java
index 012e41d..b1c380a 100644
--- a/core/java/android/text/method/TextKeyListener.java
+++ b/core/java/android/text/method/TextKeyListener.java
@@ -26,6 +26,7 @@ import android.text.*;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
+import android.text.InputType;
import java.lang.ref.WeakReference;
@@ -114,76 +115,15 @@ public class TextKeyListener extends BaseKeyListener implements SpanWatcher {
return true;
}
- // Back over allowed opening punctuation.
-
- for (i = off; i > 0; i--) {
- c = cs.charAt(i - 1);
-
- if (c != '"' && c != '(' && c != '[' && c != '\'') {
- break;
- }
- }
-
- // Start of paragraph, with optional whitespace.
-
- int j = i;
- while (j > 0 && ((c = cs.charAt(j - 1)) == ' ' || c == '\t')) {
- j--;
- }
- if (j == 0 || cs.charAt(j - 1) == '\n') {
- return true;
- }
-
- // Or start of word if we are that style.
-
- if (cap == Capitalize.WORDS) {
- return i != j;
- }
-
- // There must be a space if not the start of paragraph.
-
- if (i == j) {
- return false;
- }
-
- // Back over allowed closing punctuation.
-
- for (; j > 0; j--) {
- c = cs.charAt(j - 1);
-
- if (c != '"' && c != ')' && c != ']' && c != '\'') {
- break;
- }
- }
-
- if (j > 0) {
- c = cs.charAt(j - 1);
-
- if (c == '.' || c == '?' || c == '!') {
- // Do not capitalize if the word ends with a period but
- // also contains a period, in which case it is an abbreviation.
-
- if (c == '.') {
- for (int k = j - 2; k >= 0; k--) {
- c = cs.charAt(k);
-
- if (c == '.') {
- return false;
- }
-
- if (!Character.isLetter(c)) {
- break;
- }
- }
- }
-
- return true;
- }
- }
-
- return false;
+ return TextUtils.getCapsMode(cs, off, cap == Capitalize.WORDS
+ ? TextUtils.CAP_MODE_WORDS : TextUtils.CAP_MODE_SENTENCES)
+ != 0;
}
+ public int getInputType() {
+ return makeTextContentType(mAutoCap, mAutoText);
+ }
+
@Override
public boolean onKeyDown(View view, Editable content,
int keyCode, KeyEvent event) {
@@ -251,6 +191,10 @@ public class TextKeyListener extends BaseKeyListener implements SpanWatcher {
private static class NullKeyListener implements KeyListener
{
+ public int getInputType() {
+ return InputType.TYPE_NULL;
+ }
+
public boolean onKeyDown(View view, Editable content,
int keyCode, KeyEvent event) {
return false;
@@ -261,6 +205,9 @@ public class TextKeyListener extends BaseKeyListener implements SpanWatcher {
return false;
}
+ public void clearMetaKeyState(View view, Editable content, int states) {
+ }
+
public static NullKeyListener getInstance() {
if (sInstance != null)
return sInstance;
diff --git a/core/java/android/text/method/TimeKeyListener.java b/core/java/android/text/method/TimeKeyListener.java
index 9ba1fe6..3fbfd8c 100644
--- a/core/java/android/text/method/TimeKeyListener.java
+++ b/core/java/android/text/method/TimeKeyListener.java
@@ -17,12 +17,18 @@
package android.text.method;
import android.view.KeyEvent;
+import android.text.InputType;
/**
* For entering times in a text field.
*/
public class TimeKeyListener extends NumberKeyListener
{
+ public int getInputType() {
+ return InputType.TYPE_CLASS_DATETIME
+ | InputType.TYPE_DATETIME_VARIATION_TIME;
+ }
+
@Override
protected char[] getAcceptedChars()
{
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index bd01728..8b097c5 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.Layout.Alignment;
import android.text.Spannable;
import android.view.MotionEvent;
import android.view.View;
@@ -41,15 +42,31 @@ public class Touch {
int left = Integer.MAX_VALUE;
int right = 0;
+ Alignment a = null;
for (int i = top; i <= bottom; i++) {
left = (int) Math.min(left, layout.getLineLeft(i));
right = (int) Math.max(right, layout.getLineRight(i));
+
+ if (a == null) {
+ a = layout.getParagraphAlignment(i);
+ }
}
padding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight();
- x = Math.min(x, right - (widget.getWidth() - padding));
- x = Math.max(x, left);
+ int width = widget.getWidth();
+ int diff = 0;
+
+ if (right - left < width - padding) {
+ if (a == Alignment.ALIGN_CENTER) {
+ diff = (width - padding - (right - left)) / 2;
+ } else if (a == Alignment.ALIGN_OPPOSITE) {
+ diff = width - padding - (right - left);
+ }
+ }
+
+ x = Math.min(x, right - (width - padding) - diff);
+ x = Math.max(x, left - diff);
widget.scrollTo(x, y);
}
diff --git a/core/java/android/text/style/DynamicDrawableSpan.java b/core/java/android/text/style/DynamicDrawableSpan.java
index 3bcc335..dd89b68 100644
--- a/core/java/android/text/style/DynamicDrawableSpan.java
+++ b/core/java/android/text/style/DynamicDrawableSpan.java
@@ -16,11 +16,12 @@
package android.text.style;
-import java.lang.ref.WeakReference;
-
-import android.graphics.drawable.Drawable;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import java.lang.ref.WeakReference;
/**
*
@@ -35,48 +36,51 @@ extends ReplacementSpan
*/
public abstract Drawable getDrawable();
+ @Override
public int getSize(Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm) {
- Drawable b = getCachedDrawable();
+ Drawable d = getCachedDrawable();
+ Rect rect = d.getBounds();
if (fm != null) {
- fm.ascent = -b.getIntrinsicHeight();
- fm.descent = 0;
+ fm.ascent = -rect.bottom;
+ fm.descent = 0;
fm.top = fm.ascent;
fm.bottom = 0;
}
- return b.getIntrinsicWidth();
+ return rect.right;
}
+ @Override
public void draw(Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, Paint paint) {
Drawable b = getCachedDrawable();
canvas.save();
- canvas.translate(x, bottom-b.getIntrinsicHeight());;
+ canvas.translate(x, bottom - b.getBounds().bottom);
b.draw(canvas);
canvas.restore();
}
private Drawable getCachedDrawable() {
- WeakReference wr = mDrawableRef;
- Drawable b = null;
+ WeakReference<Drawable> wr = mDrawableRef;
+ Drawable d = null;
if (wr != null)
- b = (Drawable) wr.get();
+ d = wr.get();
- if (b == null) {
- b = getDrawable();
- mDrawableRef = new WeakReference(b);
+ if (d == null) {
+ d = getDrawable();
+ mDrawableRef = new WeakReference<Drawable>(d);
}
- return b;
+ return d;
}
- private WeakReference mDrawableRef;
+ private WeakReference<Drawable> mDrawableRef;
}
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index 79ecfbd..d61e888 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -69,7 +69,7 @@ public class Linkify {
public static final int PHONE_NUMBERS = 0x04;
/**
- * Bit field indicating that phone numbers should be matched in methods that
+ * Bit field indicating that street addresses should be matched in methods that
* take an options mask
*/
public static final int MAP_ADDRESSES = 0x08;
@@ -78,8 +78,7 @@ public class Linkify {
* Bit mask indicating that all available patterns should be matched in
* methods that take an options mask
*/
- public static final int ALL = WEB_URLS | EMAIL_ADDRESSES | PHONE_NUMBERS
- | MAP_ADDRESSES;
+ public static final int ALL = WEB_URLS | EMAIL_ADDRESSES | PHONE_NUMBERS | MAP_ADDRESSES;
/**
* Don't treat anything with fewer than this many digits as a
@@ -109,8 +108,7 @@ public class Linkify {
* Filters out URL matches that don't have enough digits to be a
* phone number.
*/
- public static final MatchFilter sPhoneNumberMatchFilter =
- new MatchFilter() {
+ public static final MatchFilter sPhoneNumberMatchFilter = new MatchFilter() {
public final boolean acceptMatch(CharSequence s, int start, int end) {
int digitCount = 0;
@@ -133,8 +131,7 @@ public class Linkify {
* &apos;+1 (919) 555-1212&apos;
* becomes &apos;+19195551212&apos;
*/
- public static final TransformFilter sPhoneNumberTransformFilter =
- new TransformFilter() {
+ public static final TransformFilter sPhoneNumberTransformFilter = new TransformFilter() {
public final String transformUrl(final Matcher match, String url) {
return Regex.digitsAndPlusOnly(match);
}
@@ -300,8 +297,7 @@ public class Linkify {
* prepended to the url of links that do not have
* a scheme specified in the link text
*/
- public static final void addLinks(TextView text, Pattern pattern,
- String scheme) {
+ public static final void addLinks(TextView text, Pattern pattern, String scheme) {
addLinks(text, pattern, scheme, null, null);
}
@@ -341,8 +337,7 @@ public class Linkify {
* prepended to the url of links that do not have
* a scheme specified in the link text
*/
- public static final boolean addLinks(Spannable text, Pattern pattern,
- String scheme) {
+ public static final boolean addLinks(Spannable text, Pattern pattern, String scheme) {
return addLinks(text, pattern, scheme, null, null);
}
@@ -388,8 +383,7 @@ public class Linkify {
return hasMatches;
}
- private static final void applyLink(String url, int start, int end,
- Spannable text) {
+ private static final void applyLink(String url, int start, int end, Spannable text) {
URLSpan span = new URLSpan(url);
text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
@@ -402,13 +396,22 @@ public class Linkify {
}
boolean hasPrefix = false;
+
for (int i = 0; i < prefixes.length; i++) {
if (url.regionMatches(true, 0, prefixes[i], 0,
prefixes[i].length())) {
hasPrefix = true;
+
+ // Fix capitalization if necessary
+ if (!url.regionMatches(false, 0, prefixes[i], 0,
+ prefixes[i].length())) {
+ url = prefixes[i] + url.substring(prefixes[i].length());
+ }
+
break;
}
}
+
if (!hasPrefix) {
url = prefixes[0] + url;
}
@@ -438,30 +441,35 @@ public class Linkify {
}
}
- private static final void gatherMapLinks(ArrayList<LinkSpec> links,
- Spannable s) {
+ private static final void gatherMapLinks(ArrayList<LinkSpec> links, Spannable s) {
String string = s.toString();
String address;
int base = 0;
+
while ((address = WebView.findAddress(string)) != null) {
int start = string.indexOf(address);
+
if (start < 0) {
break;
}
+
LinkSpec spec = new LinkSpec();
int length = address.length();
int end = start + length;
+
spec.start = base + start;
spec.end = base + end;
string = string.substring(end);
base += end;
String encodedAddress = null;
+
try {
encodedAddress = URLEncoder.encode(address,"UTF-8");
} catch (UnsupportedEncodingException e) {
continue;
}
+
spec.url = "geo:0,0?q=" + encodedAddress;
links.add(spec);
}
diff --git a/core/java/android/text/util/Regex.java b/core/java/android/text/util/Regex.java
index 55ad140..4c128ad 100644
--- a/core/java/android/text/util/Regex.java
+++ b/core/java/android/text/util/Regex.java
@@ -65,7 +65,7 @@ public class Regex {
*/
public static final Pattern WEB_URL_PATTERN
= Pattern.compile(
- "((?:(http|https):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
+ "((?:(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
@@ -103,7 +103,9 @@ public class Regex {
+ "(?:\\:\\d{1,5})?)" // plus option port number
+ "(\\/(?:(?:[a-zA-Z0-9\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
+ "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
- + "\\b"); // and finally, a word boundary this is to stop foo.sure from matching as foo.su
+ + "(?:\\b|$)"); // and finally, a word boundary or end of
+ // input. This is to stop foo.sure from
+ // matching as foo.su
public static final Pattern IP_ADDRESS_PATTERN
= Pattern.compile(
@@ -134,10 +136,20 @@ public class Regex {
* might be phone numbers in arbitrary text, not for validating whether
* something is in fact a phone number. It will miss many things that
* are legitimate phone numbers.
+ *
+ * <p> The pattern matches the following:
+ * <ul>
+ * <li>Optionally, a + sign followed immediately by one or more digits. Spaces, dots, or dashes
+ * may follow.
+ * <li>Optionally, sets of digits in parentheses, separated by spaces, dots, or dashes.
+ * <li>A string starting and ending with a digit, containing digits, spaces, dots, and/or dashes.
+ * </ul>
*/
public static final Pattern PHONE_PATTERN
- = Pattern.compile(
- "(?:\\+[0-9]+)|(?:[0-9()][0-9()\\- \\.][0-9()\\- \\.]+[0-9])");
+ = Pattern.compile( // sdd = space, dot, or dash
+ "(\\+[0-9]+[\\- \\.]*)?" // +<digits><sdd>*
+ + "(\\([0-9]+\\)[\\- \\.]*)?" // (<digits>)<sdd>*
+ + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
/**
* Convenience method to take all of the non-null matching groups in a
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 3c4e337..0fc70d5 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -19,6 +19,7 @@ package android.util;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
+import org.apache.harmony.luni.internal.util.ZoneInfoDB;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -28,14 +29,18 @@ import java.util.Date;
import com.android.internal.util.XmlUtils;
+/**
+ * A class containing utility methods related to time zones.
+ */
public class TimeUtils {
+ private static final String TAG = "TimeUtils";
+
/**
* Tries to return a time zone that would have had the specified offset
* and DST value at the specified moment in the specified country.
* Returns null if no suitable zone could be found.
*/
- public static TimeZone getTimeZone(int offset, boolean dst, long when,
- String country) {
+ public static TimeZone getTimeZone(int offset, boolean dst, long when, String country) {
if (country == null) {
return null;
}
@@ -50,18 +55,18 @@ public class TimeUtils {
String currentName = current.getID();
int currentOffset = current.getOffset(when);
boolean currentDst = current.inDaylightTime(d);
-
+
try {
XmlUtils.beginDocument(parser, "timezones");
-
+
while (true) {
XmlUtils.nextElement(parser);
-
+
String element = parser.getName();
if (element == null || !(element.equals("timezone"))) {
break;
}
-
+
String code = parser.getAttributeValue(null, "code");
if (country.equals(code)) {
@@ -95,15 +100,34 @@ public class TimeUtils {
}
}
} catch (XmlPullParserException e) {
- Log.e("TimeUtils",
- "Got exception while getting preferred time zone.", e);
+ Log.e(TAG, "Got exception while getting preferred time zone.", e);
} catch (IOException e) {
- Log.e("TimeUtils",
- "Got exception while getting preferred time zone.", e);
+ Log.e(TAG, "Got exception while getting preferred time zone.", e);
} finally {
parser.close();
}
-
+
return best;
}
+
+ /**
+ * Returns a String indicating the version of the time zone database currently
+ * in use. The format of the string is dependent on the underlying time zone
+ * database implementation, but will typically contain the year in which the database
+ * was updated plus a letter from a to z indicating changes made within that year.
+ *
+ * <p>Time zone database updates should be expected to occur periodically due to
+ * political and legal changes that cannot be anticipated in advance. Therefore,
+ * when computing the UTC time for a future event, applications should be aware that
+ * the results may differ following a time zone database update. This method allows
+ * applications to detect that a database change has occurred, and to recalculate any
+ * cached times accordingly.
+ *
+ * <p>The time zone database may be assumed to change only when the device runtime
+ * is restarted. Therefore, it is not necessary to re-query the database version
+ * during the lifetime of an activity.
+ */
+ public static String getTimeZoneDatabaseVersion() {
+ return ZoneInfoDB.getVersion();
+ }
}
diff --git a/core/java/android/view/ContextMenu.java b/core/java/android/view/ContextMenu.java
index 9bfda40..dd1d7db 100644
--- a/core/java/android/view/ContextMenu.java
+++ b/core/java/android/view/ContextMenu.java
@@ -24,7 +24,7 @@ import android.widget.AdapterView;
* Extension of {@link Menu} for context menus providing functionality to modify
* the header of the context menu.
* <p>
- * Context menus do not support item shortcuts, item icons, and sub menus.
+ * Context menus do not support item shortcuts and item icons.
* <p>
* To show a context menu on long click, most clients will want to call
* {@link Activity#registerForContextMenu} and override
diff --git a/core/java/android/view/Gravity.java b/core/java/android/view/Gravity.java
index ff9ab18..36d8ce6 100644
--- a/core/java/android/view/Gravity.java
+++ b/core/java/android/view/Gravity.java
@@ -23,7 +23,7 @@ import android.graphics.Rect;
*/
public class Gravity
{
- /** Contstant indicating that no gravity has been set **/
+ /** Constant indicating that no gravity has been set **/
public static final int NO_GRAVITY = 0x0000;
/** Raw bit indicating the gravity for an axis has been specified. */
@@ -33,6 +33,9 @@ public class Gravity
public static final int AXIS_PULL_BEFORE = 0x0002;
/** Raw bit controlling how the right/bottom edge is placed. */
public static final int AXIS_PULL_AFTER = 0x0004;
+ /** Raw bit controlling whether the right/bottom edge is clipped to its
+ * container, based on the gravity direction being applied. */
+ public static final int AXIS_CLIP = 0x0008;
/** Bits defining the horizontal axis. */
public static final int AXIS_X_SHIFT = 0;
@@ -66,10 +69,18 @@ public class Gravity
* and horizontal axis, not changing its size. */
public static final int CENTER = CENTER_VERTICAL|CENTER_HORIZONTAL;
- /** Grow the horizontal and vertical size of the obejct if needed so it
+ /** Grow the horizontal and vertical size of the object if needed so it
* completely fills its container. */
public static final int FILL = FILL_VERTICAL|FILL_HORIZONTAL;
+ /** Flag to clip the edges of the object to its container along the
+ * vertical axis. */
+ public static final int CLIP_VERTICAL = AXIS_CLIP<<AXIS_Y_SHIFT;
+
+ /** Flag to clip the edges of the object to its container along the
+ * horizontal axis. */
+ public static final int CLIP_HORIZONTAL = AXIS_CLIP<<AXIS_X_SHIFT;
+
/**
* Binary mask to get the horizontal gravity of a gravity.
*/
@@ -81,6 +92,20 @@ public class Gravity
public static final int VERTICAL_GRAVITY_MASK = (AXIS_SPECIFIED |
AXIS_PULL_BEFORE | AXIS_PULL_AFTER) << AXIS_Y_SHIFT;
+ /** Special constant to enable clipping to an overall display along the
+ * vertical dimension. This is not applied by default by
+ * {@link #apply(int, int, int, Rect, int, int, Rect)}; you must do so
+ * yourself by calling {@link #applyDisplay}.
+ */
+ public static final int DISPLAY_CLIP_VERTICAL = 0x10000000;
+
+ /** Special constant to enable clipping to an overall display along the
+ * horizontal dimension. This is not applied by default by
+ * {@link #apply(int, int, int, Rect, int, int, Rect)}; you must do so
+ * yourself by calling {@link #applyDisplay}.
+ */
+ public static final int DISPLAY_CLIP_HORIZONTAL = 0x01000000;
+
/**
* Apply a gravity constant to an object.
*
@@ -122,27 +147,143 @@ public class Gravity
*/
public static void apply(int gravity, int w, int h, Rect container,
int xAdj, int yAdj, Rect outRect) {
- if ((gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT))
- == ((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT)) {
- outRect.left = container.left;
- outRect.right = container.right;
- } else {
- outRect.left = applyMovement(
- gravity>>AXIS_X_SHIFT, w, container.left, container.right, xAdj);
- outRect.right = outRect.left + w;
+ switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT)) {
+ case 0:
+ outRect.left = container.left
+ + ((container.right - container.left - w)/2) + xAdj;
+ outRect.right = outRect.left + w;
+ if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
+ == (AXIS_CLIP<<AXIS_X_SHIFT)) {
+ if (outRect.left < container.left) {
+ outRect.left = container.left;
+ }
+ if (outRect.right > container.right) {
+ outRect.right = container.right;
+ }
+ }
+ break;
+ case AXIS_PULL_BEFORE<<AXIS_X_SHIFT:
+ outRect.left = container.left + xAdj;
+ outRect.right = outRect.left + w;
+ if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
+ == (AXIS_CLIP<<AXIS_X_SHIFT)) {
+ if (outRect.right > container.right) {
+ outRect.right = container.right;
+ }
+ }
+ break;
+ case AXIS_PULL_AFTER<<AXIS_X_SHIFT:
+ outRect.right = container.right - xAdj;
+ outRect.left = outRect.right - w;
+ if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
+ == (AXIS_CLIP<<AXIS_X_SHIFT)) {
+ if (outRect.left < container.left) {
+ outRect.left = container.left;
+ }
+ }
+ break;
+ default:
+ outRect.left = container.left + xAdj;
+ outRect.right = container.right + xAdj;
+ break;
+ }
+
+ switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_Y_SHIFT)) {
+ case 0:
+ outRect.top = container.top
+ + ((container.bottom - container.top - h)/2) + yAdj;
+ outRect.bottom = outRect.top + h;
+ if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
+ == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
+ if (outRect.top < container.top) {
+ outRect.top = container.top;
+ }
+ if (outRect.bottom > container.bottom) {
+ outRect.bottom = container.bottom;
+ }
+ }
+ break;
+ case AXIS_PULL_BEFORE<<AXIS_Y_SHIFT:
+ outRect.top = container.top + yAdj;
+ outRect.bottom = outRect.top + h;
+ if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
+ == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
+ if (outRect.bottom > container.bottom) {
+ outRect.bottom = container.bottom;
+ }
+ }
+ break;
+ case AXIS_PULL_AFTER<<AXIS_Y_SHIFT:
+ outRect.bottom = container.bottom - yAdj;
+ outRect.top = outRect.bottom - h;
+ if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
+ == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
+ if (outRect.top < container.top) {
+ outRect.top = container.top;
+ }
+ }
+ break;
+ default:
+ outRect.top = container.top + yAdj;
+ outRect.bottom = container.bottom + yAdj;
+ break;
}
+ }
- if ((gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_Y_SHIFT))
- == ((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_Y_SHIFT)) {
- outRect.top = container.top;
- outRect.bottom = container.bottom;
+ /**
+ * Apply addition gravity behavior based on the overall "display" that an
+ * object exists in. This can be used after
+ * {@link #apply(int, int, int, Rect, int, int, Rect)} to place the object
+ * within a visible display. By default this moves or clips the object
+ * to be visible in the display; the gravity flags
+ * {@link #DISPLAY_CLIP_HORIZONTAL} and {@link #DISPLAY_CLIP_VERTICAL}
+ * can be used to change this behavior.
+ *
+ * @param gravity Gravity constants to modify the placement within the
+ * display.
+ * @param display The rectangle of the display in which the object is
+ * being placed.
+ * @param inoutObj Supplies the current object position; returns with it
+ * modified if needed to fit in the display.
+ */
+ public static void applyDisplay(int gravity, Rect display, Rect inoutObj) {
+ if ((gravity&DISPLAY_CLIP_VERTICAL) != 0) {
+ if (inoutObj.top < display.top) inoutObj.top = display.top;
+ if (inoutObj.bottom > display.bottom) inoutObj.bottom = display.bottom;
} else {
- outRect.top = applyMovement(
- gravity>>AXIS_Y_SHIFT, h, container.top, container.bottom, yAdj);
- outRect.bottom = outRect.top + h;
+ int off = 0;
+ if (inoutObj.top < display.top) off = display.top-inoutObj.top;
+ else if (inoutObj.bottom > display.bottom) off = display.bottom-inoutObj.bottom;
+ if (off != 0) {
+ if (inoutObj.height() > (display.bottom-display.top)) {
+ inoutObj.top = display.top;
+ inoutObj.bottom = display.bottom;
+ } else {
+ inoutObj.top += off;
+ inoutObj.bottom += off;
+ }
+ }
+ }
+
+ if ((gravity&DISPLAY_CLIP_HORIZONTAL) != 0) {
+ if (inoutObj.left < display.left) inoutObj.left = display.left;
+ if (inoutObj.right > display.right) inoutObj.right = display.right;
+ } else {
+ int off = 0;
+ if (inoutObj.left < display.left) off = display.left-inoutObj.left;
+ else if (inoutObj.right > display.right) off = display.right-inoutObj.right;
+ if (off != 0) {
+ if (inoutObj.width() > (display.right-display.left)) {
+ inoutObj.left = display.left;
+ inoutObj.right = display.right;
+ } else {
+ inoutObj.left += off;
+ inoutObj.right += off;
+ }
+ }
}
}
-
+
/**
* <p>Indicate whether the supplied gravity has a vertical pull.</p>
*
@@ -162,18 +303,4 @@ public class Gravity
public static boolean isHorizontal(int gravity) {
return gravity > 0 && (gravity & HORIZONTAL_GRAVITY_MASK) != 0;
}
-
- private static int applyMovement(int mode, int size,
- int start, int end, int adj) {
- if ((mode & AXIS_PULL_BEFORE) != 0) {
- return start + adj;
- }
-
- if ((mode & AXIS_PULL_AFTER) != 0) {
- return end - size - adj;
- }
-
- return start + ((end - start - size)/2) + adj;
- }
}
-
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index b4a3067..99d5c0c 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -17,6 +17,7 @@
package android.view;
+import android.graphics.Rect;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -42,7 +43,8 @@ oneway interface IWindow {
*/
void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor);
- void resized(int w, int h, boolean reportDraw);
+ void resized(int w, int h, in Rect coveredInsets, in Rect visibleInsets,
+ boolean reportDraw);
void dispatchKey(in KeyEvent event);
void dispatchPointer(in MotionEvent event, long eventTime);
void dispatchTrackball(in MotionEvent event, long eventTime);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index e6d52e2..d89c7b4 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -17,6 +17,9 @@
package android.view;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+
import android.content.res.Configuration;
import android.view.IApplicationToken;
import android.view.IOnKeyguardExitResult;
@@ -42,8 +45,10 @@ interface IWindowManager
boolean stopViewServer(); // Transaction #2
boolean isViewServerRunning(); // Transaction #3
- IWindowSession openSession(IBinder token);
-
+ IWindowSession openSession(in IInputMethodClient client,
+ in IInputContext inputContext);
+ boolean inputMethodClientHasFocus(IInputMethodClient client);
+
// These can only be called when injecting events to your own window,
// or by holding the INJECT_EVENTS permission.
boolean injectKeyEvent(in KeyEvent ev, boolean sync);
@@ -74,6 +79,8 @@ interface IWindowManager
void moveAppToken(int index, IBinder token);
void moveAppTokensToTop(in List<IBinder> tokens);
void moveAppTokensToBottom(in List<IBinder> tokens);
+ void addWindowToken(IBinder token, int type);
+ void removeWindowToken(IBinder token);
// these require DISABLE_KEYGUARD permission
void disableKeyguard(IBinder token, String tag);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index c2c0b97..7276f17 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -31,7 +31,7 @@ import android.view.Surface;
*/
interface IWindowSession {
int add(IWindow window, in WindowManager.LayoutParams attrs,
- in int viewVisibility, out Rect outCoveredInsets);
+ in int viewVisibility, out Rect outContentInsets);
void remove(IWindow window);
/**
@@ -46,10 +46,22 @@ interface IWindowSession {
* @param requestedWidth The width the window wants to be.
* @param requestedHeight The height the window wants to be.
* @param viewVisibility Window root view's visibility.
- * @param outFrame Object in which is placed the new position/size on
- * screen.
- * @param outCoveredInsets Object in which is placed the insets for the areas covered by
- * system windows (e.g. status bar)
+ * @param insetsPending Set to true if the client will be later giving
+ * internal insets; as a result, the window will not impact other window
+ * layouts until the insets are given.
+ * @param outFrame Rect in which is placed the new position/size on
+ * screen.
+ * @param outContentInsets Rect in which is placed the offsets from
+ * <var>outFrame</var> in which the content of the window should be
+ * placed. This can be used to modify the window layout to ensure its
+ * contents are visible to the user, taking into account system windows
+ * like the status bar or a soft keyboard.
+ * @param outVisibleInsets Rect in which is placed the offsets from
+ * <var>outFrame</var> in which the window is actually completely visible
+ * to the user. This can be used to temporarily scroll the window's
+ * contents to make sure the user can see it. This is different than
+ * <var>outContentInsets</var> in that these insets change transiently,
+ * so complex relayout of the window should not happen based on them.
* @param outSurface Object in which is placed the new display surface.
*
* @return int Result flags: {@link WindowManagerImpl#RELAYOUT_SHOW_FOCUS},
@@ -57,16 +69,41 @@ interface IWindowSession {
*/
int relayout(IWindow window, in WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility,
- out Rect outFrame, out Rect outCoveredInsets, out Surface outSurface);
+ boolean insetsPending, out Rect outFrame, out Rect outContentInsets,
+ out Rect outVisibleInsets, out Surface outSurface);
+ /**
+ * Give the window manager a hint of the part of the window that is
+ * completely transparent, allowing it to work with the surface flinger
+ * to optimize compositing of this part of the window.
+ */
+ void setTransparentRegion(IWindow window, in Region region);
+
+ /**
+ * Tell the window manager about the content and visible insets of the
+ * given window, which can be used to adjust the <var>outContentInsets</var>
+ * and <var>outVisibleInsets</var> values returned by
+ * {@link #relayout relayout()} for windows behind this one.
+ *
+ * @param touchableInsets Controls which part of the window inside of its
+ * frame can receive pointer events, as defined by
+ * {@link android.view.ViewTreeObserver.InternalInsetsInfo}.
+ */
+ void setInsets(IWindow window, int touchableInsets, in Rect contentInsets,
+ in Rect visibleInsets);
+
+ /**
+ * Return the current display size in which the window is being laid out,
+ * accounting for screen decorations around it.
+ */
+ void getDisplayFrame(IWindow window, out Rect outDisplayFrame);
+
void finishDrawing(IWindow window);
void finishKey(IWindow window);
MotionEvent getPendingPointerMove(IWindow window);
MotionEvent getPendingTrackballMove(IWindow window);
- void setTransparentRegion(IWindow window, in Region region);
-
void setInTouchMode(boolean showFocus);
boolean getInTouchMode();
}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 24b0316..1575aad 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -111,15 +111,27 @@ public class KeyEvent implements Parcelable {
public static final int KEYCODE_MENU = 82;
public static final int KEYCODE_NOTIFICATION = 83;
public static final int KEYCODE_SEARCH = 84;
+ public static final int KEYCODE_PLAYPAUSE = 85;
+ public static final int KEYCODE_STOP = 86;
+ public static final int KEYCODE_NEXTSONG = 87;
+ 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;
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
- // include/ui/KeycodeLabels.h
+ // frameworks/base/include/ui/KeycodeLabels.h
// tools/puppet_master/PuppetMaster/nav_keys.py
- // apps/common/res/values/attrs.xml
+ // frameworks/base/core/res/res/values/attrs.xml
// commands/monkey/Monkey.java
// emulator?
-
+
+ /**
+ * @deprecated There are now more than MAX_KEYCODE keycodes.
+ * Use {@link #getMaxKeyCode()} instead.
+ */
+ @Deprecated
public static final int MAX_KEYCODE = 84;
/**
@@ -207,6 +219,18 @@ public class KeyEvent implements Parcelable {
public static final int FLAG_WOKE_HERE = 0x1;
/**
+ * This mask is set if the key event was generated by a software keyboard.
+ */
+ public static final int FLAG_SOFT_KEYBOARD = 0x2;
+
+ /**
+ * Returns the maximum keycode.
+ */
+ public static int getMaxKeyCode() {
+ return LAST_KEYCODE;
+ }
+
+ /**
* Get the character that is produced by putting accent on the character
* c.
* For example, getDeadChar('`', 'e') returns &egrave;.
@@ -402,6 +426,24 @@ public class KeyEvent implements Parcelable {
}
/**
+ * Copy an existing key event, modifying its action.
+ *
+ * @param origEvent The existing event to be copied.
+ * @param action The new action code of the event.
+ */
+ public KeyEvent(KeyEvent origEvent, int action) {
+ mDownTime = origEvent.mDownTime;
+ mEventTime = origEvent.mEventTime;
+ mAction = action;
+ mKeyCode = origEvent.mKeyCode;
+ mRepeatCount = origEvent.mRepeatCount;
+ mMetaState = origEvent.mMetaState;
+ mDeviceId = origEvent.mDeviceId;
+ mScancode = origEvent.mScancode;
+ mFlags = origEvent.mFlags;
+ }
+
+ /**
* Don't use in new code, instead explicitly check
* {@link #getAction()}.
*
@@ -432,6 +474,12 @@ public class KeyEvent implements Parcelable {
case KEYCODE_VOLUME_DOWN:
case KEYCODE_POWER:
case KEYCODE_HEADSETHOOK:
+ case KEYCODE_PLAYPAUSE:
+ case KEYCODE_STOP:
+ case KEYCODE_NEXTSONG:
+ case KEYCODE_PREVIOUSSONG:
+ case KEYCODE_REWIND:
+ case KEYCODE_FORWARD:
case KEYCODE_CAMERA:
case KEYCODE_FOCUS:
case KEYCODE_SEARCH:
diff --git a/core/java/android/view/Menu.java b/core/java/android/view/Menu.java
index f2ec076..ca2140b 100644
--- a/core/java/android/view/Menu.java
+++ b/core/java/android/view/Menu.java
@@ -32,8 +32,7 @@ import android.content.Intent;
* <p>
* Different menu types support different features:
* <ol>
- * <li><b>Context menus</b>: Do not support item shortcuts, item icons, and sub
- * menus.
+ * <li><b>Context menus</b>: Do not support item shortcuts and item icons.
* <li><b>Options menus</b>: The <b>icon menus</b> do not support item check
* marks and only show the item's
* {@link MenuItem#setTitleCondensed(CharSequence) condensed title}. The
@@ -380,6 +379,24 @@ public interface Menu {
public int size();
/**
+ * Gets the menu item at the given index.
+ *
+ * @param index The index of the menu item to return.
+ * @return The menu item.
+ * @exception IndexOutOfBoundsException
+ * when {@code index < 0 || >= size()}
+ * @hide pending API council
+ */
+ public MenuItem getItem(int index);
+
+ /**
+ * Closes the menu, if open.
+ *
+ * @hide pending API council
+ */
+ public void close();
+
+ /**
* Execute the menu item action associated with the given shortcut
* character.
*
diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java
index 92cf4af..fcebec5 100644
--- a/core/java/android/view/MenuItem.java
+++ b/core/java/android/view/MenuItem.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.view;
import android.app.Activity;
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index ab05e16..882a079 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -27,10 +27,36 @@ import android.util.Config;
* it is being used for.
*/
public final class MotionEvent implements Parcelable {
+ /**
+ * Constant for {@link #getAction}: A pressed gesture has started, the
+ * motion contains the initial starting location.
+ */
public static final int ACTION_DOWN = 0;
+ /**
+ * Constant for {@link #getAction}: A pressed gesture has finished, the
+ * motion contains the final release location as well as any intermediate
+ * points since the last down or move event.
+ */
public static final int ACTION_UP = 1;
+ /**
+ * Constant for {@link #getAction}: A change has happened during a
+ * press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}).
+ * The motion contains the most recent point, as well as any intermediate
+ * points since the last down or move event.
+ */
public static final int ACTION_MOVE = 2;
+ /**
+ * Constant for {@link #getAction}: The current gesture has been aborted.
+ * You will not receive any more points in it. You should treat this as
+ * an up event, but not perform any action that you normally would.
+ */
public static final int ACTION_CANCEL = 3;
+ /**
+ * Constant for {@link #getAction}: A movement has happened outside of the
+ * normal bounds of the UI element. This does not provide a full gesture,
+ * but only the initial location of the movement/touch.
+ */
+ public static final int ACTION_OUTSIDE = 4;
private static final boolean TRACK_RECYCLED_LOCATION = false;
diff --git a/core/java/android/view/OrientationListener.java b/core/java/android/view/OrientationListener.java
index 0add025..974c2e8 100644
--- a/core/java/android/view/OrientationListener.java
+++ b/core/java/android/view/OrientationListener.java
@@ -34,6 +34,7 @@ public abstract class OrientationListener implements SensorListener {
private SensorManager mSensorManager;
private int mOrientation = ORIENTATION_UNKNOWN;
private boolean mEnabled = false;
+ private int mRate;
/**
* Returned from onOrientationChanged when the device orientation cannot be determined
@@ -50,6 +51,21 @@ public abstract class OrientationListener implements SensorListener {
*/
public OrientationListener(Context context) {
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+ mRate = SensorManager.SENSOR_DELAY_NORMAL;
+ }
+
+ /**
+ * Creates a new OrientationListener.
+ *
+ * @param context for the OrientationListener.
+ * @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 OrientationListener(Context context, int rate) {
+ mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+ mRate = rate;
}
/**
@@ -59,7 +75,7 @@ public abstract class OrientationListener implements SensorListener {
public void enable() {
if (mEnabled == false) {
if (localLOGV) Log.d(TAG, "OrientationListener enabled");
- mSensorManager.registerListener(this, SensorManager.SENSOR_ACCELEROMETER);
+ mSensorManager.registerListener(this, SensorManager.SENSOR_ACCELEROMETER, mRate);
mEnabled = true;
}
}
@@ -106,7 +122,6 @@ public abstract class OrientationListener implements SensorListener {
public void onAccuracyChanged(int sensor, int accuracy) {
// TODO Auto-generated method stub
-
}
/**
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 57689f2..0d9e221 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -82,6 +82,8 @@ public class SurfaceView extends View {
final ArrayList<SurfaceHolder.Callback> mCallbacks
= new ArrayList<SurfaceHolder.Callback>();
+ final int[] mLocation = new int[2];
+
final ReentrantLock mSurfaceLock = new ReentrantLock();
final Surface mSurface = new Surface();
boolean mDrawingStopped = true;
@@ -90,8 +92,9 @@ public class SurfaceView extends View {
= new WindowManager.LayoutParams();
IWindowSession mSession;
MyWindow mWindow;
+ final Rect mVisibleInsets = new Rect();
final Rect mWinFrame = new Rect();
- final Rect mCoveredInsets = new Rect();
+ final Rect mContentInsets = new Rect();
static final int KEEP_SCREEN_ON_MSG = 1;
static final int GET_NEW_SURFACE_MSG = 2;
@@ -309,7 +312,7 @@ public class SurfaceView extends View {
mLayout.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
mLayout.gravity = Gravity.LEFT|Gravity.TOP;
mSession.add(mWindow, mLayout,
- mVisible ? VISIBLE : GONE, mCoveredInsets);
+ mVisible ? VISIBLE : GONE, mContentInsets);
}
if (visibleChanged && (!visible || mNewSurfaceNeeded)) {
@@ -321,8 +324,9 @@ public class SurfaceView extends View {
mSurfaceLock.lock();
mDrawingStopped = !visible;
final int relayoutResult = mSession.relayout(
- mWindow, mLayout, mWidth, mHeight,
- visible ? VISIBLE : GONE, mWinFrame, mCoveredInsets, mSurface);
+ mWindow, mLayout, mWidth, mHeight,
+ visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets,
+ mVisibleInsets, mSurface);
if (localLOGV) Log.i(TAG, "New surface: " + mSurface
+ ", vis=" + visible + ", frame=" + mWinFrame);
mSurfaceFrame.left = 0;
@@ -390,7 +394,8 @@ public class SurfaceView extends View {
}
private class MyWindow extends IWindow.Stub {
- public void resized(int w, int h, boolean reportDraw) {
+ public void resized(int w, int h, Rect coveredInsets,
+ Rect visibleInsets, boolean reportDraw) {
if (localLOGV) Log.v(
"SurfaceView", SurfaceView.this + " got resized: w=" +
w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 30402f8..f948b33 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -38,13 +38,18 @@ import android.os.IBinder;
import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.util.AttributeSet;
import android.util.EventLog;
import android.util.Log;
import android.util.SparseArray;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.animation.Animation;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.EditorInfo;
import android.widget.ScrollBarDrawable;
import com.android.internal.R;
@@ -58,9 +63,9 @@ import java.util.Arrays;
* The <code>View</code> class represents the basic UI building block. 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.
+ * used to create interactive graphical user interfaces.
* </p>
- *
+ *
* <a name="Using"></a>
* <h3>Using Views</h3>
* <p>
@@ -98,7 +103,7 @@ import java.util.Arrays;
* views yourself unless you are actually implementing a
* {@link android.view.ViewGroup}.
* </em></p>
- *
+ *
* <a name="Lifecycle"></a>
* <h3>Implementing a Custom View</h3>
*
@@ -111,7 +116,7 @@ import java.util.Arrays;
* <thead>
* <tr><th>Category</th> <th>Methods</th> <th>Description</th></tr>
* </thead>
- *
+ *
* <tbody>
* <tr>
* <td rowspan="2">Creation</td>
@@ -127,7 +132,7 @@ import java.util.Arrays;
* <td>Called after a view and all of its children has been inflated
* from XML.</td>
* </tr>
- *
+ *
* <tr>
* <td rowspan="3">Layout</td>
* <td><code>{@link #onMeasure}</code></td>
@@ -146,7 +151,7 @@ import java.util.Arrays;
* <td>Called when the size of this view has changed.
* </td>
* </tr>
- *
+ *
* <tr>
* <td>Drawing</td>
* <td><code>{@link #onDraw}</code></td>
@@ -164,31 +169,31 @@ import java.util.Arrays;
* <td><code>{@link #onKeyUp}</code></td>
* <td>Called when a key up event occurs.
* </td>
- * </tr>
+ * </tr>
* <tr>
* <td><code>{@link #onTrackballEvent}</code></td>
* <td>Called when a trackball motion event occurs.
* </td>
- * </tr>
+ * </tr>
* <tr>
* <td><code>{@link #onTouchEvent}</code></td>
* <td>Called when a touch screen motion event occurs.
* </td>
- * </tr>
- *
+ * </tr>
+ *
* <tr>
* <td rowspan="2">Focus</td>
* <td><code>{@link #onFocusChanged}</code></td>
* <td>Called when the view gains or loses focus.
* </td>
* </tr>
- *
+ *
* <tr>
* <td><code>{@link #onWindowFocusChanged}</code></td>
* <td>Called when the window containing the view gains or loses focus.
* </td>
* </tr>
- *
+ *
* <tr>
* <td rowspan="3">Attaching</td>
* <td><code>{@link #onAttachedToWindow()}</code></td>
@@ -200,26 +205,26 @@ import java.util.Arrays;
* <td><code>{@link #onDetachedFromWindow}</code></td>
* <td>Called when the view is detached from its window.
* </td>
- * </tr>
+ * </tr>
*
* <tr>
* <td><code>{@link #onWindowVisibilityChanged}</code></td>
* <td>Called when the visibility of the window containing the view
* has changed.
* </td>
- * </tr>
+ * </tr>
* </tbody>
- *
+ *
* </table>
* </p>
- *
+ *
* <a name="IDs"></a>
* <h3>IDs</h3>
* Views may have an integer id associated with them. These ids are typically
* assigned in the layout XML files, and are used to find specific views within
* the view tree. A common pattern is to:
* <ul>
- * <li>Define a Button in the layout file and assign it a unique ID.
+ * <li>Define a Button in the layout file and assign it a unique ID.
* <pre>
* &lt;Button id="@+id/my_button"
* android:layout_width="wrap_content"
@@ -232,11 +237,11 @@ import java.util.Arrays;
* </pre></li>
* </ul>
* <p>
- * View IDs need not be unique throughout the tree, but it is good practice to
+ * View IDs need not be unique throughout the tree, but it is good practice to
* ensure that they are at least unique within the part of the tree you are
* searching.
* </p>
- *
+ *
* <a name="Position"></a>
* <h3>Position</h3>
* <p>
@@ -264,7 +269,7 @@ import java.util.Arrays;
* is similar to the following computation: <code>getLeft() + getWidth()</code>
* (see <a href="#SizePaddingMargins">Size</a> for more information about the width.)
* </p>
- *
+ *
* <a name="SizePaddingMargins"></a>
* <h3>Size, padding and margins</h3>
* <p>
@@ -286,7 +291,7 @@ import java.util.Arrays;
* dimensions define the actual size of the view on screen, at drawing time and
* after layout. These values may, but do not have to, be different from the
* measured width and height. The width and height can be obtained by calling
- * {@link #getWidth()} and {@link #getHeight()}.
+ * {@link #getWidth()} and {@link #getHeight()}.
* </p>
*
* <p>
@@ -297,7 +302,7 @@ import java.util.Arrays;
* 2 pixels to the right of the left edge. Padding can be set using the
* {@link #setPadding(int, int, int, int)} method and queried by calling
* {@link #getPaddingLeft()}, {@link #getPaddingTop()},
- * {@link #getPaddingRight()} and {@link #getPaddingBottom()}.
+ * {@link #getPaddingRight()} and {@link #getPaddingBottom()}.
* </p>
*
* <p>
@@ -306,7 +311,7 @@ import java.util.Arrays;
* {@link android.view.ViewGroup} and
* {@link android.view.ViewGroup.MarginLayoutParams} for further information.
* </p>
- *
+ *
* <a name="Layout"></a>
* <h3>Layout</h3>
* <p>
@@ -319,7 +324,7 @@ import java.util.Arrays;
* this pass each parent is responsible for positioning all of its children
* using the sizes computed in the measure pass.
* </p>
- *
+ *
* <p>
* When a view's measure() method returns, its {@link #getMeasuredWidth()} and
* {@link #getMeasuredHeight()} values must be set, along with those for all of
@@ -332,7 +337,7 @@ import java.util.Arrays;
* measure() on them again with actual numbers if the sum of all the children's
* unconstrained sizes is too big or too small.
* </p>
- *
+ *
* <p>
* The measure pass uses two classes to communicate dimensions. The
* {@link MeasureSpec} class is used by views to tell their parents how they
@@ -350,7 +355,7 @@ import java.util.Arrays;
* For example, AbsoluteLayout has its own subclass of LayoutParams which adds
* an X and Y value.
* </p>
- *
+ *
* <p>
* MeasureSpecs are used to push requirements down the tree from parent to
* child. A MeasureSpec can be in one of three modes:
@@ -367,13 +372,13 @@ import java.util.Arrays;
* within this size.
* </ul>
* </p>
- *
+ *
* <p>
* To intiate a layout, call {@link #requestLayout}. This method is typically
* called by a view on itself when it believes that is can no longer fit within
* its current bounds.
* </p>
- *
+ *
* <a name="Drawing"></a>
* <h3>Drawing</h3>
* <p>
@@ -381,17 +386,18 @@ import java.util.Arrays;
* intersects the the invalid region. Because the tree is traversed in-order,
* this means that parents will draw before (i.e., behind) their children, with
* siblings drawn in the order they appear in the tree.
+ * If you set a background drawable for a View, then the View will draw it for you
+ * before calling back to its <code>onDraw()</code> method.
* </p>
- *
+ *
* <p>
- * The framework will not draw views that are not in the invalid region, and also
- * will take care of drawing the views background for you.
+ * Note that the framework will not draw views that are not in the invalid region.
* </p>
- *
+ *
* <p>
* To force a view to draw, call {@link #invalidate()}.
* </p>
- *
+ *
* <a name="EventHandlingThreading"></a>
* <h3>Event Handling and Threading</h3>
* <p>
@@ -408,13 +414,13 @@ import java.util.Arrays;
* as appropriate.</li>
* </ol>
* </p>
- *
+ *
* <p><em>Note: The entire view tree is single threaded. You must always be on
* the UI thread when calling any method on any view.</em>
* If you are doing work on other threads and want to update the state of a view
* from that thread, you should use a {@link Handler}.
* </p>
- *
+ *
* <a name="FocusHandling"></a>
* <h3>Focus Handling</h3>
* <p>
@@ -479,7 +485,7 @@ import java.util.Arrays;
* offset as well as mechanisms for drawing scrollbars. See
* {@link #scrollBy(int, int)}, {@link #scrollTo(int, int)} for more details.
* </p>
- *
+ *
* <a name="Tags"></a>
* <h3>Tags</h3>
* <p>
@@ -488,7 +494,7 @@ import java.util.Arrays;
* often used as a convenience to store data related to views in the views
* themselves rather than by putting them in a separate structure.
* </p>
- *
+ *
* <a name="Animation"></a>
* <h3>Animation</h3>
* <p>
@@ -543,7 +549,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* setFlags.
*/
private static final int FOCUSABLE = 0x00000001;
-
+
/**
* Mask for use with setFlags indicating bits used for focus.
*/
@@ -599,7 +605,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* {@hide}
*/
static final int ENABLED_MASK = 0x00000020;
-
+
/**
* This view won't draw. {@link #onDraw} won't be called and further
* optimizations
@@ -615,7 +621,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* {@hide}
*/
static final int DRAW_MASK = 0x00000080;
-
+
/**
* <p>This view doesn't show scrollbars.</p>
* {@hide}
@@ -752,22 +758,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* The scrollbar style to display the scrollbars inside the content area,
- * without increasing the padding. The scrollbars will be overlaid with
+ * without increasing the padding. The scrollbars will be overlaid with
* translucency on the view's content.
*/
public static final int SCROLLBARS_INSIDE_OVERLAY = 0;
-
+
/**
* The scrollbar style to display the scrollbars inside the padded area,
- * increasing the padding of the view. The scrollbars will not overlap the
+ * increasing the padding of the view. The scrollbars will not overlap the
* content area of the view.
*/
public static final int SCROLLBARS_INSIDE_INSET = 0x01000000;
/**
* The scrollbar style to display the scrollbars at the edge of the view,
- * without increasing the padding. The scrollbars will be overlaid with
- * translucency.
+ * without increasing the padding. The scrollbars will be overlaid with
+ * translucency.
*/
public static final int SCROLLBARS_OUTSIDE_OVERLAY = 0x02000000;
@@ -789,7 +795,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* {@hide}
*/
static final int SCROLLBARS_OUTSIDE_MASK = 0x02000000;
-
+
/**
* Mask for scrollbar style.
* {@hide}
@@ -841,7 +847,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Use with {@link #focusSearch}. Move focus down.
*/
public static final int FOCUS_DOWN = 0x00000082;
-
+
/**
* Base View state sets
*/
@@ -850,7 +856,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Indicates the view has no states set. States are used with
* {@link android.graphics.drawable.Drawable} to change the drawing of the
* view depending on its state.
- *
+ *
* @see android.graphics.drawable.Drawable
* @see #getDrawableState()
*/
@@ -1003,7 +1009,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Indicates the view is pressed and its window has the focus.
- *
+ *
* @see #PRESSED_STATE_SET
* @see #WINDOW_FOCUSED_STATE_SET
*/
@@ -1021,7 +1027,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Indicates the view is pressed, selected and its window has the focus.
- *
+ *
* @see #PRESSED_STATE_SET
* @see #SELECTED_STATE_SET
* @see #WINDOW_FOCUSED_STATE_SET
@@ -1040,7 +1046,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Indicates the view is pressed, focused and its window has the focus.
- *
+ *
* @see #PRESSED_STATE_SET
* @see #FOCUSED_STATE_SET
* @see #WINDOW_FOCUSED_STATE_SET
@@ -1050,7 +1056,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Indicates the view is pressed, focused and selected.
- *
+ *
* @see #PRESSED_STATE_SET
* @see #SELECTED_STATE_SET
* @see #FOCUSED_STATE_SET
@@ -1060,7 +1066,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Indicates the view is pressed, focused, selected and its window has the focus.
- *
+ *
* @see #PRESSED_STATE_SET
* @see #FOCUSED_STATE_SET
* @see #SELECTED_STATE_SET
@@ -1071,7 +1077,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Indicates the view is pressed and enabled.
- *
+ *
* @see #PRESSED_STATE_SET
* @see #ENABLED_STATE_SET
*/
@@ -1080,7 +1086,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Indicates the view is pressed, enabled and its window has the focus.
- *
+ *
* @see #PRESSED_STATE_SET
* @see #ENABLED_STATE_SET
* @see #WINDOW_FOCUSED_STATE_SET
@@ -1090,7 +1096,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Indicates the view is pressed, enabled and selected.
- *
+ *
* @see #PRESSED_STATE_SET
* @see #ENABLED_STATE_SET
* @see #SELECTED_STATE_SET
@@ -1101,7 +1107,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Indicates the view is pressed, enabled, selected and its window has the
* focus.
- *
+ *
* @see #PRESSED_STATE_SET
* @see #ENABLED_STATE_SET
* @see #SELECTED_STATE_SET
@@ -1112,18 +1118,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Indicates the view is pressed, enabled and focused.
- *
+ *
* @see #PRESSED_STATE_SET
* @see #ENABLED_STATE_SET
* @see #FOCUSED_STATE_SET
*/
protected static final int[] PRESSED_ENABLED_FOCUSED_STATE_SET =
stateSetUnion(PRESSED_ENABLED_STATE_SET, FOCUSED_STATE_SET);
-
+
/**
* Indicates the view is pressed, enabled, focused and its window has the
* focus.
- *
+ *
* @see #PRESSED_STATE_SET
* @see #ENABLED_STATE_SET
* @see #FOCUSED_STATE_SET
@@ -1134,7 +1140,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Indicates the view is pressed, enabled, focused and selected.
- *
+ *
* @see #PRESSED_STATE_SET
* @see #ENABLED_STATE_SET
* @see #SELECTED_STATE_SET
@@ -1146,7 +1152,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Indicates the view is pressed, enabled, focused, selected and its window
* has the focus.
- *
+ *
* @see #PRESSED_STATE_SET
* @see #ENABLED_STATE_SET
* @see #SELECTED_STATE_SET
@@ -1242,6 +1248,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @hide
*/
protected static final int[] PRESSED_SINGLE_STATE_SET = {R.attr.state_single, R.attr.state_pressed};
+
+ /**
+ * Temporary Rect currently for use in setBackground(). This will probably
+ * be extended in the future to hold our own class with more than just
+ * a Rect. :)
+ */
+ static final ThreadLocal<Rect> sThreadLocal = new ThreadLocal<Rect>();
/**
* The animation currently associated with this view.
@@ -1264,20 +1277,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
protected int mMeasuredHeight;
/**
- * Used to store a pair of coordinates, for instance returned values
- * returned by {@link #getLocationInWindow(int[])}.
- *
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected final int[] mLocation = new int[2];
-
- /**
* The view's identifier.
* {@hide}
*
* @see #setId(int)
- * @see #getId()
+ * @see #getId()
*/
@ViewDebug.ExportedProperty(resolveId = true)
int mID = NO_ID;
@@ -1287,7 +1291,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* {@hide}
*
* @see #setTag(Object)
- * @see #getTag()
+ * @see #getTag()
*/
protected Object mTag;
@@ -1320,7 +1324,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
private static final int LAYOUT_REQUIRED = 0x00002000;
private static final int PRESSED = 0x00004000;
-
+
/** {@hide} */
static final int DRAWING_CACHE_VALID = 0x00008000;
/**
@@ -1329,7 +1333,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* {@hide}
*/
static final int ANIMATION_STARTED = 0x00010000;
-
+
private static final int SAVE_STATE_CALLED = 0x00020000;
/**
@@ -1339,8 +1343,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
static final int ALPHA_SET = 0x00040000;
+ /**
+ * Set by {@link #setScrollContainer(boolean)}.
+ */
+ static final int SCROLL_CONTAINER = 0x00080000;
+
+ /**
+ * Set by {@link #setScrollContainer(boolean)}.
+ */
+ static final int SCROLL_CONTAINER_ADDED = 0x00100000;
+
// Note: flag 0x00000040 is available
-
+
/**
* The parent this view is attached to.
* {@hide}
@@ -1357,10 +1371,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* {@hide}
*/
+ @ViewDebug.ExportedProperty
int mPrivateFlags;
-
+
/**
- * Count of how many windows this view has been attached to.
+ * Count of how many windows this view has been attached to.
*/
int mWindowAttachCount;
@@ -1376,6 +1391,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* The view flags hold various views states.
* {@hide}
*/
+ @ViewDebug.ExportedProperty
int mViewFlags;
/**
@@ -1450,7 +1466,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
@ViewDebug.ExportedProperty
protected int mPaddingBottom;
-
+
/**
* Cache the paddingRight set by the user to append to the scrollbar's size.
*/
@@ -1462,7 +1478,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
@ViewDebug.ExportedProperty
int mUserPaddingBottom;
-
+
private int mOldWidthMeasureSpec = Integer.MIN_VALUE;
private int mOldHeightMeasureSpec = Integer.MIN_VALUE;
@@ -1479,28 +1495,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* {@hide}
*/
protected OnFocusChangeListener mOnFocusChangeListener;
-
+
/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnClickListener mOnClickListener;
-
+
/**
* Listener used to dispatch long click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnLongClickListener mOnLongClickListener;
-
+
/**
* Listener used to build the context menu.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnCreateContextMenuListener mOnCreateContextMenuListener;
-
+
private OnKeyListener mOnKeyListener;
private OnTouchListener mOnTouchListener;
@@ -1519,11 +1535,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
private Bitmap mDrawingCache;
/**
- * Used for local (within a stackframe) calls that need a rect temporarily
- */
- private final Rect mTempRect = new Rect();
-
- /**
* When this view has focus and the next focus is {@link #FOCUS_LEFT},
* the user may specify which view to go to next.
*/
@@ -1542,13 +1553,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
private int mNextFocusUpId = View.NO_ID;
/**
- * When this view has focus and the next focus is {@link #FOCUS_DOWN},
+ * When this view has focus and the next focus is {@link #FOCUS_DOWN},
* the user may specify which view to go to next.
*/
private int mNextFocusDownId = View.NO_ID;
private CheckForLongPress mPendingCheckForLongPress;
-
+
/**
* Whether the long press's action has been invoked. The tap's action is invoked on the
* up event while a long press is invoked as soon as the long press duration is reached, so
@@ -1561,12 +1572,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* The minimum height of the view. We'll try our best to have the height
* of this view to at least this amount.
*/
+ @ViewDebug.ExportedProperty
private int mMinHeight;
-
+
/**
* The minimum width of the view. We'll try our best to have the width
* of this view to at least this amount.
*/
+ @ViewDebug.ExportedProperty
private int mMinWidth;
/**
@@ -1574,7 +1587,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* but should be handled by another view.
*/
private TouchDelegate mTouchDelegate = null;
-
+
/**
* Solid color to use as a background when creating the drawing cache. Enables
* the cache to use 16 bit bitmaps instead of 32 bit.
@@ -1591,7 +1604,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Simple constructor to use when creating a view from code.
- *
+ *
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
*/
@@ -1607,11 +1620,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* that were specified in the XML file. This version uses a default style of
* 0, so the only attribute values applied are those in the Context's Theme
* and the given AttributeSet.
- *
+ *
* <p>
* The method onFinishInflate() will be called after all children have been
* added.
- *
+ *
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
@@ -1629,7 +1642,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* <code>R.attr.buttonStyle</code> for <var>defStyle</var>; this allows
* the theme's button style to modify all of the base view attributes (in
* particular its background) as well as the Button class's attributes.
- *
+ *
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
@@ -1663,7 +1676,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
viewFlagValues |= SOUND_EFFECTS_ENABLED;
- viewFlagMasks |= SOUND_EFFECTS_ENABLED;
+ viewFlagMasks |= SOUND_EFFECTS_ENABLED;
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
@@ -1833,7 +1846,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if (viewFlagMasks != 0) {
setFlags(viewFlagValues, viewFlagMasks);
}
-
+
// Needs to be called after mViewFlags is set
if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) {
recomputePadding();
@@ -1866,7 +1879,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* being inflated from XML. This method is automatically called when the XML
* is inflated.
* </p>
- *
+ *
* @param a the styled attributes set to initialize the fading edges from
*/
protected void initializeFadingEdge(TypedArray a) {
@@ -1875,7 +1888,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
mScrollCache.fadingEdgeLength = a.getDimensionPixelSize(
R.styleable.View_fadingEdgeLength, ViewConfiguration.getFadingEdgeLength());
}
-
+
/**
* Returns the size of the vertical faded edges used to indicate that more
* content in this view is visible.
@@ -1907,7 +1920,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
initScrollCache();
mScrollCache.fadingEdgeLength = length;
}
-
+
/**
* Returns the size of the horizontal faded edges used to indicate that more
* content in this view is visible.
@@ -1978,7 +1991,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* being inflated from XML. This method is automatically called when the XML
* is inflated.
* </p>
- *
+ *
* @param a the styled attributes set to initialize the scrollbars from
*/
protected void initializeScrollbars(TypedArray a) {
@@ -1999,8 +2012,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if (thumb != null) {
mScrollCache.scrollBar.setHorizontalThumbDrawable(thumb);
}
-
- boolean alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawHorizontalTrack,
+
+ boolean alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawHorizontalTrack,
false);
if (alwaysDraw) {
mScrollCache.scrollBar.setAlwaysDrawHorizontalTrack(true);
@@ -2013,13 +2026,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if (thumb != null) {
mScrollCache.scrollBar.setVerticalThumbDrawable(thumb);
}
-
- alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawVerticalTrack,
+
+ alwaysDraw = a.getBoolean(R.styleable.View_scrollbarAlwaysDrawVerticalTrack,
false);
if (alwaysDraw) {
mScrollCache.scrollBar.setAlwaysDrawVerticalTrack(true);
}
-
+
// Re-apply user/background padding so that scrollbar(s) get added
recomputePadding();
}
@@ -2037,16 +2050,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Register a callback to be invoked when focus of this view changed.
- *
+ *
* @param l The callback that will run.
*/
public void setOnFocusChangeListener(OnFocusChangeListener l) {
mOnFocusChangeListener = l;
}
-
+
/**
* Returns the focus-change callback registered for this view.
- *
+ *
* @return The callback, or null if one is not registered.
*/
public OnFocusChangeListener getOnFocusChangeListener() {
@@ -2056,7 +2069,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Register a callback to be invoked when this view is clicked. If this view is not
* clickable, it becomes clickable.
- *
+ *
* @param l The callback that will run
*
* @see #setClickable(boolean)
@@ -2067,11 +2080,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
mOnClickListener = l;
}
-
+
/**
* Register a callback to be invoked when this view is clicked and held. If this view is not
* long clickable, it becomes long clickable.
- *
+ *
* @param l The callback that will run
*
* @see #setLongClickable(boolean)
@@ -2084,9 +2097,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
/**
- * Register a callback to be invoked when the context menu for this view is
+ * Register a callback to be invoked when the context menu for this view is
* being built. If this view is not long clickable, it becomes long clickable.
- *
+ *
* @param l The callback that will run
*
*/
@@ -2096,10 +2109,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
mOnCreateContextMenuListener = l;
}
-
+
/**
* Call this view's OnClickListener, if it is defined.
- *
+ *
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
@@ -2112,11 +2125,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
return false;
}
-
+
/**
* Call this view's OnLongClickListener, if it is defined. Invokes the context menu
* if the OnLongClickListener did not consume the event.
- *
+ *
* @return True there was an assigned OnLongClickListener that was called, false
* otherwise is returned.
*/
@@ -2130,10 +2143,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
return handled;
}
-
+
/**
* Bring up the context menu for this view.
- *
+ *
* @return Whether a context menu was displayed.
*/
public boolean showContextMenu() {
@@ -2155,7 +2168,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
-
+
/**
* Give this view focus. This will cause {@link #onFocusChanged} to be called.
*
@@ -2191,7 +2204,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Request that a rectangle of this view be visible on the screen,
* scrolling if necessary just enough.
*
- * A View should call this if it maintains some notion of which part
+ * <p>A View should call this if it maintains some notion of which part
* of its content is interesting. For example, a text editing view
* should call this when its cursor moves.
*
@@ -2206,11 +2219,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Request that a rectangle of this view be visible on the screen,
* scrolling if necessary just enough.
*
- * A View should call this if it maintains some notion of which part
+ * <p>A View should call this if it maintains some notion of which part
* of its content is interesting. For example, a text editing view
* should call this when its cursor moves.
*
- * When <code>immediate</code> is set to true, scrolling will not be
+ * <p>When <code>immediate</code> is set to true, scrolling will not be
* animated.
*
* @param rectangle The rectangle.
@@ -2221,9 +2234,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
View child = this;
ViewParent parent = mParent;
boolean scrolled = false;
- while (parent instanceof ViewGroup) {
- ViewGroup vgParent = (ViewGroup) parent;
- scrolled |= vgParent.requestChildRectangleOnScreen(child,
+ while (parent != null) {
+ scrolled |= parent.requestChildRectangleOnScreen(child,
rectangle, immediate);
// offset rect so next call has the rectangle in the
@@ -2231,12 +2243,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
rectangle.offset(child.getLeft(), child.getTop());
rectangle.offset(-child.getScrollX(), -child.getScrollY());
+ if (!(parent instanceof View)) {
+ break;
+ }
+
child = (View) parent;
parent = child.getParent();
}
return scrolled;
}
-
+
/**
* Called when this view wants to give up focus. This will cause
* {@link #onFocusChanged} to be called.
@@ -2257,7 +2273,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
refreshDrawableState();
}
}
-
+
/**
* Called to clear the focus of a view that is about to be removed.
* Doesn't call clearChildFocus, which prevents this view from taking
@@ -2271,7 +2287,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
refreshDrawableState();
}
}
-
+
/**
* Called internally by the view system when a new view is getting focus.
* This is what clears the old focus.
@@ -2292,7 +2308,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Returns true if this view has focus iteself, or is the ancestor of the
* view that has focus.
- *
+ *
* @return True if this view has or contains focus, false otherwise.
*/
@ViewDebug.ExportedProperty
@@ -2315,17 +2331,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public boolean hasFocusable() {
return (mViewFlags & VISIBILITY_MASK) == VISIBLE && isFocusable();
}
-
+
/**
* Called by the view system when the focus state of this view changes.
* When the focus change event is caused by directional navigation, direction
* and previouslyFocusedRect provide insight into where the focus is coming from.
+ * When overriding, be sure to call up through to the super class so that
+ * the standard focus handling will occur.
*
* @param gainFocus True if the View has focus; false otherwise.
- * @param direction The direction focus has moved when requestFocus()
- * is called to give this view focus. Values are
- * View.FOCUS_UP, View.FOCUS_DOWN, View.FOCUS_LEFT or
- * View.FOCUS_RIGHT. It may not always apply, in which
+ * @param direction The direction focus has moved when requestFocus()
+ * is called to give this view focus. Values are
+ * View.FOCUS_UP, View.FOCUS_DOWN, View.FOCUS_LEFT or
+ * View.FOCUS_RIGHT. It may not always apply, in which
* case use the default.
* @param previouslyFocusedRect The rectangle, in this view's coordinate
* system, of the previously focused view. If applicable, this will be
@@ -2333,20 +2351,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* from (in addition to direction). Will be <code>null</code> otherwise.
*/
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
if (!gainFocus) {
if (isPressed()) {
setPressed(false);
}
+ if (imm != null && mAttachInfo != null
+ && mAttachInfo.mHasWindowFocus) {
+ imm.focusOut(this);
+ }
+ } else if (imm != null && mAttachInfo != null
+ && mAttachInfo.mHasWindowFocus) {
+ imm.focusIn(this);
}
+
invalidate();
if (mOnFocusChangeListener != null) {
mOnFocusChangeListener.onFocusChange(this, gainFocus);
}
}
-
+
/**
* Returns true if this view has focus
- *
+ *
* @return True if this view has focus, false otherwise.
*/
@ViewDebug.ExportedProperty
@@ -2357,7 +2384,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Find the view in the hierarchy rooted at this view that currently has
* focus.
- *
+ *
* @return The view that currently has focus, or null if no focused view can
* be found.
*/
@@ -2366,6 +2393,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
/**
+ * Change whether this view is one of the set of scrollable containers in
+ * its window. This will be used to determine whether the window can
+ * resize or must pan when a soft input area is open -- scrollable
+ * containers allow the window to use resize mode since the container
+ * will appropriately shrink.
+ */
+ public void setScrollContainer(boolean isScrollContainer) {
+ if (isScrollContainer) {
+ if (mAttachInfo != null && (mPrivateFlags&SCROLL_CONTAINER_ADDED) == 0) {
+ mAttachInfo.mScrollContainers.add(this);
+ mPrivateFlags |= SCROLL_CONTAINER_ADDED;
+ }
+ mPrivateFlags |= SCROLL_CONTAINER;
+ } else {
+ if ((mPrivateFlags&SCROLL_CONTAINER_ADDED) != 0) {
+ mAttachInfo.mScrollContainers.remove(this);
+ }
+ mPrivateFlags &= ~(SCROLL_CONTAINER|SCROLL_CONTAINER_ADDED);
+ }
+ }
+
+ /**
* Returns the quality of the drawing cache.
*
* @return One of {@link #DRAWING_CACHE_QUALITY_AUTO},
@@ -2388,7 +2437,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @param quality One of {@link #DRAWING_CACHE_QUALITY_AUTO},
* {@link #DRAWING_CACHE_QUALITY_LOW}, or {@link #DRAWING_CACHE_QUALITY_HIGH}
*
- * @see #getDrawingCacheQuality()
+ * @see #getDrawingCacheQuality()
* @see #setDrawingCacheEnabled(boolean)
* @see #isDrawingCacheEnabled()
*
@@ -2418,7 +2467,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*
* @param keepScreenOn Supply true to set {@link #KEEP_SCREEN_ON}.
*
- * @see #getKeepScreenOn()
+ * @see #getKeepScreenOn()
*
* @attr ref android.R.styleable#View_keepScreenOn
*/
@@ -2508,10 +2557,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Returns the visibility of this view and all of its ancestors
- *
+ *
* @return True if this view and all of its ancestors are {@link #VISIBLE}
*/
- public boolean isShown() {
+ public boolean isShown() {
View current = this;
//noinspection ConstantConditions
do {
@@ -2522,7 +2571,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if (parent == null) {
return false; // We are not attached to the view root
}
- if (parent instanceof ViewRoot) {
+ if (!(parent instanceof View)) {
return true;
}
current = (View) parent;
@@ -2534,7 +2583,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Apply the insets for system windows to this view, if the FITS_SYSTEM_WINDOWS flag
* is set
- *
+ *
* @param insets Insets for system windows
*
* @return True if this view applied the insets, false otherwise
@@ -2545,14 +2594,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
mPaddingTop = insets.top;
mPaddingRight = insets.right;
mPaddingBottom = insets.bottom;
+ requestLayout();
return true;
}
return false;
}
-
+
/**
* Returns the visibility status for this view.
- *
+ *
* @return One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
* @attr ref android.R.styleable#View_visibility
*/
@@ -2567,7 +2617,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Set the enabled state of this view.
- *
+ *
* @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
* @attr ref android.R.styleable#View_visibility
*/
@@ -2578,40 +2628,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Returns the enabled status for this view. The interpretation of the
* enabled state varies by subclass.
- *
+ *
* @return True if this view is enabled, false otherwise.
*/
@ViewDebug.ExportedProperty
public boolean isEnabled() {
return (mViewFlags & ENABLED_MASK) == ENABLED;
}
-
+
/**
* Set the enabled state of this view. The interpretation of the enabled
* state varies by subclass.
- *
+ *
* @param enabled True if this view is enabled, false otherwise.
*/
public void setEnabled(boolean enabled) {
setFlags(enabled ? ENABLED : DISABLED, ENABLED_MASK);
-
+
/*
* The View most likely has to change its appearance, so refresh
* the drawable state.
*/
refreshDrawableState();
-
+
// Invalidate too, since the default behavior for views is to be
// be drawn at 50% alpha rather than to change the drawable.
invalidate();
}
-
+
/**
* Set whether this view can receive the focus.
*
* Setting this to false will also ensure that this view is not focusable
* in touch mode.
- *
+ *
* @param focusable If true, this view can receive the focus.
*
* @see #setFocusableInTouchMode(boolean)
@@ -2631,7 +2681,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*
* @param focusableInTouchMode If true, this view can receive the focus while
* in touch mode.
- *
+ *
* @see #setFocusable(boolean)
* @attr ref android.R.styleable#View_focusableInTouchMode
*/
@@ -2676,18 +2726,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
/**
- * If this view doesn't do any drawing on its own, set this flag to
+ * 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.
- *
+ *
* Typically, if you override {@link #onDraw} you should clear this flag.
- *
+ *
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
-
+
/**
* Returns whether or not this View draws on its own.
*
@@ -2739,7 +2789,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* is clickable it will change its state to "pressed" on every click.
* Subclasses should set the view clickable to visually react to
* user's clicks.
- *
+ *
* @param clickable true to make the view clickable, false otherwise
*
* @see #isClickable()
@@ -2766,7 +2816,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* clickable it reacts to the user holding down the button for a longer
* duration than a tap. This event can either launch the listener or a
* context menu.
- *
+ *
* @param longClickable true to make the view long clickable, false otherwise
* @see #isLongClickable()
* @attr ref android.R.styleable#View_longClickable
@@ -2777,10 +2827,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Sets the pressed that for this view.
- *
+ *
* @see #isClickable()
* @see #setClickable(boolean)
- *
+ *
* @param pressed Pass true to set the View's internal state to "pressed", or false to reverts
* the View's internal state from a previously set "pressed" state.
*/
@@ -2793,12 +2843,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
refreshDrawableState();
dispatchSetPressed(pressed);
}
-
+
/**
* Dispatch setPressed to all of this View's children.
- *
+ *
* @see #setPressed(boolean)
- *
+ *
* @param pressed The new pressed state
*/
protected void dispatchSetPressed(boolean pressed) {
@@ -2818,7 +2868,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public boolean isPressed() {
return (mPrivateFlags & PRESSED) == PRESSED;
}
-
+
/**
* Indicates whether this view will save its state (that is,
* whether its {@link #onSaveInstanceState} method will be called).
@@ -2839,12 +2889,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* view still must have an id assigned to it (via {@link #setId setId()})
* for its state to be saved. This flag can only disable the
* saving of this view; any child views may still have their state saved.
- *
+ *
* @param enabled Set to false to <em>disable</em> state saving, or true
* (the default) to allow it.
*
* @see #isSaveEnabled()
- * @see #setId(int)
+ * @see #setId(int)
* @see #onSaveInstanceState()
* @attr ref android.R.styleable#View_saveEnabled
*/
@@ -2855,7 +2905,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Returns whether this View is able to take focus.
- *
+ *
* @return True if this view can take focus, or false otherwise.
* @attr ref android.R.styleable#View_focusable
*/
@@ -2880,9 +2930,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Find the nearest view in the specified direction that can take focus.
* This does not actually give focus to that view.
- *
+ *
* @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT
- *
+ *
* @return The nearest focusable in the specified direction, or null if none
* can be found.
*/
@@ -2893,7 +2943,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
return null;
}
}
-
+
/**
* This method is the last chance for the focused view and its ancestors to
* respond to an arrow key. This is called when the focused view did not
@@ -2943,7 +2993,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
return result;
}
-
+
/**
* Find and return all focusable views that are descendants of this view,
* possibly including this view if it is focusable itself.
@@ -2961,7 +3011,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Add any focusable views that are descendants of this view (possibly
* including this view if it is focusable itself) to views. If we are in touch mode,
* only add views that are also focusable in touch mode.
- *
+ *
* @param views Focusable views found so far
* @param direction The direction of the focus
*/
@@ -2972,11 +3022,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
views.add(this);
}
-
+
/**
* Find and return all touchable views that are descendants of this view,
* possibly including this view if it is touchable itself.
- *
+ *
* @return A list of touchable views
*/
public ArrayList<View> getTouchables() {
@@ -2987,13 +3037,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Add any touchable views that are descendants of this view (possibly
- * including this view if it is touchable itself) to views.
- *
+ * including this view if it is touchable itself) to views.
+ *
* @param views Touchable views found so far
*/
public void addTouchables(ArrayList<View> views) {
final int viewFlags = mViewFlags;
-
+
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
&& (viewFlags & ENABLED_MASK) == ENABLED) {
views.add(this);
@@ -3024,14 +3074,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Call this to try to give focus to a specific view or to one of its
* descendants and give it a hint about what direction focus is heading.
- *
+ *
* A view will not actually take focus if it is not focusable ({@link #isFocusable} returns false),
* or if it is focusable and it is not focusable in touch mode ({@link #isFocusableInTouchMode})
* while the device is in touch mode.
*
* See also {@link #focusSearch}, which is what you call to say that you
* have focus, and you want your parent to look for the next one.
- *
+ *
* This is equivalent to calling {@link #requestFocus(int, Rect)} with
* <code>null</code> set for the previously focused rectangle.
*
@@ -3096,11 +3146,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Call this to try to give focus to a specific view or to one of its descendants. This is a
* special variant of {@link #requestFocus() } that will allow views that are not focuable in
* touch mode to request focus when they are touched.
- *
+ *
* @return Whether this view or one of its descendants actually took focus.
- *
+ *
* @see #isInTouchMode()
- *
+ *
*/
public final boolean requestFocusFromTouch() {
// Leave touch mode if we need to
@@ -3115,7 +3165,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
return requestFocus(View.FOCUS_DOWN);
}
-
+
/**
* @return Whether any ancestor of this view blocks descendant focus.
*/
@@ -3131,42 +3181,74 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
return false;
}
-
+
+ /**
+ * 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) {
+ return;
+ }
+ ViewDebug.dumpCapturedView(subTag, v);
+ }
+
+ /**
+ * Dispatch a key event before it is processed by any input method
+ * associated with the view hierarchy. This can be used to intercept
+ * key events in special situations before the IME consumes them; a
+ * typical example would be handling the BACK key to update the application's
+ * UI instead of allowing the IME to see it and close itself.
+ *
+ * @param event The key event to be dispatched.
+ * @return True if the event was handled, false otherwise.
+ */
+ public boolean dispatchKeyEventPreIme(KeyEvent event) {
+ return onKeyPreIme(event.getKeyCode(), event);
+ }
+
/**
* Dispatch a key event to the next view on the focus path. This path runs
* from the top of the view tree down to the currently focused view. If this
* view has focus, it will dispatch to itself. Otherwise it will dispatch
* the next node down the focus path. This method also fires any key
* listeners.
- *
+ *
* @param event The key event to be dispatched.
* @return True if the event was handled, false otherwise.
*/
public boolean dispatchKeyEvent(KeyEvent event) {
// If any attached key listener a first crack at the event.
//noinspection SimplifiableIfStatement
+
+ if (android.util.Config.LOGV) {
+ captureViewInfo("captureViewKeyEvent", this);
+ }
+
if (mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
-
+
return event.dispatch(this);
}
/**
* Dispatches a key shortcut event.
- *
+ *
* @param event The key event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
return onKeyShortcut(event.getKeyCode(), event);
}
-
+
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
- *
+ *
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
@@ -3180,7 +3262,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Pass a trackball motion event down to the focused view.
- *
+ *
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
@@ -3192,7 +3274,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Called when the window containing this view gains or loses window focus.
* ViewGroups should override to route to their children.
- *
+ *
* @param hasFocus True if the window containing this view now has focus,
* false otherwise.
*/
@@ -3206,23 +3288,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* your view and its window must have focus. If a window is displayed
* on top of yours that takes input focus, then your own window will lose
* focus but the view focus will remain unchanged.
- *
+ *
* @param hasWindowFocus True if the window containing this view now has
* focus, false otherwise.
*/
public void onWindowFocusChanged(boolean hasWindowFocus) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
if (!hasWindowFocus) {
if (isPressed()) {
setPressed(false);
}
+ if (imm != null && (mPrivateFlags & FOCUSED) != 0) {
+ imm.focusOut(this);
+ }
+ } else if (imm != null && (mPrivateFlags & FOCUSED) != 0) {
+ imm.focusIn(this);
}
refreshDrawableState();
}
-
+
/**
* Returns true if this view is in a window that currently has window focus.
* Note that this is not the same as the view itself having focus.
- *
+ *
* @return True if this view is in a window that currently has window focus.
*/
public boolean hasWindowFocus() {
@@ -3232,9 +3320,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Dispatch a window visibility change down the view hierarchy.
* ViewGroups should override to route to their children.
- *
+ *
* @param visibility The new visibility of the window.
- *
+ *
* @see #onWindowVisibilityChanged
*/
public void dispatchWindowVisibilityChanged(int visibility) {
@@ -3248,22 +3336,59 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* to the window manager; this does <em>not</em> tell you whether or not
* your window is obscured by other windows on the screen, even if it
* is itself visible.
- *
+ *
* @param visibility The new visibility of the window.
*/
protected void onWindowVisibilityChanged(int visibility) {
}
-
+
/**
* Returns the current visibility of the window this view is attached to
* (either {@link #GONE}, {@link #INVISIBLE}, or {@link #VISIBLE}).
- *
+ *
* @return Returns the current visibility of the view's window.
*/
public int getWindowVisibility() {
return mAttachInfo != null ? mAttachInfo.mWindowVisibility : GONE;
}
-
+
+ /**
+ * Retrieve the overall visible display size in which the window this view is
+ * attached to has been positioned in. This takes into account screen
+ * decorations above the window, for both cases where the window itself
+ * is being position inside of them or the window is being placed under
+ * then and covered insets are used for the window to position its content
+ * inside. In effect, this tells you the available area where content can
+ * be placed and remain visible to users.
+ *
+ * <p>This function requires an IPC back to the window manager to retrieve
+ * the requested information, so should not be used in performance critical
+ * code like drawing.
+ *
+ * @param outRect Filled in with the visible display frame. If the view
+ * is not attached to a window, this is simply the raw display size.
+ */
+ public void getWindowVisibleDisplayFrame(Rect outRect) {
+ if (mAttachInfo != null) {
+ try {
+ mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect);
+ } catch (RemoteException e) {
+ return;
+ }
+ // XXX This is really broken, and probably all needs to be done
+ // in the window manager, and we need to know more about whether
+ // we want the area behind or in front of the IME.
+ final Rect insets = mAttachInfo.mVisibleInsets;
+ outRect.left += insets.left;
+ outRect.top += insets.top;
+ outRect.right -= insets.right;
+ outRect.bottom -= insets.bottom;
+ return;
+ }
+ Display d = WindowManagerImpl.getDefault().getDefaultDisplay();
+ outRect.set(0, 0, d.getWidth(), d.getHeight());
+ }
+
/**
* Private function to aggregate all per-view attributes in to the view
* root.
@@ -3278,7 +3403,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
mAttachInfo.mKeepScreenOn = true;
}
}
-
+
void needGlobalAttributesUpdate(boolean force) {
AttachInfo ai = mAttachInfo;
if (ai != null) {
@@ -3287,7 +3412,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
}
}
-
+
/**
* Returns whether the device is currently in touch mode. Touch mode is entered
* once the user begins interacting with the device by touch, and affects various
@@ -3306,21 +3431,38 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Returns the context the view is running in, through which it can
- * access the current theme, resources, etc.
- *
+ * access the current theme, resources, etc.
+ *
* @return The view's Context.
*/
+ @ViewDebug.CapturedViewProperty
public final Context getContext() {
return mContext;
}
/**
+ * Handle a key event before it is processed by any input method
+ * associated with the view hierarchy. This can be used to intercept
+ * key events in special situations before the IME consumes them; a
+ * typical example would be handling the BACK key to update the application's
+ * UI instead of allowing the IME to see it and close itself.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ * @return If you handled the event, return true. If you want to allow the
+ * event to be handled by the next receiver, return false.
+ */
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /**
* Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
* KeyEvent.Callback.onKeyMultiple()}: perform press of the view
* when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER}
* is released, if the view is enabled and clickable.
- *
- * @param keyCode A key code that represents the button pressed, from
+ *
+ * @param keyCode A key code that represents the button pressed, from
* {@link android.view.KeyEvent}.
* @param event The KeyEvent object that defines the button action.
*/
@@ -3354,8 +3496,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* KeyEvent.Callback.onKeyMultiple()}: perform clicking of the view
* when {@link KeyEvent#KEYCODE_DPAD_CENTER} or
* {@link KeyEvent#KEYCODE_ENTER} is released.
- *
- * @param keyCode A key code that represents the button pressed, from
+ *
+ * @param keyCode A key code that represents the button pressed, from
* {@link android.view.KeyEvent}.
* @param event The KeyEvent object that defines the button action.
*/
@@ -3390,8 +3532,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
* KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
* the event).
- *
- * @param keyCode A key code that represents the button pressed, from
+ *
+ * @param keyCode A key code that represents the button pressed, from
* {@link android.view.KeyEvent}.
* @param repeatCount The number of times the action was made.
* @param event The KeyEvent object that defines the button action.
@@ -3402,7 +3544,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Called when an unhandled key shortcut event occurs.
- *
+ *
* @param keyCode The value in event.getKeyCode().
* @param event Description of the key event.
* @return If you handled the event, return true. If you want to allow the
@@ -3411,11 +3553,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public boolean onKeyShortcut(int keyCode, KeyEvent event) {
return false;
}
-
+
+ /**
+ * Create a new InputConnection for an InputMethod to interact
+ * with the view. The default implementation returns null, since it doesn't
+ * support input methods. You can override this to implement such support.
+ * This is only needed for views that take focus and text input.
+ *
+ * @param outAttrs Fill in with attribute information about the connection.
+ */
+ public InputConnection createInputConnection(EditorInfo outAttrs) {
+ return null;
+ }
+
/**
* Show the context menu for this view. It is not safe to hold on to the
* menu after returning from this method.
- *
+ *
* @param menu The context menu to populate
*/
public void createContextMenu(ContextMenu menu) {
@@ -3517,7 +3671,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
-
+
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
performClick();
@@ -3548,10 +3702,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
-
+
// Be lenient about moving outside of buttons
int slop = ViewConfiguration.getTouchSlop();
- if ((x < 0 - slop) || (x >= getWidth() + slop) ||
+ if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
if ((mPrivateFlags & PRESSED) != 0) {
@@ -3598,7 +3752,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public void setTouchDelegate(TouchDelegate delegate) {
mTouchDelegate = delegate;
}
-
+
/**
* Gets the TouchDelegate for this View.
*/
@@ -3608,7 +3762,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Set flags controlling behavior of this view.
- *
+ *
* @param flags Constant indicating the value which should be set
* @param mask Constant indicating the bit range that should be changed
*/
@@ -3648,7 +3802,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
mPrivateFlags |= DRAWN;
needGlobalAttributesUpdate(true);
-
+
// a view becoming visible is worth notifying the parent
// about in case nothing has focus. even if this specific view
// isn't focusable, it may contain something that is, so let
@@ -3668,6 +3822,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if (((mViewFlags & VISIBILITY_MASK) == GONE) && hasFocus()) {
clearFocus();
}
+ if (mAttachInfo != null) {
+ mAttachInfo.mViewVisibilityChanged = true;
+ }
}
/* Check if the VISIBLE bit has changed */
@@ -3681,6 +3838,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
clearFocus();
}
}
+ if (mAttachInfo != null) {
+ mAttachInfo.mViewVisibilityChanged = true;
+ }
}
if ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
@@ -3712,7 +3872,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
mPrivateFlags &= ~SKIP_DRAW;
mPrivateFlags |= ONLY_DRAWS_BACKGROUND;
} else {
- mPrivateFlags |= SKIP_DRAW;
+ mPrivateFlags |= SKIP_DRAW;
}
} else {
mPrivateFlags &= ~SKIP_DRAW;
@@ -3720,7 +3880,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
requestLayout();
invalidate();
}
-
+
if ((changed & KEEP_SCREEN_ON) != 0) {
if (mParent != null) {
mParent.recomputeViewAttributes(this);
@@ -3740,10 +3900,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* This is called in response to an internal scroll in this view (i.e., the
- * view scrolled its own contents). This is typically as a result of
+ * view scrolled its own contents). This is typically as a result of
* {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been
* called.
- *
+ *
* @param l Current horizontal scroll origin.
* @param t Current vertical scroll origin.
* @param oldl Previous horizontal scroll origin.
@@ -3757,7 +3917,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* This is called during layout when the size of this view has changed. If
* you were just added to the view hierarchy, you're called with the old
* values of 0.
- *
+ *
* @param w Current width of this view.
* @param h Current height of this view.
* @param oldw Old width of this view.
@@ -3765,7 +3925,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
}
-
+
/**
* Called by draw to draw the child views. This may be overridden
* by derived classes to gain control just before its children are drawn
@@ -3778,7 +3938,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Gets the parent of this view. Note that the parent is a
* ViewParent and not necessarily a View.
- *
+ *
* @return Parent of this view.
*/
public final ViewParent getParent() {
@@ -3790,7 +3950,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* the displayed part of your view. You do not need to draw any pixels
* farther left, since those are outside of the frame of your view on
* screen.
- *
+ *
* @return The left edge of the displayed part of your view, in pixels.
*/
public final int getScrollX() {
@@ -3801,7 +3961,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Return the scrolled top position of this view. This is the top edge of
* the displayed part of your view. You do not need to draw any pixels above
* it, since those are outside of the frame of your view on screen.
- *
+ *
* @return The top edge of the displayed part of your view, in pixels.
*/
public final int getScrollY() {
@@ -3810,7 +3970,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Return the width of the your view.
- *
+ *
* @return The width of your view, in pixels.
*/
@ViewDebug.ExportedProperty
@@ -3820,7 +3980,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Return the height of your view.
- *
+ *
* @return The height of your view, in pixels.
*/
@ViewDebug.ExportedProperty
@@ -3832,7 +3992,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Return the visible drawing bounds of your view. Fills in the output
* rectangle with the values from getScrollX(), getScrollY(),
* getWidth(), and getHeight().
- *
+ *
* @param outRect The (scrolled) drawing bounds of the view.
*/
public void getDrawingRect(Rect outRect) {
@@ -3846,69 +4006,73 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* The width of this view as measured in the most recent call to measure().
* This should be used during measurement and layout calculations only. Use
* {@link #getWidth()} to see how wide a view is after layout.
- *
+ *
* @return The measured width of this view.
*/
public final int getMeasuredWidth() {
return mMeasuredWidth;
}
-
+
/**
* The height of this view as measured in the most recent call to measure().
* This should be used during measurement and layout calculations only. Use
* {@link #getHeight()} to see how tall a view is after layout.
- *
+ *
* @return The measured height of this view.
*/
public final int getMeasuredHeight() {
return mMeasuredHeight;
}
-
+
/**
* Top position of this view relative to its parent.
- *
+ *
* @return The top of this view, in pixels.
*/
+ @ViewDebug.CapturedViewProperty
public final int getTop() {
return mTop;
}
-
+
/**
* Bottom position of this view relative to its parent.
- *
+ *
* @return The bottom of this view, in pixels.
*/
+ @ViewDebug.CapturedViewProperty
public final int getBottom() {
return mBottom;
}
-
+
/**
* Left position of this view relative to its parent.
- *
+ *
* @return The left edge of this view, in pixels.
*/
+ @ViewDebug.CapturedViewProperty
public final int getLeft() {
return mLeft;
}
-
+
/**
* Right position of this view relative to its parent.
- *
+ *
* @return The right edge of this view, in pixels.
*/
+ @ViewDebug.CapturedViewProperty
public final int getRight() {
return mRight;
}
/**
* Hit rectangle in parent's coordinates
- *
+ *
* @param outRect The hit rectangle of the view.
*/
public void getHitRect(Rect outRect) {
outRect.set(mLeft, mTop, mRight, mBottom);
}
-
+
/**
* When a view has focus and the user navigates away from it, the next view is searched for
* starting from the rectangle filled in by this method.
@@ -3929,7 +4093,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* coordinates, offset it by -globalOffset (e.g. r.offset(-globalOffset.x,
* -globalOffset.y)) If the view is completely clipped or translated out,
* return false.
- *
+ *
* @param r If true is returned, r holds the global coordinates of the
* visible portion of this view.
* @param globalOffset If true is returned, globalOffset holds the dx,dy
@@ -3962,7 +4126,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
return false;
}
-
+
/**
* Offset this view's vertical location by the specified number of pixels.
*
@@ -3975,18 +4139,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Offset this view's horizontal location by the specified amount of pixels.
- *
+ *
* @param offset the numer of pixels to offset the view by
*/
public void offsetLeftAndRight(int offset) {
mLeft += offset;
mRight += offset;
}
-
+
/**
* Get the LayoutParams associated with this view. All views should have
- * layout parameters. These supply parameters to the <i>parent</i> of this
- * view specifying how it should be arranged. There are many subclasses of
+ * layout parameters. These supply parameters to the <i>parent</i> of this
+ * view specifying how it should be arranged. There are many subclasses of
* ViewGroup.LayoutParams, and these correspond to the different subclasses
* of ViewGroup that are responsible for arranging their children.
* @return The LayoutParams associated with this view
@@ -4012,7 +4176,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
mLayoutParams = params;
requestLayout();
}
-
+
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
@@ -4047,7 +4211,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* visible, {@link #onDraw} will be called at some point in the future.
* This must be called from a UI thread. To call from a non-UI thread, call
* {@link #postInvalidate()}.
- *
+ *
* WARNING: This method is destructive to dirty.
* @param dirty the rectangle representing the bounds of the dirty region
*/
@@ -4058,13 +4222,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
mPrivateFlags &= ~DRAWING_CACHE_VALID;
- ViewParent p = mParent;
- if (p != null) {
+ final ViewParent p = mParent;
+ final AttachInfo ai = mAttachInfo;
+ if (p != null && ai != null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
- mTempRect.set(dirty.left - scrollX, dirty.top - scrollY,
- dirty.right - scrollX, dirty.bottom - scrollY);
- p.invalidateChild(this, mTempRect);
+ final Rect r = ai.mTmpInvalRect;
+ r.set(dirty.left - scrollX, dirty.top - scrollY,
+ dirty.right - scrollX, dirty.bottom - scrollY);
+ mParent.invalidateChild(this, r);
}
}
}
@@ -4087,12 +4253,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
mPrivateFlags &= ~DRAWING_CACHE_VALID;
- ViewParent p = mParent;
- if (p != null && l < r && t < b) {
+ final ViewParent p = mParent;
+ final AttachInfo ai = mAttachInfo;
+ if (p != null && ai != null && l < r && t < b) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
- mTempRect.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY);
- p.invalidateChild(this, mTempRect);
+ final Rect tmpr = ai.mTmpInvalRect;
+ tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY);
+ p.invalidateChild(this, tmpr);
}
}
}
@@ -4109,25 +4277,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
- ViewParent p = mParent;
- if (p != null) {
- mTempRect.set(0, 0, mRight - mLeft, mBottom - mTop);
+ final ViewParent p = mParent;
+ final AttachInfo ai = mAttachInfo;
+ if (p != null && ai != null) {
+ final Rect r = ai.mTmpInvalRect;
+ r.set(0, 0, mRight - mLeft, mBottom - mTop);
// Don't call invalidate -- we don't want to internally scroll
// our own bounds
- p.invalidateChild(this, mTempRect);
+ p.invalidateChild(this, r);
}
}
}
-
+
/**
* @return A handler associated with the thread running the View. This
* handler can be used to pump events in the UI events queue.
*/
- protected Handler getHandler() {
+ public Handler getHandler() {
if (mAttachInfo != null) {
return mAttachInfo.mHandler;
}
- return null;
+ return null;
}
/**
@@ -4260,7 +4430,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Cause an invalidate to happen on a subsequent cycle through the event
* loop. Waits for the specified amount of time.
- *
+ *
* @param delayMilliseconds the duration in milliseconds to delay the
* invalidation by
*/
@@ -4304,7 +4474,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Called by a parent to request that a child update its values for mScrollX
* and mScrollY if necessary. This will typically be done if the child is
* animating a scroll using a {@link android.widget.Scroller Scroller}
- * object.
+ * object.
*/
public void computeScroll() {
}
@@ -4337,7 +4507,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public void setHorizontalFadingEdgeEnabled(boolean horizontalFadingEdgeEnabled) {
if (isHorizontalFadingEdgeEnabled() != horizontalFadingEdgeEnabled) {
if (horizontalFadingEdgeEnabled) {
- initScrollCache();
+ initScrollCache();
}
mViewFlags ^= FADING_EDGE_HORIZONTAL;
@@ -4504,12 +4674,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* inset. When inset, they add to the padding of the view. And the scrollbars
* can be drawn inside the padding area or on the edge of the view. For example,
* if a view has a background drawable and you want to draw the scrollbars
- * inside the padding specified by the drawable, you can use
+ * inside the padding specified by the drawable, you can use
* SCROLLBARS_INSIDE_OVERLAY or SCROLLBARS_INSIDE_INSET. If you want them to
* appear at the edge of the view, ignoring the padding, then you can use
- * SCROLLBARS_OUTSIDE_OVERLAY or SCROLLBARS_OUTSIDE_INSET.</p>
- * @param style the style of the scrollbars. Should be one of
- * SCROLLBARS_INSIDE_OVERLAY, SCROLLBARS_INSIDE_INSET,
+ * SCROLLBARS_OUTSIDE_OVERLAY or SCROLLBARS_OUTSIDE_INSET.</p>
+ * @param style the style of the scrollbars. Should be one of
+ * SCROLLBARS_INSIDE_OVERLAY, SCROLLBARS_INSIDE_INSET,
* SCROLLBARS_OUTSIDE_OVERLAY or SCROLLBARS_OUTSIDE_INSET.
* @see #SCROLLBARS_INSIDE_OVERLAY
* @see #SCROLLBARS_INSIDE_INSET
@@ -4517,12 +4687,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @see #SCROLLBARS_OUTSIDE_INSET
*/
public void setScrollBarStyle(int style) {
- if (style != (mViewFlags & SCROLLBARS_STYLE_MASK)) {
+ if (style != (mViewFlags & SCROLLBARS_STYLE_MASK)) {
mViewFlags = (mViewFlags & ~SCROLLBARS_STYLE_MASK) | (style & SCROLLBARS_STYLE_MASK);
recomputePadding();
}
}
-
+
/**
* <p>Returns the current scrollbar style.</p>
* @return the current scrollbar style
@@ -4534,7 +4704,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public int getScrollBarStyle() {
return mViewFlags & SCROLLBARS_STYLE_MASK;
}
-
+
/**
* <p>Compute the horizontal range that the horizontal scrollbar
* represents.</p>
@@ -4670,10 +4840,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
final ScrollabilityCache cache = mScrollCache;
if (cache != null) {
final int viewFlags = mViewFlags;
-
- final boolean drawHorizontalScrollBar =
+
+ final boolean drawHorizontalScrollBar =
(viewFlags & SCROLLBARS_HORIZONTAL) == SCROLLBARS_HORIZONTAL;
- final boolean drawVerticalScrollBar =
+ final boolean drawVerticalScrollBar =
(viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL;
if (drawVerticalScrollBar || drawHorizontalScrollBar) {
@@ -4728,12 +4898,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
final int scrollY = mScrollY;
final int inside = (viewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
final int top = scrollY + height - size - (mUserPaddingBottom & inside);
-
- final int verticalScrollBarGap =
+
+ final int verticalScrollBarGap =
(viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL ?
getVerticalScrollbarWidth() : 0;
-
- scrollBar.setBounds(scrollX + (mPaddingLeft & inside) + getScrollBarPaddingLeft(), top,
+
+ scrollBar.setBounds(scrollX + (mPaddingLeft & inside) + getScrollBarPaddingLeft(), top,
scrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap, top + size);
scrollBar.setParameters(
computeHorizontalScrollRange(),
@@ -4773,8 +4943,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
// TODO: Deal with RTL languages to position scrollbar on left
final int left = scrollX + width - size - (mUserPaddingRight & inside);
-
- scrollBar.setBounds(left, scrollY + (mPaddingTop & inside),
+
+ scrollBar.setBounds(left, scrollY + (mPaddingTop & inside),
left + size, scrollY + height - (mUserPaddingBottom & inside));
scrollBar.setParameters(
computeVerticalScrollRange(),
@@ -4832,7 +5002,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
removeCallbacks(mPendingCheckForLongPress);
}
}
-
+
/**
* @return The number of times this view has been attached to a window
*/
@@ -4855,7 +5025,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* {@link #getWindowToken}, except if the window this view in is a panel
* window (attached to another containing window), then the token of
* the containing window is returned instead.
- *
+ *
* @return Returns the associated window token, either
* {@link #getWindowToken()} or the containing window's token.
*/
@@ -4892,6 +5062,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
+ if ((mPrivateFlags&SCROLL_CONTAINER) != 0) {
+ mAttachInfo.mScrollContainers.add(this);
+ mPrivateFlags |= SCROLL_CONTAINER_ADDED;
+ }
performCollectViewAttributes(visibility);
onAttachedToWindow();
int vis = info.mWindowVisibility;
@@ -4909,16 +5083,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
onWindowVisibilityChanged(GONE);
}
}
-
+
onDetachedFromWindow();
+ if ((mPrivateFlags&SCROLL_CONTAINER_ADDED) != 0) {
+ mAttachInfo.mScrollContainers.remove(this);
+ mPrivateFlags &= ~SCROLL_CONTAINER_ADDED;
+ }
mAttachInfo = null;
}
/**
* Store this view hierarchy's frozen state into the given container.
- *
+ *
* @param container The SparseArray in which to save the view's state.
- *
+ *
* @see #restoreHierarchyState
* @see #dispatchSaveInstanceState
* @see #onSaveInstanceState
@@ -4931,9 +5109,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Called by {@link #saveHierarchyState} to store the state for this view and its children.
* May be overridden to modify how freezing happens to a view's children; for example, some
* views may want to not store state for their children.
- *
+ *
* @param container The SparseArray in which to save the view's state.
- *
+ *
* @see #dispatchRestoreInstanceState
* @see #saveHierarchyState
* @see #onSaveInstanceState
@@ -4966,7 +5144,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* in a text view (but usually not the text itself since that is stored in a
* content provider or other persistent storage), the currently selected
* item in a list view.
- *
+ *
* @return Returns a Parcelable object containing the view's current dynamic
* state, or null if there is nothing interesting to save. The
* default implementation returns null.
@@ -4982,9 +5160,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Restore this view hierarchy's frozen state from the given container.
- *
+ *
* @param container The SparseArray which holds previously frozen states.
- *
+ *
* @see #saveHierarchyState
* @see #dispatchRestoreInstanceState
* @see #onRestoreInstanceState
@@ -4997,9 +5175,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Called by {@link #restoreHierarchyState} to retrieve the state for this view and its
* children. May be overridden to modify how restoreing happens to a view's children; for
* example, some views may want to not store state for their children.
- *
+ *
* @param container The SparseArray which holds previously saved state.
- *
+ *
* @see #dispatchSaveInstanceState
* @see #restoreHierarchyState
* @see #onRestoreInstanceState
@@ -5024,10 +5202,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Hook allowing a view to re-apply a representation of its internal state that had previously
* been generated by {@link #onSaveInstanceState}. This function will never be called with a
* null state.
- *
+ *
* @param state The frozen state that had previously been returned by
* {@link #onSaveInstanceState}.
- *
+ *
* @see #onSaveInstanceState
* @see #restoreHierarchyState
* @see #dispatchRestoreInstanceState
@@ -5038,7 +5216,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
throw new IllegalArgumentException("Wrong state class -- expecting View State");
}
}
-
+
/**
* <p>Return the time at which the drawing of the view hierarchy started.</p>
*
@@ -5095,7 +5273,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*
* @see #isDrawingCacheEnabled()
* @see #getDrawingCache()
- * @see #buildDrawingCache()
+ * @see #buildDrawingCache()
*/
public void setDrawingCacheEnabled(boolean enabled) {
setFlags(enabled ? DRAWING_CACHE_ENABLED : 0, DRAWING_CACHE_ENABLED);
@@ -5126,7 +5304,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*
* @see #setDrawingCacheEnabled(boolean)
* @see #isDrawingCacheEnabled()
- * @see #buildDrawingCache()
+ * @see #buildDrawingCache()
* @see #destroyDrawingCache()
*/
public Bitmap getDrawingCache() {
@@ -5148,7 +5326,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*
* @see #setDrawingCacheEnabled(boolean)
* @see #buildDrawingCache()
- * @see #getDrawingCache()
+ * @see #getDrawingCache()
*/
public void destroyDrawingCache() {
if (mDrawingCache != null) {
@@ -5156,31 +5334,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
mDrawingCache = null;
}
}
-
+
/**
* Setting a solid background color for the drawing cache's bitmaps will improve
* perfromance and memory usage. Note, though that this should only be used if this
* view will always be drawn on top of a solid color.
- *
+ *
* @param color The background color to use for the drawing cache's bitmap
- *
+ *
* @see #setDrawingCacheEnabled(boolean)
* @see #buildDrawingCache()
- * @see #getDrawingCache()
+ * @see #getDrawingCache()
*/
public void setDrawingCacheBackgroundColor(int color) {
mDrawingCacheBackgroundColor = color;
}
-
+
/**
* @see #setDrawingCacheBackgroundColor(int)
- *
+ *
* @return The background color to used for the drawing cache's bitmap
*/
public int getDrawingCacheBackgroundColor() {
return mDrawingCacheBackgroundColor;
}
-
+
/**
* <p>Forces the drawing cache to be built if the drawing cache is invalid.</p>
*
@@ -5204,7 +5382,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
final int height = mBottom - mTop;
final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;
- final boolean opaque = drawingCacheBackgroundColor != 0 ||
+ final boolean opaque = drawingCacheBackgroundColor != 0 ||
(mBGDrawable != null && mBGDrawable.getOpacity() == PixelFormat.OPAQUE);
if (width <= 0 || height <= 0 ||
@@ -5242,9 +5420,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
quality = Bitmap.Config.RGB_565;
}
- mDrawingCache = bitmap = Bitmap.createBitmap(width, height, quality);
+ // Try to cleanup memory
+ if (mDrawingCache != null) {
+ mDrawingCache.recycle();
+ }
- clear = drawingCacheBackgroundColor != 0;
+ try {
+ mDrawingCache = bitmap = Bitmap.createBitmap(width, height, quality);
+ } 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
+ // view hierarchy
+ mDrawingCache = null;
+ return;
+ }
+
+ clear = drawingCacheBackgroundColor != 0;
}
Canvas canvas;
@@ -5293,7 +5484,103 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
mPrivateFlags |= DRAWING_CACHE_VALID;
}
}
-
+
+ /**
+ * Indicates whether this View is currently in edit mode. A View is usually
+ * in edit mode when displayed within a developer tool. For instance, if
+ * this View is being drawn by a visual user interface builder, this method
+ * should return true.
+ *
+ * Subclasses should check the return value of this method to provide
+ * different behaviors if their normal behavior might interfere with the
+ * host environment. For instance: the class spawns a thread in its
+ * constructor, the drawing code relies on device-specific features, etc.
+ *
+ * This method is usually checked in the drawing code of custom widgets.
+ *
+ * @return True if this View is in edit mode, false otherwise.
+ */
+ public boolean isInEditMode() {
+ return false;
+ }
+
+ /**
+ * If the View draws content inside its padding and enables fading edges,
+ * it needs to support padding offsets. Padding offsets are added to the
+ * fading edges to extend the length of the fade so that it covers pixels
+ * drawn inside the padding.
+ *
+ * Subclasses of this class should override this method if they need
+ * to draw content inside the padding.
+ *
+ * @return True if padding offset must be applied, false otherwise.
+ *
+ * @see #getLeftPaddingOffset()
+ * @see #getRightPaddingOffset()
+ * @see #getTopPaddingOffset()
+ * @see #getBottomPaddingOffset()
+ *
+ * @since CURRENT
+ */
+ protected boolean isPaddingOffsetRequired() {
+ return false;
+ }
+
+ /**
+ * Amount by which to extend the left fading region. Called only when
+ * {@link #isPaddingOffsetRequired()} returns true.
+ *
+ * @return The left padding offset in pixels.
+ *
+ * @see #isPaddingOffsetRequired()
+ *
+ * @since CURRENT
+ */
+ protected int getLeftPaddingOffset() {
+ return 0;
+ }
+
+ /**
+ * Amount by which to extend the right fading region. Called only when
+ * {@link #isPaddingOffsetRequired()} returns true.
+ *
+ * @return The right padding offset in pixels.
+ *
+ * @see #isPaddingOffsetRequired()
+ *
+ * @since CURRENT
+ */
+ protected int getRightPaddingOffset() {
+ return 0;
+ }
+
+ /**
+ * Amount by which to extend the top fading region. Called only when
+ * {@link #isPaddingOffsetRequired()} returns true.
+ *
+ * @return The top padding offset in pixels.
+ *
+ * @see #isPaddingOffsetRequired()
+ *
+ * @since CURRENT
+ */
+ protected int getTopPaddingOffset() {
+ return 0;
+ }
+
+ /**
+ * Amount by which to extend the bottom fading region. Called only when
+ * {@link #isPaddingOffsetRequired()} returns true.
+ *
+ * @return The bottom padding offset in pixels.
+ *
+ * @see #isPaddingOffsetRequired()
+ *
+ * @since CURRENT
+ */
+ protected int getBottomPaddingOffset() {
+ return 0;
+ }
/**
* Manually render this view (and all of its children) to the given Canvas.
@@ -5379,13 +5666,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
float rightFadeStrength = 0.0f;
// Step 2, save the canvas' layers
- final int paddingLeft = mPaddingLeft;
- final int paddingTop = mPaddingTop;
+ int paddingLeft = mPaddingLeft;
+ int paddingTop = mPaddingTop;
- final int left = mScrollX + paddingLeft;
- final int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
- final int top = mScrollY + paddingTop;
- final int bottom = top + mBottom - mTop - mPaddingBottom - paddingTop;
+ final boolean offsetRequired = isPaddingOffsetRequired();
+ if (offsetRequired) {
+ paddingLeft += getLeftPaddingOffset();
+ paddingTop += getTopPaddingOffset();
+ }
+
+ int left = mScrollX + paddingLeft;
+ int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
+ int top = mScrollY + paddingTop;
+ int bottom = top + mBottom - mTop - mPaddingBottom - paddingTop;
+
+ if (offsetRequired) {
+ right += getRightPaddingOffset();
+ bottom += getBottomPaddingOffset();
+ }
final ScrollabilityCache scrollabilityCache = mScrollCache;
int length = scrollabilityCache.fadingEdgeLength;
@@ -5416,23 +5714,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
saveCount = canvas.getSaveCount();
-
+
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
-
+
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
-
+
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
-
+
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
-
+
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
@@ -5495,10 +5793,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* and needs to draw fading edges. Returning a non-zero color enables the view system to
* optimize the drawing of the fading edges. If you do return a non-zero color, the alpha
* should be set to 0xFF.
- *
+ *
* @see #setVerticalFadingEdgeEnabled
* @see #setHorizontalFadingEdgeEnabled
- *
+ *
* @return The known solid color background for this view, or 0 if the color may vary
*/
public int getSolidColor() {
@@ -5550,12 +5848,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
private static String printPrivateFlags(int privateFlags) {
String output = "";
int numFlags = 0;
-
+
if ((privateFlags & WANTS_FOCUS) == WANTS_FOCUS) {
output += "WANTS_FOCUS";
numFlags++;
}
-
+
if ((privateFlags & FOCUSED) == FOCUSED) {
if (numFlags > 0) {
output += " ";
@@ -5563,7 +5861,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
output += "FOCUSED";
numFlags++;
}
-
+
if ((privateFlags & SELECTED) == SELECTED) {
if (numFlags > 0) {
output += " ";
@@ -5571,15 +5869,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
output += "SELECTED";
numFlags++;
}
-
+
if ((privateFlags & IS_ROOT_NAMESPACE) == IS_ROOT_NAMESPACE) {
if (numFlags > 0) {
output += " ";
}
output += "IS_ROOT_NAMESPACE";
numFlags++;
- }
-
+ }
+
if ((privateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
if (numFlags > 0) {
output += " ";
@@ -5587,7 +5885,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
output += "HAS_BOUNDS";
numFlags++;
}
-
+
if ((privateFlags & DRAWN) == DRAWN) {
if (numFlags > 0) {
output += " ";
@@ -5611,17 +5909,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Assign a size and position to a view and all of its
* descendants
- *
- * <p>This is the second phase of the layout mechanism.
+ *
+ * <p>This is the second phase of the layout mechanism.
* (The first is measuring). In this phase, each parent calls
* layout on all of its children to position them.
* This is typically done using the child measurements
* that were stored in the measure pass().
- *
+ *
* Derived classes with children should override
* onLayout. In that method, they should
* call layout on each of their their children.
- *
+ *
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
@@ -5639,11 +5937,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
mPrivateFlags &= ~FORCE_LAYOUT;
}
-
+
/**
* Called from layout when this view should
* assign a size and position to each of its children.
- *
+ *
* Derived classes with children should override
* this method and call layout on each of
* their their children.
@@ -5655,12 +5953,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
-
+
/**
* Assign a size and position to this view.
- *
+ *
* This is called from layout.
- *
+ *
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
@@ -5699,7 +5997,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
int newWidth = right - left;
int newHeight = bottom - top;
-
+
if (newWidth != oldWidth || newHeight != oldHeight) {
onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
}
@@ -5725,7 +6023,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Finalize inflating a view from XML. This is called as the last phase
* of inflation, after all child views have been added.
- *
+ *
* <p>Even if the subclass overrides onFinishInflate, they should always be
* sure to call the super method, so that we get called.
*/
@@ -5734,7 +6032,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Returns the resources associated with this view.
- *
+ *
* @return Resources object.
*/
public Resources getResources() {
@@ -5787,9 +6085,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Unschedule any events associated with the given Drawable. This can be
* used when selecting a new Drawable into a view, so that the previous
* one is completely unscheduled.
- *
+ *
* @param who The Drawable to unschedule.
- *
+ *
* @see #drawableStateChanged
*/
public void unscheduleDrawable(Drawable who) {
@@ -5803,17 +6101,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* override this function and return true for any Drawable it is
* displaying. This allows animations for those drawables to be
* scheduled.
- *
+ *
* <p>Be sure to call through to the super class when overriding this
* function.
- *
+ *
* @param who The Drawable to verify. Return true if it is one you are
* displaying, else return the result of calling through to the
* super class.
- *
+ *
* @return boolean If true than the Drawable is being displayed in the
* view; else false and it is not allowed to animate.
- *
+ *
* @see #unscheduleDrawable
* @see #drawableStateChanged
*/
@@ -5824,7 +6122,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* This function is called whenever the state of the view changes in such
* a way that it impacts the state of drawables being shown.
- *
+ *
* <p>Be sure to call through to the superclass when overriding this
* function.
*
@@ -5836,12 +6134,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
d.setState(getDrawableState());
}
}
-
+
/**
* Call this to force a view to update its drawable state. This will cause
* drawableStateChanged to be called on this view. Views that are interested
* in the new state should call getDrawableState.
- *
+ *
* @see #drawableStateChanged
* @see #getDrawableState
*/
@@ -5858,9 +6156,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Return an array of resource IDs of the drawable states representing the
* current state of the view.
- *
+ *
* @return The current drawable state
- *
+ *
* @see Drawable#setState
* @see #drawableStateChanged
* @see #onCreateDrawableState
@@ -5880,14 +6178,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* this view. This is called by the view
* system when the cached Drawable state is determined to be invalid. To
* retrieve the current state, you should use {@link #getDrawableState}.
- *
+ *
* @param extraSpace if non-zero, this is the number of extra entries you
* would like in the returned array in which you can place your own
* states.
- *
+ *
* @return Returns an array holding the current {@link Drawable} state of
* the view.
- *
+ *
* @see #mergeDrawableStates
*/
protected int[] onCreateDrawableState(int extraSpace) {
@@ -5897,22 +6195,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
int[] drawableState;
-
+
int privateFlags = mPrivateFlags;
- boolean isPressed = (privateFlags & PRESSED) != 0;
- int viewStateIndex = (isPressed ? 1 : 0);
+ int viewStateIndex = (((privateFlags & PRESSED) != 0) ? 1 : 0);
- boolean isEnabled = (mViewFlags & ENABLED_MASK) == ENABLED;
- viewStateIndex = (viewStateIndex << 1) + (isEnabled ? 1 : 0);
+ viewStateIndex = (viewStateIndex << 1)
+ + (((mViewFlags & ENABLED_MASK) == ENABLED) ? 1 : 0);
- boolean isFocused = isFocused();
- viewStateIndex = (viewStateIndex << 1) + (isFocused ? 1 : 0);
-
- boolean isSelected = (privateFlags & SELECTED) != 0;
- viewStateIndex = (viewStateIndex << 1) + (isSelected ? 1 : 0);
-
- boolean hasWindowFocus = hasWindowFocus();
+ viewStateIndex = (viewStateIndex << 1) + (isFocused() ? 1 : 0);
+
+ viewStateIndex = (viewStateIndex << 1)
+ + (((privateFlags & SELECTED) != 0) ? 1 : 0);
+
+ final boolean hasWindowFocus = hasWindowFocus();
viewStateIndex = (viewStateIndex << 1) + (hasWindowFocus ? 1 : 0);
drawableState = VIEW_STATE_SETS[viewStateIndex];
@@ -5920,9 +6216,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
//noinspection ConstantIfStatement
if (false) {
Log.i("View", "drawableStateIndex=" + viewStateIndex);
- Log.i("View", toString() + " pressed=" + isPressed
- + " en=" + isEnabled + " fo=" + isFocused
- + " sl=" + isSelected + " wf=" + hasWindowFocus
+ Log.i("View", toString()
+ + " pressed=" + ((privateFlags & PRESSED) != 0)
+ + " en=" + ((mViewFlags & ENABLED_MASK) == ENABLED)
+ + " fo=" + hasFocus()
+ + " sl=" + ((privateFlags & SELECTED) != 0)
+ + " wf=" + hasWindowFocus
+ ": " + Arrays.toString(drawableState));
}
@@ -5940,22 +6239,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
return fullState;
}
-
+
/**
* Merge your own state values in <var>additionalState</var> into the base
* state values <var>baseState</var> that were returned by
* {@link #onCreateDrawableState}.
- *
+ *
* @param baseState The base state values returned by
* {@link #onCreateDrawableState}, which will be modified to also hold your
* own additional state values.
- *
+ *
* @param additionalState The additional state values you would like
* added to <var>baseState</var>; this array is not modified.
- *
+ *
* @return As a convenience, the <var>baseState</var> array you originally
* passed into the function is returned.
- *
+ *
* @see #onCreateDrawableState
*/
protected static int[] mergeDrawableStates(int[] baseState, int[] additionalState) {
@@ -5967,7 +6266,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
System.arraycopy(additionalState, 0, baseState, i + 1, additionalState.length);
return baseState;
}
-
+
/**
* Sets the background color for this view.
* @param color the color of the background
@@ -5975,7 +6274,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public void setBackgroundColor(int color) {
setBackgroundDrawable(new ColorDrawable(color));
}
-
+
/**
* Set the background to a given resource. The resource should refer to
* a Drawable object.
@@ -6002,13 +6301,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* padding. However, when a background is removed, this View's padding isn't
* touched. If setting the padding is desired, please use
* {@link #setPadding(int, int, int, int)}.
- *
+ *
* @param d The Drawable to use as the background, or null to remove the
* background
*/
public void setBackgroundDrawable(Drawable d) {
boolean requestLayout = false;
-
+
mBackgroundResource = 0;
/*
@@ -6021,11 +6320,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
if (d != null) {
- final Rect padding = mTempRect;
+ Rect padding = sThreadLocal.get();
+ if (padding == null) {
+ padding = new Rect();
+ sThreadLocal.set(padding);
+ }
if (d.getPadding(padding)) {
setPadding(padding.left, padding.top, padding.right, padding.bottom);
}
-
+
// Compare the minimum sizes of the old Drawable and the new. If there isn't an old or
// if it has a different minimum size, we should layout again
if (mBGDrawable == null || mBGDrawable.getMinimumHeight() != d.getMinimumHeight() ||
@@ -6048,7 +6351,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
} else {
/* Remove the background */
mBGDrawable = null;
-
+
if ((mPrivateFlags & ONLY_DRAWS_BACKGROUND) != 0) {
/*
* This view ONLY drew the background before and we're removing
@@ -6065,7 +6368,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* padding. This is noted in the Javadocs. Hence, we don't need to
* requestLayout(), the invalidate() below is sufficient.
*/
-
+
// The old background's minimum size could have affected this
// View's layout, so let's requestLayout
requestLayout = true;
@@ -6091,7 +6394,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
// TODO: Deal with RTL languages
return 0;
}
-
+
/*
* Returns the pixels occupied by the vertical scrollbar, if not overlaid
*/
@@ -6114,7 +6417,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
/**
- * Sets the padding. The view may add on the space required to display
+ * Sets the padding. The view may add on the space required to display
* the scrollbars, depending on the style and visibility of the scrollbars.
* So the values returned from {@link #getPaddingLeft}, {@link #getPaddingTop},
* {@link #getPaddingRight} and {@link #getPaddingBottom} may be different
@@ -6132,10 +6435,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
public void setPadding(int left, int top, int right, int bottom) {
boolean changed = false;
-
+
mUserPaddingRight = right;
mUserPaddingBottom = bottom;
-
+
if (mPaddingLeft != left + getScrollBarPaddingLeft()) {
changed = true;
mPaddingLeft = left;
@@ -6216,17 +6519,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
dispatchSetSelected(selected);
}
}
-
+
/**
* Dispatch setSelected to all of this View's children.
- *
+ *
* @see #setSelected(boolean)
- *
+ *
* @param selected The new selected state
*/
protected void dispatchSetSelected(boolean selected) {
}
-
+
/**
* Indicates the selection state of this view.
*
@@ -6265,6 +6568,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @return the topmost view containing this view
*/
public View getRootView() {
+ if (mAttachInfo != null) {
+ final View v = mAttachInfo.mRootView;
+ if (v != null) {
+ return v;
+ }
+ }
+
View parent = this;
while (parent.mParent != null && parent.mParent instanceof View) {
@@ -6305,22 +6615,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
location[0] = mLeft;
location[1] = mTop;
- if (!(mParent instanceof View)) {
- return;
+ ViewParent viewParent = mParent;
+ while (viewParent instanceof View) {
+ final View view = (View)viewParent;
+ location[0] += view.mLeft - view.mScrollX;
+ location[1] += view.mTop - view.mScrollY;
+ viewParent = view.mParent;
}
-
- View parent = (View) mParent;
-
- while (parent != null) {
- location[0] += parent.mLeft - parent.mScrollX;
- location[1] += parent.mTop - parent.mScrollY;
-
- final ViewParent viewParent = parent.mParent;
- if (viewParent != null && viewParent instanceof View) {
- parent = (View) viewParent;
- } else {
- parent = null;
- }
+
+ if (viewParent instanceof ViewRoot) {
+ // *cough*
+ final ViewRoot vr = (ViewRoot)viewParent;
+ location[1] -= vr.mCurScrollY;
}
}
@@ -6380,13 +6686,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Sets the identifier for this view. The identifier does not have to be
* unique in this view's hierarchy. The identifier should be a positive
* number.
- *
+ *
* @see #NO_ID
* @see #getId
* @see #findViewById
*
* @param id a number used to identify the view
- *
+ *
* @attr ref android.R.styleable#View_id
*/
public void setId(int id) {
@@ -6421,11 +6727,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*
* @return a positive integer used to identify the view or {@link #NO_ID}
* if the view has no ID
- *
+ *
* @see #setId
* @see #findViewById
* @attr ref android.R.styleable#View_id
*/
+ @ViewDebug.CapturedViewProperty
public int getId() {
return mID;
}
@@ -6433,7 +6740,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Returns this view's tag.
*
- * @return the Object stored in this view as a tag
+ * @return the Object stored in this view as a tag
*/
@ViewDebug.ExportedProperty
public Object getTag() {
@@ -6473,7 +6780,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
protected void debug(int depth) {
String output = debugIndent(depth - 1);
-
+
output += "+ " + this;
int id = getId();
if (id != -1) {
@@ -6484,18 +6791,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
output += " (tag=" + tag + ")";
}
Log.d(VIEW_LOG_TAG, output);
-
+
if ((mPrivateFlags & FOCUSED) != 0) {
- output = debugIndent(depth) + " FOCUSED";
+ output = debugIndent(depth) + " FOCUSED";
Log.d(VIEW_LOG_TAG, output);
}
-
+
output = debugIndent(depth);
output += "frame={" + mLeft + ", " + mTop + ", " + mRight
+ ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
+ "} ";
Log.d(VIEW_LOG_TAG, output);
-
+
if (mPaddingLeft != 0 || mPaddingTop != 0 || mPaddingRight != 0
|| mPaddingBottom != 0) {
output = debugIndent(depth);
@@ -6503,12 +6810,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
+ ", " + mPaddingRight + ", " + mPaddingBottom + "}";
Log.d(VIEW_LOG_TAG, output);
}
-
+
output = debugIndent(depth);
output += "mMeasureWidth=" + mMeasuredWidth +
" mMeasureHeight=" + mMeasuredHeight;
Log.d(VIEW_LOG_TAG, output);
-
+
output = debugIndent(depth);
if (mLayoutParams == null) {
output += "BAD! no layout params";
@@ -6516,7 +6823,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
output = mLayoutParams.debug(output);
}
Log.d(VIEW_LOG_TAG, output);
-
+
output = debugIndent(depth);
output += "flags={";
output += View.printFlags(mViewFlags);
@@ -6583,8 +6890,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
public void forceLayout() {
mPrivateFlags |= FORCE_LAYOUT;
- }
-
+ }
+
/**
* <p>
* This is called to find out how big a view should be. The parent
@@ -6596,14 +6903,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* {@link #onMeasure(int, int)}, called by this method. Therefore, only
* {@link #onMeasure(int, int)} can and must be overriden by subclasses.
* </p>
- *
- *
+ *
+ *
* @param widthMeasureSpec Horizontal space requirements as imposed by the
* parent
* @param heightMeasureSpec Vertical space requirements as imposed by the
* parent
*
- * @see #onMeasure(int, int)
+ * @see #onMeasure(int, int)
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
@@ -6642,7 +6949,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* should be overriden by subclasses to provide accurate and efficient
* measurement of their contents.
* </p>
- *
+ *
* <p>
* <strong>CONTRACT:</strong> When overriding this method, you
* <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
@@ -6651,35 +6958,35 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* {@link #measure(int, int)}. Calling the superclass'
* {@link #onMeasure(int, int)} is a valid use.
* </p>
- *
+ *
* <p>
* The base class implementation of measure defaults to the background size,
* unless a larger size is allowed by the MeasureSpec. Subclasses should
* override {@link #onMeasure(int, int)} to provide better measurements of
* their content.
* </p>
- *
+ *
* <p>
* If this method is overridden, it is the subclass's responsibility to make
* sure the measured height and width are at least the view's minimum height
* and width ({@link #getSuggestedMinimumHeight()} and
* {@link #getSuggestedMinimumWidth()}).
* </p>
- *
+ *
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
* @param heightMeasureSpec vertical space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
- *
+ *
* @see #getMeasuredWidth()
* @see #getMeasuredHeight()
* @see #setMeasuredDimension(int, int)
* @see #getSuggestedMinimumHeight()
* @see #getSuggestedMinimumWidth()
* @see android.view.View.MeasureSpec#getMode(int)
- * @see android.view.View.MeasureSpec#getSize(int)
+ * @see android.view.View.MeasureSpec#getSize(int)
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
@@ -6704,7 +7011,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Utility to reconcile a desired size with constraints imposed by a MeasureSpec.
* Will take the desired size, unless a different size is imposed by the constraints.
- *
+ *
* @param size How big the view wants to be
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
@@ -6726,12 +7033,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
return result;
}
-
+
/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no contraints. Will get larger if allowed
* by the MeasureSpec.
- *
+ *
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
@@ -6740,7 +7047,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
-
+
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
@@ -6761,19 +7068,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* <p>
* When being used in {@link #onMeasure(int, int)}, the caller should still
* ensure the returned height is within the requirements of the parent.
- *
+ *
* @return The suggested minimum height of the view.
*/
protected int getSuggestedMinimumHeight() {
int suggestedMinHeight = mMinHeight;
-
+
if (mBGDrawable != null) {
final int bgMinHeight = mBGDrawable.getMinimumHeight();
if (suggestedMinHeight < bgMinHeight) {
suggestedMinHeight = bgMinHeight;
}
}
-
+
return suggestedMinHeight;
}
@@ -6785,19 +7092,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* <p>
* When being used in {@link #onMeasure(int, int)}, the caller should still
* ensure the returned width is within the requirements of the parent.
- *
+ *
* @return The suggested minimum width of the view.
*/
protected int getSuggestedMinimumWidth() {
int suggestedMinWidth = mMinWidth;
-
+
if (mBGDrawable != null) {
final int bgMinWidth = mBGDrawable.getMinimumWidth();
if (suggestedMinWidth < bgMinWidth) {
suggestedMinWidth = bgMinWidth;
}
}
-
+
return suggestedMinWidth;
}
@@ -6805,7 +7112,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Sets the minimum height of the view. It is not guaranteed the view will
* be able to achieve this minimum height (for example, if its parent layout
* constrains it with less available height).
- *
+ *
* @param minHeight The minimum height the view will try to be.
*/
public void setMinimumHeight(int minHeight) {
@@ -6816,7 +7123,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Sets the minimum width of the view. It is not guaranteed the view will
* be able to achieve this minimum width (for example, if its parent layout
* constrains it with less available width).
- *
+ *
* @param minWidth The minimum width the view will try to be.
*/
public void setMinimumWidth(int minWidth) {
@@ -6832,10 +7139,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public Animation getAnimation() {
return mCurrentAnimation;
}
-
+
/**
* Start the specified animation now.
- *
+ *
* @param animation the animation to start now
*/
public void startAnimation(Animation animation) {
@@ -6843,23 +7150,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
setAnimation(animation);
invalidate();
}
-
+
/**
* Cancels any animations for this view.
*/
public void clearAnimation() {
mCurrentAnimation = null;
}
-
+
/**
* Sets the next animation to play for this view.
* If you want the animation to play immediately, use
* startAnimation. This method provides allows fine-grained
* control over the start time and invalidation, but you
* must make sure that 1) the animation has a start time set, and
- * 2) the view will be invalidated when the animation is supposed to
+ * 2) the view will be invalidated when the animation is supposed to
* start.
- *
+ *
* @param animation The next animation, or null.
*/
public void setAnimation(Animation animation) {
@@ -6914,25 +7221,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* SurfaceView is always considered transparent, but its children are not,
* therefore all View objects remove themselves from the global transparent
* region (passed as a parameter to this function).
- *
+ *
* @param region The transparent region for this ViewRoot (window).
- *
+ *
* @return Returns true if the effective visibility of the view at this
* point is opaque, regardless of the transparent region; returns false
* if it is possible for underlying windows to be seen behind the view.
- *
+ *
* {@hide}
*/
public boolean gatherTransparentRegion(Region region) {
- if (region != null) {
+ final AttachInfo attachInfo = mAttachInfo;
+ if (region != null && attachInfo != null) {
final int pflags = mPrivateFlags;
if ((pflags & SKIP_DRAW) == 0) {
// The SKIP_DRAW flag IS NOT set, so this view draws. We need to
// remove it from the transparent region.
- getLocationInWindow(mLocation);
- region.op(mLocation[0], mLocation[1],
- mLocation[0] + mRight - mLeft, mLocation[1] + mBottom - mTop,
- Region.Op.DIFFERENCE);
+ final int[] location = attachInfo.mTransparentLocation;
+ getLocationInWindow(location);
+ region.op(location[0], location[1], location[0] + mRight - mLeft,
+ location[1] + mBottom - mTop, Region.Op.DIFFERENCE);
} else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBGDrawable != null) {
// The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable
// exists, so we remove the background drawable's non-transparent
@@ -6952,7 +7260,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*
* 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) {
@@ -6967,7 +7275,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* update a Region being computed for {@link #gatherTransparentRegion} so
* that any non-transparent parts of the Drawable are removed from the
* given transparent region.
- *
+ *
* @param dr The Drawable whose transparency is to be applied to the region.
* @param region A Region holding the current transparency information,
* where any parts of the region that are set are considered to be
@@ -6982,7 +7290,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
final Region r = dr.getTransparentRegion();
final Rect db = dr.getBounds();
- if (r != null) {
+ final AttachInfo attachInfo = mAttachInfo;
+ if (r != null && attachInfo != null) {
final int w = getRight()-getLeft();
final int h = getBottom()-getTop();
if (db.left > 0) {
@@ -7001,8 +7310,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
//Log.i("VIEW", "Drawable bottom " + db.bottom + " < view " + h);
r.op(0, db.bottom, w, h, Region.Op.UNION);
}
- getLocationInWindow(mLocation);
- r.translate(mLocation[0], mLocation[1]);
+ final int[] location = attachInfo.mTransparentLocation;
+ getLocationInWindow(location);
+ r.translate(location[0], location[1]);
region.op(r, Region.Op.INTERSECT);
} else {
region.op(db, Region.Op.DIFFERENCE);
@@ -7177,10 +7487,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
class CheckForLongPress implements Runnable {
-
+
private int mOriginalWindowAttachCount;
-
- public void run() {
+
+ public void run() {
if (isPressed() && (mParent != null) && hasWindowFocus()
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick()) {
@@ -7188,7 +7498,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
}
}
-
+
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
@@ -7279,7 +7589,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Called when the context menu for this view is being built. It is not
* safe to hold onto the menu after this method returns.
- *
+ *
* @param menu The context menu that is being built
* @param v The view for which the context menu is being built
* @param menuInfo Extra information about the item for which the
@@ -7297,12 +7607,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Base class for derived classes that want to save and restore their own
- * state in {@link #onSaveInstanceState}.
+ * state in {@link android.view.View#onSaveInstanceState()}.
*/
public static class BaseSavedState extends AbsSavedState {
/**
* Constructor used when reading from a parcel. Reads the state of the superclass.
- *
+ *
* @param source
*/
public BaseSavedState(Parcel source) {
@@ -7311,7 +7621,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Constructor called by derived classes when creating their SavedState objects
- *
+ *
* @param superState The state of the superclass of this view
*/
public BaseSavedState(Parcelable superState) {
@@ -7340,11 +7650,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
void playSoundEffect(int effectId);
}
- IBinder mWindowToken;
+ final IWindowSession mSession;
+
+ final IWindow mWindow;
+
+ final IBinder mWindowToken;
+
+ final SoundEffectPlayer mSoundEffectPlayer;
+
+ /**
+ * The top view of the hierarchy.
+ */
+ View mRootView;
+
IBinder mPanelParentWindowToken;
Surface mSurface;
- IWindowSession mSession;
- SoundEffectPlayer mSoundEffectPlayer;
/**
* Left position of this view's window
@@ -7357,6 +7677,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
int mWindowTop;
/**
+ * For windows that are full-screen but using insets to layout inside
+ * of the screen decorations, these are the current insets for the
+ * content of the window.
+ */
+ final Rect mContentInsets = new Rect();
+
+ /**
+ * For windows that are full-screen but using insets to layout inside
+ * of the screen decorations, these are the current insets for the
+ * actual visible parts of the window.
+ */
+ final Rect mVisibleInsets = new Rect();
+
+ /**
+ * The internal insets given by this window. This value is
+ * supplied by the client (through
+ * {@link ViewTreeObserver.OnComputeInternalInsetsListener}) and will
+ * be given to the window manager when changed to be used in laying
+ * out windows behind it.
+ */
+ final ViewTreeObserver.InternalInsetsInfo mGivenInternalInsets
+ = new ViewTreeObserver.InternalInsetsInfo();
+
+ /**
+ * All views in the window's hierarchy that serve as scroll containers,
+ * used to determine if the window can be resized or must be panned
+ * to adjust for a soft input area.
+ */
+ final ArrayList<View> mScrollContainers = new ArrayList<View>();
+
+ /**
* Indicates whether the view's window currently has the focus.
*/
boolean mHasWindowFocus;
@@ -7365,7 +7716,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* The current visibility of the window.
*/
int mWindowVisibility;
-
+
/**
* Indicates the time at which drawing started to occur.
*/
@@ -7375,7 +7726,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Indicates whether the view's window is currently in touch mode.
*/
boolean mInTouchMode;
-
+
/**
* Indicates that ViewRoot should trigger a global layout change
* the next time it performs a traversal
@@ -7387,12 +7738,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* recomputed.
*/
boolean mAttributesChanged;
-
+
/**
* Set during a traveral if any views want to keep the screen on.
*/
boolean mKeepScreenOn;
-
+
+ /**
+ * Set if the visibility of any views has changed.
+ */
+ boolean mViewVisibilityChanged;
+
+ /**
+ * Global to the view hierarchy used as a temporary for dealing with
+ * x/y points in the transparent region computations.
+ */
+ final int[] mTransparentLocation = new int[2];
+
+ /**
+ * Global to the view hierarchy used as a temporary for dealing with
+ * x/y points in the ViewGroup.invalidateChild implementation.
+ */
+ final int[] mInvalidateChildLocation = new int[2];
+
/**
* The view tree observer used to dispatch global events like
* layout, pre-draw, touch mode change, etc.
@@ -7422,17 +7790,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
static final int INVALIDATE_RECT_MSG = 0x2;
- AttachInfo(Handler handler) {
- this(handler, null);
- }
-
+ /**
+ * Temporary for use in computing invalidate rectangles while
+ * calling up the hierarchy.
+ */
+ final Rect mTmpInvalRect = new Rect();
+
/**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
* @param handler the events handler the view must use
*/
- AttachInfo(Handler handler, SoundEffectPlayer effectPlayer) {
+ AttachInfo(IWindowSession session, IWindow window,
+ Handler handler, SoundEffectPlayer effectPlayer) {
+ mSession = session;
+ mWindow = window;
+ mWindowToken = window.asBinder();
mHandler = handler;
mSoundEffectPlayer = effectPlayer;
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 38be806..b7110ce 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -46,6 +46,13 @@ public class ViewConfiguration {
private static final int LONG_PRESS_TIMEOUT = 500;
/**
+ * Defines the duration in milliseconds a user needs to hold down the
+ * appropriate button to bring up the global actions dialog (power off,
+ * lock screen, etc).
+ */
+ private static final int GLOBAL_ACTIONS_KEY_TIMEOUT = 500;
+
+ /**
* 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
* considered to be a tap.
@@ -66,14 +73,6 @@ public class ViewConfiguration {
private static final int ZOOM_CONTROLS_TIMEOUT = 3000;
/**
- * Defines the duration in milliseconds a user needs to hold down the
- * appropriate button to bring up the global actions dialog (power off,
- * lock screen, etc).
- */
- private static final int GLOBAL_ACTIONS_KEY_TIMEOUT = 1000;
-
-
- /**
* Inset in pixels to look for touchable content when the user touches the edge of the screen
*/
private static final int EDGE_SLOP = 12;
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 1bf46b4..883c7bd 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -19,6 +19,7 @@ package android.view;
import android.util.Log;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.os.Environment;
import java.io.File;
import java.io.BufferedWriter;
@@ -60,7 +61,19 @@ public class ViewDebug {
* check that this value is set to true as not to affect performance.
*/
public static final boolean TRACE_RECYCLER = false;
-
+
+ /**
+ * The system property of dynamic switch for capturing view information
+ * when it is set, we dump interested fields and methods for the view on focus
+ */
+ static final String SYSTEM_PROPERTY_CAPTURE_VIEW = "debug.captureview";
+
+ /**
+ * The system property of dynamic switch for capturing event information
+ * 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
@@ -143,6 +156,29 @@ public class ViewDebug {
*/
String to();
}
+
+ /**
+ * This annotation can be used to mark fields and methods to be dumped when
+ * the view is captured. Methods with this annotation must have no arguments
+ * and must return <some type of data>.
+ *
+ * @hide pending API Council approval
+ */
+ @Target({ ElementType.FIELD, ElementType.METHOD })
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface CapturedViewProperty {
+ /**
+ * When retrieveReturn is true, we need to retrieve second level methods
+ * e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod()
+ * we will set retrieveReturn = true on the annotation of
+ * myView.getFirstLevelMethod()
+ * @return true if we need the second level methods
+ */
+ boolean retrieveReturn() default false;
+ }
+
+ private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
+ private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
// Maximum delay in ms after which we stop trying to capture a View's drawing
private static final int CAPTURE_TIMEOUT = 4000;
@@ -154,7 +190,7 @@ public class ViewDebug {
private static HashMap<Class<?>, Field[]> sFieldsForClasses;
private static HashMap<Class<?>, Method[]> sMethodsForClasses;
-
+
/**
* Defines the type of hierarhcy trace to output to the hierarchy traces file.
*/
@@ -250,8 +286,8 @@ public class ViewDebug {
/**
* Starts tracing the view recycler of the specified view. The trace is identified by a prefix,
- * used to build the traces files names: <code>/tmp/view-recycler/PREFIX.traces</code> and
- * <code>/tmp/view-recycler/PREFIX.recycler</code>.
+ * used to build the traces files names: <code>/EXTERNAL/view-recycler/PREFIX.traces</code> and
+ * <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>.
*
* Only one view recycler can be traced at the same time. After calling this method, any
* other invocation will result in a <code>IllegalStateException</code> unless
@@ -287,10 +323,10 @@ public class ViewDebug {
/**
* Stops the current view recycer tracing.
*
- * Calling this method creates the file <code>/tmp/view-recycler/PREFIX.traces</code>
+ * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.traces</code>
* containing all the traces (or method calls) relative to the specified view's recycler.
*
- * Calling this method creates the file <code>/tmp/view-recycler/PREFIX.recycler</code>
+ * Calling this method creates the file <code>/EXTERNAL/view-recycler/PREFIX.recycler</code>
* containing all of the views used by the recycler of the view supplied to
* {@link #startRecyclerTracing(String, View)}.
*
@@ -310,7 +346,7 @@ public class ViewDebug {
" stopRecyclerTracing()!");
}
- File recyclerDump = new File("/tmp/view-recycler/");
+ File recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
recyclerDump.mkdirs();
recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".recycler");
@@ -329,7 +365,7 @@ public class ViewDebug {
return;
}
- recyclerDump = new File("/tmp/view-recycler/");
+ recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".traces");
try {
final FileOutputStream file = new FileOutputStream(recyclerDump);
@@ -384,14 +420,14 @@ public class ViewDebug {
/**
* Starts tracing the view hierarchy of the specified view. The trace is identified by a prefix,
- * used to build the traces files names: <code>/tmp/view-hierarchy/PREFIX.traces</code> and
- * <code>/tmp/view-hierarchy/PREFIX.tree</code>.
+ * used to build the traces files names: <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code> and
+ * <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>.
*
* Only one view hierarchy can be traced at the same time. After calling this method, any
* other invocation will result in a <code>IllegalStateException</code> unless
* {@link #stopHierarchyTracing()} is invoked before.
*
- * Calling this method creates the file <code>/tmp/view-hierarchy/PREFIX.traces</code>
+ * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>
* containing all the traces (or method calls) relative to the specified view's hierarchy.
*
* This method will return immediately if TRACE_HIERARCHY is false.
@@ -413,7 +449,7 @@ public class ViewDebug {
" a new trace!");
}
- File hierarchyDump = new File("/tmp/view-hierarchy/");
+ File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
hierarchyDump.mkdirs();
hierarchyDump = new File(hierarchyDump, prefix + ".traces");
@@ -431,10 +467,11 @@ public class ViewDebug {
/**
* Stops the current view hierarchy tracing. This method closes the file
- * <code>/tmp/view-hierarchy/PREFIX.traces</code>.
+ * <code>/EXTERNAL/view-hierarchy/PREFIX.traces</code>.
*
- * Calling this method creates the file <code>/tmp/view-hierarchy/PREFIX.tree</code> containing
- * the view hierarchy of the view supplied to {@link #startHierarchyTracing(String, View)}.
+ * Calling this method creates the file <code>/EXTERNAL/view-hierarchy/PREFIX.tree</code>
+ * containing the view hierarchy of the view supplied to
+ * {@link #startHierarchyTracing(String, View)}.
*
* This method will return immediately if TRACE_HIERARCHY is false.
*
@@ -459,7 +496,7 @@ public class ViewDebug {
}
sHierarchyTraces = null;
- File hierarchyDump = new File("/tmp/view-hierarchy/");
+ File hierarchyDump = new File(Environment.getExternalStorageDirectory(), "view-hierarchy/");
hierarchyDump.mkdirs();
hierarchyDump = new File(hierarchyDump, sHierarchyTracePrefix + ".tree");
@@ -484,7 +521,7 @@ public class ViewDebug {
sHierarhcyRoot = null;
}
-
+
static void dispatchCommand(View view, String command, String parameters,
OutputStream clientStream) throws IOException {
@@ -714,10 +751,10 @@ public class ViewDebug {
final ArrayList<Method> foundMethods = new ArrayList<Method>();
methods = klass.getDeclaredMethods();
-
+
int count = methods.length;
for (int i = 0; i < count; i++) {
- final Method method = methods[i];
+ final Method method = methods[i];
if (method.getParameterTypes().length == 0 &&
method.isAnnotationPresent(ExportedProperty.class) &&
method.getReturnType() != Void.class) {
@@ -746,7 +783,7 @@ public class ViewDebug {
klass = klass.getSuperclass();
} while (klass != Object.class);
}
-
+
private static void exportMethods(Object view, BufferedWriter out, Class<?> klass,
String prefix) throws IOException {
@@ -767,8 +804,12 @@ public class ViewDebug {
final Resources resources = ((View) view).getContext().getResources();
final int id = (Integer) methodValue;
if (id >= 0) {
- methodValue = resources.getResourceTypeName(id) + '/' +
- resources.getResourceEntryName(id);
+ try {
+ methodValue = resources.getResourceTypeName(id) + '/' +
+ resources.getResourceEntryName(id);
+ } catch (Resources.NotFoundException e) {
+ methodValue = "UNKNOWN";
+ }
} else {
methodValue = "NO_ID";
}
@@ -839,8 +880,12 @@ public class ViewDebug {
final Resources resources = ((View) view).getContext().getResources();
final int id = field.getInt(view);
if (id >= 0) {
- fieldValue = resources.getResourceTypeName(id) + '/' +
- resources.getResourceEntryName(id);
+ try {
+ fieldValue = resources.getResourceTypeName(id) + '/' +
+ resources.getResourceEntryName(id);
+ } catch (Resources.NotFoundException e) {
+ fieldValue = "UNKNOWN";
+ }
} else {
fieldValue = "NO_ID";
}
@@ -924,4 +969,160 @@ public class ViewDebug {
}
return true;
}
+
+ private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
+ if (mCapturedViewFieldsForClasses == null) {
+ mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
+ }
+ final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
+
+ Field[] fields = map.get(klass);
+ if (fields != null) {
+ return fields;
+ }
+
+ final ArrayList<Field> foundFields = new ArrayList<Field>();
+ fields = klass.getFields();
+
+ int count = fields.length;
+ for (int i = 0; i < count; i++) {
+ final Field field = fields[i];
+ if (field.isAnnotationPresent(CapturedViewProperty.class)) {
+ field.setAccessible(true);
+ foundFields.add(field);
+ }
+ }
+
+ fields = foundFields.toArray(new Field[foundFields.size()]);
+ map.put(klass, fields);
+
+ return fields;
+ }
+
+ private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
+ if (mCapturedViewMethodsForClasses == null) {
+ mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
+ }
+ final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
+
+ Method[] methods = map.get(klass);
+ if (methods != null) {
+ return methods;
+ }
+
+ final ArrayList<Method> foundMethods = new ArrayList<Method>();
+ methods = klass.getMethods();
+
+ int count = methods.length;
+ for (int i = 0; i < count; i++) {
+ final Method method = methods[i];
+ if (method.getParameterTypes().length == 0 &&
+ method.isAnnotationPresent(CapturedViewProperty.class) &&
+ method.getReturnType() != Void.class) {
+ method.setAccessible(true);
+ foundMethods.add(method);
+ }
+ }
+
+ methods = foundMethods.toArray(new Method[foundMethods.size()]);
+ map.put(klass, methods);
+
+ return methods;
+ }
+
+ private static String capturedViewExportMethods(Object obj, Class<?> klass,
+ String prefix) {
+
+ if (obj == null) {
+ return "null";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ final Method[] methods = capturedViewGetPropertyMethods(klass);
+
+ int count = methods.length;
+ for (int i = 0; i < count; i++) {
+ final Method method = methods[i];
+ try {
+ Object methodValue = method.invoke(obj, (Object[]) null);
+ final Class<?> returnType = method.getReturnType();
+
+ CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
+ if (property.retrieveReturn()) {
+ //we are interested in the second level data only
+ sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
+ } else {
+ sb.append(prefix);
+ sb.append(method.getName());
+ sb.append("()=");
+
+ if (methodValue != null) {
+ final String value = methodValue.toString().replace("\n", "\\n");
+ sb.append(value);
+ } else {
+ sb.append("null");
+ }
+ sb.append("; ");
+ }
+ } catch (IllegalAccessException e) {
+ //Exception IllegalAccess, it is OK here
+ //we simply ignore this method
+ } catch (InvocationTargetException e) {
+ //Exception InvocationTarget, it is OK here
+ //we simply ignore this method
+ }
+ }
+ return sb.toString();
+ }
+
+ private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
+
+ if (obj == null) {
+ return "null";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ final Field[] fields = capturedViewGetPropertyFields(klass);
+
+ int count = fields.length;
+ for (int i = 0; i < count; i++) {
+ final Field field = fields[i];
+ try {
+ Object fieldValue = field.get(obj);
+
+ sb.append(prefix);
+ sb.append(field.getName());
+ sb.append("=");
+
+ if (fieldValue != null) {
+ final String value = fieldValue.toString().replace("\n", "\\n");
+ sb.append(value);
+ } else {
+ sb.append("null");
+ }
+ sb.append(' ');
+ } catch (IllegalAccessException e) {
+ //Exception IllegalAccess, it is OK here
+ //we simply ignore this field
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * dump view info for id based instrument test generation
+ * (and possibly further data analysis). The results are dumped
+ * to the log.
+ * @param tag for log
+ * @param view for dump
+ *
+ * @hide pending API Council approval
+ */
+ public static void dumpCapturedView(String tag, Object view) {
+ Class<?> klass = view.getClass();
+ StringBuilder sb = new StringBuilder(klass.getName() + ": ");
+ sb.append(capturedViewExportFields(view, klass, ""));
+ sb.append(capturedViewExportMethods(view, klass, ""));
+ Log.d(tag, sb.toString());
+ }
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 9063821..e26a19e 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -73,7 +73,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private View mFocused;
// The current transformation to apply on the child being drawn
- private final Transformation mChildTransformation = new Transformation();
+ private Transformation mChildTransformation;
// Target of Motion events
private View mMotionTarget;
@@ -148,9 +148,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} should
* set this flags in {@link #mGroupFlags}.
*
- * This flag needs to be removed until we can add a setter for it. People
- * can't be directly stuffing values in to mGroupFlags!!!
- *
* {@hide}
*/
protected static final int FLAG_SUPPORT_STATIC_TRANSFORMATIONS = 0x800;
@@ -466,27 +463,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Called when a child of this group wants a particular rectangle to be
- * positioned onto the screen. {@link ViewGroup}s overriding this can trust
- * that:
- * <ul>
- * <li>child will be a direct child of this group</li>
- * <li>rectangle will be in the child's coordinates</li>
- * </ul>
- *
- * <p>{@link ViewGroup}s overriding this should uphold the contract:</p>
- * <ul>
- * <li>nothing will change if the rectangle is already visible</li>
- * <li>the view port will be scrolled only just enough to make the
- * rectangle visible</li>
- * <ul>
- *
- * @param child The direct child making the request.
- * @param rectangle The rectangle in the child's coordinates the child
- * wishes to be on the screen.
- * @param immediate True to forbid animated or delayed scrolling,
- * false otherwise
- * @return Whether the group scrolled to handle the operation
+ * {@inheritDoc}
*/
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
return false;
@@ -727,6 +704,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* {@inheritDoc}
*/
@Override
+ public boolean dispatchKeyEventPreIme(KeyEvent event) {
+ if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
+ return super.dispatchKeyEventPreIme(event);
+ } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
+ return mFocused.dispatchKeyEventPreIme(event);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public boolean dispatchKeyEvent(KeyEvent event) {
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
return super.dispatchKeyEvent(event);
@@ -740,6 +730,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* {@inheritDoc}
*/
@Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
+ return super.dispatchKeyShortcutEvent(event);
+ } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
+ return mFocused.dispatchKeyShortcutEvent(event);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public boolean dispatchTrackballEvent(MotionEvent event) {
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
return super.dispatchTrackballEvent(event);
@@ -1314,7 +1317,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final int flags = mGroupFlags;
if ((flags & FLAG_CLEAR_TRANSFORMATION) == FLAG_CLEAR_TRANSFORMATION) {
- mChildTransformation.clear();
+ if (mChildTransformation != null) {
+ mChildTransformation.clear();
+ }
mGroupFlags &= ~FLAG_CLEAR_TRANSFORMATION;
}
@@ -1328,6 +1333,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
child.onAnimationStart();
}
+ if (mChildTransformation == null) {
+ mChildTransformation = new Transformation();
+ }
more = a.getTransformation(drawingTime, mChildTransformation);
transformToApply = mChildTransformation;
@@ -1347,6 +1355,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
} else if ((flags & FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ==
FLAG_SUPPORT_STATIC_TRANSFORMATIONS) {
+ if (mChildTransformation == null) {
+ mChildTransformation = new Transformation();
+ }
final boolean hasTransform = getChildStaticTransformation(child, mChildTransformation);
if (hasTransform) {
final int transformType = mChildTransformation.getTransformationType();
@@ -1507,7 +1518,27 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * When this property is set to true, this ViewGroup supports static transformations on
+ * children; this causes
+ * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} to be
+ * invoked when a child is drawn.
+ *
+ * Any subclass overriding
+ * {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} should
+ * set this property to true.
+ *
+ * @param enabled True to enable static transformations on children, false otherwise.
+ *
+ * @see #FLAG_SUPPORT_STATIC_TRANSFORMATIONS
+ */
+ protected void setStaticTransformationsEnabled(boolean enabled) {
+ setBooleanFlag(FLAG_SUPPORT_STATIC_TRANSFORMATIONS, enabled);
+ }
+
+ /**
* {@inheritDoc}
+ *
+ * @see #setStaticTransformationsEnabled(boolean)
*/
protected boolean getChildStaticTransformation(View child, Transformation t) {
return false;
@@ -1969,7 +2000,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (view.getAnimation() != null) {
addDisappearingView(view);
- } else if (mAttachInfo != null) {
+ } else if (view.mAttachInfo != null) {
view.dispatchDetachedFromWindow();
}
@@ -2107,7 +2138,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (animate && child.getAnimation() != null) {
addDisappearingView(child);
- } else if (mAttachInfo != null) {
+ } else if (child.mAttachInfo != null) {
child.dispatchDetachedFromWindow();
}
@@ -2205,7 +2236,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Detaches all views from theparent. Detaching a view should be temporary and followed
+ * Detaches all views from the parent. Detaching a view should be temporary and followed
* either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
* or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached,
* its parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}.
@@ -2242,13 +2273,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
ViewParent parent = this;
- final int[] location = mLocation;
- location[CHILD_LEFT_INDEX] = child.mLeft;
- location[CHILD_TOP_INDEX] = child.mTop;
-
- do {
- parent = parent.invalidateChildInParent(location, dirty);
- } while (parent != null);
+ final AttachInfo attachInfo = mAttachInfo;
+ if (attachInfo != null) {
+ final int[] location = attachInfo.mInvalidateChildLocation;
+ location[CHILD_LEFT_INDEX] = child.mLeft;
+ location[CHILD_TOP_INDEX] = child.mTop;
+
+ do {
+ parent = parent.invalidateChildInParent(location, dirty);
+ } while (parent != null);
+ }
}
/**
@@ -2917,7 +2951,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (disappearingChildren.contains(view)) {
disappearingChildren.remove(view);
- if (mAttachInfo != null) {
+ if (view.mAttachInfo != null) {
view.dispatchDetachedFromWindow();
}
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 1a5d495..b456c5d 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -182,4 +182,30 @@ public interface ViewParent {
* intercept touch events.
*/
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);
+
+ /**
+ * Called when a child of this group wants a particular rectangle to be
+ * positioned onto the screen. {@link ViewGroup}s overriding this can trust
+ * that:
+ * <ul>
+ * <li>child will be a direct child of this group</li>
+ * <li>rectangle will be in the child's coordinates</li>
+ * </ul>
+ *
+ * <p>{@link ViewGroup}s overriding this should uphold the contract:</p>
+ * <ul>
+ * <li>nothing will change if the rectangle is already visible</li>
+ * <li>the view port will be scrolled only just enough to make the
+ * rectangle visible</li>
+ * <ul>
+ *
+ * @param child The direct child making the request.
+ * @param rectangle The rectangle in the child's coordinates the child
+ * wishes to be on the screen.
+ * @param immediate True to forbid animated or delayed scrolling,
+ * false otherwise
+ * @return Whether the group scrolled to handle the operation
+ */
+ public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
+ boolean immediate);
}
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index ca67404..db0b368 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -16,18 +16,26 @@
package android.view;
+import com.android.internal.view.IInputMethodCallback;
+import com.android.internal.view.IInputMethodSession;
+
import android.graphics.Canvas;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.*;
import android.os.Process;
+import android.os.SystemProperties;
import android.util.AndroidRuntimeException;
import android.util.Config;
import android.util.Log;
import android.util.EventLog;
+import android.util.SparseArray;
import android.view.View.MeasureSpec;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Scroller;
import android.content.pm.PackageManager;
import android.content.Context;
import android.app.ActivityManagerNative;
@@ -51,15 +59,19 @@ import static javax.microedition.khronos.opengles.GL10.*;
* {@hide}
*/
@SuppressWarnings({"EmptyCatchBlock"})
-final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.SoundEffectPlayer {
+public final class ViewRoot extends Handler implements ViewParent,
+ View.AttachInfo.SoundEffectPlayer {
private static final String TAG = "ViewRoot";
private static final boolean DBG = false;
@SuppressWarnings({"ConstantConditionalExpression"})
private static final boolean LOCAL_LOGV = false ? Config.LOGD : Config.LOGV;
/** @noinspection PointlessBooleanExpression*/
private static final boolean DEBUG_DRAW = false || LOCAL_LOGV;
+ private static final boolean DEBUG_LAYOUT = false || LOCAL_LOGV;
+ private static final boolean DEBUG_INPUT_RESIZE = false || LOCAL_LOGV;
private static final boolean DEBUG_ORIENTATION = false || LOCAL_LOGV;
- private static final boolean DEBUG_TRACKBALL = LOCAL_LOGV;
+ private static final boolean DEBUG_TRACKBALL = false || LOCAL_LOGV;
+ private static final boolean DEBUG_IMF = false || LOCAL_LOGV;
private static final boolean WATCH_POINTER = false;
static final boolean PROFILE_DRAWING = false;
@@ -75,85 +87,103 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
*/
static final int MAX_TRACKBALL_DELAY = 250;
- private static long sInstanceCount = 0;
+ static long sInstanceCount = 0;
- private static IWindowSession sWindowSession;
+ static IWindowSession sWindowSession;
- private static final Object mStaticInit = new Object();
- private static boolean mInitialized = false;
+ static final Object mStaticInit = new Object();
+ static boolean mInitialized = false;
static final ThreadLocal<Handler> sUiThreads = new ThreadLocal<Handler>();
static final RunQueue sRunQueue = new RunQueue();
- private long mLastTrackballTime = 0;
- private final TrackballAxis mTrackballAxisX = new TrackballAxis();
- private final TrackballAxis mTrackballAxisY = new TrackballAxis();
+ long mLastTrackballTime = 0;
+ final TrackballAxis mTrackballAxisX = new TrackballAxis();
+ final TrackballAxis mTrackballAxisY = new TrackballAxis();
- private final Thread mThread;
+ final int[] mTmpLocation = new int[2];
+
+ final InputMethodCallback mInputMethodCallback;
+ final SparseArray<Object> mPendingEvents = new SparseArray<Object>();
+ int mPendingEventSeq = 0;
+
+ final Thread mThread;
- private final WindowLeaked mLocation;
+ final WindowLeaked mLocation;
- private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
+ final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
final W mWindow;
- private View mView;
- private View mFocusedView;
- private int mViewVisibility;
- private boolean mAppVisible = true;
+ View mView;
+ View mFocusedView;
+ int mViewVisibility;
+ boolean mAppVisible = true;
- private final Region mTransparentRegion;
- private final Region mPreviousTransparentRegion;
+ final Region mTransparentRegion;
+ final Region mPreviousTransparentRegion;
- private int mWidth;
- private int mHeight;
- private Rect mDirty; // will be a graphics.Region soon
+ int mWidth;
+ int mHeight;
+ Rect mDirty; // will be a graphics.Region soon
- private final View.AttachInfo mAttachInfo;
+ final View.AttachInfo mAttachInfo;
- private final Rect mTempRect; // used in the transaction to not thrash the heap.
+ final Rect mTempRect; // used in the transaction to not thrash the heap.
+ final Rect mVisRect; // used to retrieve visible rect of focused view.
+ final Point mVisPoint; // used to retrieve global offset of focused view.
- private boolean mTraversalScheduled;
- private boolean mWillDrawSoon;
- private boolean mLayoutRequested;
- private boolean mFirst;
- private boolean mReportNextDraw;
- private boolean mFullRedrawNeeded;
- private boolean mNewSurfaceNeeded;
+ boolean mTraversalScheduled;
+ boolean mWillDrawSoon;
+ boolean mLayoutRequested;
+ boolean mFirst;
+ boolean mReportNextDraw;
+ boolean mFullRedrawNeeded;
+ boolean mNewSurfaceNeeded;
+ boolean mHasHadWindowFocus;
- private boolean mWindowAttributesChanged = false;
+ boolean mWindowAttributesChanged = false;
// These can be accessed by any thread, must be protected with a lock.
- private Surface mSurface;
+ Surface mSurface;
- private boolean mAdded;
- private boolean mAddedTouchMode;
+ boolean mAdded;
+ boolean mAddedTouchMode;
/*package*/ int mAddNesting;
// These are accessed by multiple threads.
- private final Rect mWinFrame; // frame given by window manager.
-
- private final Rect mCoveredInsets = new Rect();
- private final Rect mNewCoveredInsets = new Rect();
-
- private EGL10 mEgl;
- private EGLDisplay mEglDisplay;
- private EGLContext mEglContext;
- private EGLSurface mEglSurface;
- private GL11 mGL;
- private Canvas mGlCanvas;
- private boolean mUseGL;
- private boolean mGlWanted;
+ final Rect mWinFrame; // frame given by window manager.
+
+ final Rect mPendingVisibleInsets = new Rect();
+ final Rect mPendingContentInsets = new Rect();
+ final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
+ = new ViewTreeObserver.InternalInsetsInfo();
+
+ boolean mScrollMayChange;
+ int mSoftInputMode;
+ View mLastScrolledFocus;
+ int mScrollY;
+ int mCurScrollY;
+ Scroller mScroller;
+
+ EGL10 mEgl;
+ EGLDisplay mEglDisplay;
+ EGLContext mEglContext;
+ EGLSurface mEglSurface;
+ GL11 mGL;
+ Canvas mGlCanvas;
+ boolean mUseGL;
+ boolean mGlWanted;
/**
* see {@link #playSoundEffect(int)}
*/
- private AudioManager mAudioManager;
+ AudioManager mAudioManager;
- public ViewRoot() {
+ public ViewRoot(Context context) {
super();
++sInstanceCount;
@@ -164,9 +194,10 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
synchronized (mStaticInit) {
if (!mInitialized) {
try {
+ InputMethodManager imm = InputMethodManager.getInstance(context);
sWindowSession = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"))
- .openSession(new Binder());
+ .openSession(imm.getClient(), imm.getInputContext());
mInitialized = true;
} catch (RemoteException e) {
}
@@ -180,8 +211,11 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
mHeight = -1;
mDirty = new Rect();
mTempRect = new Rect();
+ mVisRect = new Rect();
+ mVisPoint = new Point();
mWinFrame = new Rect();
mWindow = new W(this);
+ mInputMethodCallback = new InputMethodCallback(this);
mViewVisibility = View.GONE;
mTransparentRegion = new Region();
mPreviousTransparentRegion = new Region();
@@ -194,7 +228,7 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
handler = new RootHandler();
sUiThreads.set(handler);
}
- mAttachInfo = new View.AttachInfo(handler, this);
+ mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, handler, this);
}
@Override
@@ -350,8 +384,10 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
synchronized (this) {
if (mView == null) {
mWindowAttributes.copyFrom(attrs);
+ mSoftInputMode = attrs.softInputMode;
mWindowAttributesChanged = true;
mView = view;
+ mAttachInfo.mRootView = view;
if (panelParentView != null) {
mAttachInfo.mPanelParentWindowToken
= panelParentView.getApplicationWindowToken();
@@ -366,16 +402,20 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
try {
res = sWindowSession.add(mWindow, attrs,
- getHostVisibility(), mCoveredInsets);
+ getHostVisibility(), mAttachInfo.mContentInsets);
} catch (RemoteException e) {
mAdded = false;
mView = null;
+ mAttachInfo.mRootView = null;
unscheduleTraversals();
throw new RuntimeException("Adding window failed", e);
}
+ mPendingContentInsets.set(mAttachInfo.mContentInsets);
+ mPendingVisibleInsets.set(0, 0, 0, 0);
if (Config.LOGV) Log.v("ViewRoot", "Added window " + mWindow);
if (res < WindowManagerImpl.ADD_OKAY) {
mView = null;
+ mAttachInfo.mRootView = null;
mAdded = false;
unscheduleTraversals();
switch (res) {
@@ -427,9 +467,13 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
return mLocation;
}
- public void setLayoutParams(WindowManager.LayoutParams attrs) {
+ void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
mWindowAttributes.copyFrom(attrs);
+ if (newView) {
+ mSoftInputMode = attrs.softInputMode;
+ requestLayout();
+ }
mWindowAttributesChanged = true;
scheduleTraversals();
}
@@ -467,6 +511,11 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
public void invalidateChild(View child, Rect dirty) {
checkThread();
if (LOCAL_LOGV) Log.v(TAG, "Invalidate child: " + dirty);
+ if (mCurScrollY != 0) {
+ mTempRect.set(dirty);
+ mTempRect.offset(0, -mCurScrollY);
+ dirty = mTempRect;
+ }
mDirty.union(dirty);
if (!mWillDrawSoon) {
scheduleTraversals();
@@ -486,6 +535,8 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
if (child != mView) {
throw new RuntimeException("child is not mine, honest!");
}
+ // Note: don't apply scroll offset, because we want to know its
+ // visibility in the virtual canvas being given to the view hierarchy.
return r.intersect(0, 0, mWidth, mHeight);
}
@@ -528,7 +579,7 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
boolean windowResizesToFitContent = false;
boolean fullRedrawNeeded = mFullRedrawNeeded;
boolean newSurface = false;
- WindowManager.LayoutParams lp = (WindowManager.LayoutParams) host.getLayoutParams();
+ WindowManager.LayoutParams lp = mWindowAttributes;
int desiredWindowWidth;
int desiredWindowHeight;
@@ -544,7 +595,7 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
WindowManager.LayoutParams params = null;
if (mWindowAttributesChanged) {
mWindowAttributesChanged = false;
- params = mWindowAttributes;
+ params = lp;
}
if (mFirst) {
@@ -559,9 +610,7 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
// is attached to the window. Note that at this point the surface
// object is not initialized to its backing store, but soon it
// will be (assuming the window is visible).
- attachInfo.mWindowToken = mWindow.asBinder();
attachInfo.mSurface = mSurface;
- attachInfo.mSession = sWindowSession;
attachInfo.mHasWindowFocus = false;
attachInfo.mWindowVisibility = viewVisibility;
attachInfo.mRecomputeGlobalAttributes = false;
@@ -590,16 +639,35 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
destroyGL();
}
}
+ if (viewVisibility == View.GONE) {
+ // After making a window gone, we will count it as being
+ // shown for the first time the next time it gets focus.
+ mHasHadWindowFocus = false;
+ }
}
+ boolean insetsChanged = false;
+
if (mLayoutRequested) {
if (mFirst) {
- host.fitSystemWindows(mCoveredInsets);
+ host.fitSystemWindows(mAttachInfo.mContentInsets);
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
} else {
+ if (!mAttachInfo.mContentInsets.equals(mPendingContentInsets)) {
+ mAttachInfo.mContentInsets.set(mPendingContentInsets);
+ host.fitSystemWindows(mAttachInfo.mContentInsets);
+ insetsChanged = true;
+ if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
+ + mAttachInfo.mContentInsets);
+ }
+ if (!mAttachInfo.mVisibleInsets.equals(mPendingVisibleInsets)) {
+ mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+ if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: "
+ + mAttachInfo.mVisibleInsets);
+ }
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowResizesToFitContent = true;
@@ -614,7 +682,7 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
// Ask host how big it wants to be
- if (DEBUG_ORIENTATION) Log.v("ViewRoot",
+ if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v("ViewRoot",
"Measuring " + host + " in display " + desiredWindowWidth
+ "x" + desiredWindowHeight + "...");
host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
@@ -633,11 +701,37 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
attachInfo.mKeepScreenOn = false;
host.dispatchCollectViewAttributes(0);
if (attachInfo.mKeepScreenOn != oldVal) {
- params = mWindowAttributes;
+ params = lp;
//Log.i(TAG, "Keep screen on changed: " + attachInfo.mKeepScreenOn);
}
}
+ if (mFirst || attachInfo.mViewVisibilityChanged) {
+ attachInfo.mViewVisibilityChanged = false;
+ int resizeMode = mSoftInputMode &
+ WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+ // If we are in auto resize mode, then we need to determine
+ // what mode to use now.
+ if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
+ final int N = attachInfo.mScrollContainers.size();
+ for (int i=0; i<N; i++) {
+ if (attachInfo.mScrollContainers.get(i).isShown()) {
+ resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+ }
+ }
+ if (resizeMode == 0) {
+ resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+ }
+ if ((lp.softInputMode &
+ WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) != resizeMode) {
+ lp.softInputMode = (lp.softInputMode &
+ ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) |
+ resizeMode;
+ params = lp;
+ }
+ }
+ }
+
if (params != null && (host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) {
if (!PixelFormat.formatHasAlpha(params.format)) {
params.format = PixelFormat.TRANSLUCENT;
@@ -647,10 +741,26 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
boolean windowShouldResize = mLayoutRequested && windowResizesToFitContent
&& (mWidth != host.mMeasuredWidth || mHeight != host.mMeasuredHeight);
+ final boolean computesInternalInsets =
+ attachInfo.mTreeObserver.hasComputeInternalInsetsListeners();
+ boolean insetsPending = false;
int relayoutResult = 0;
- if (mFirst || windowShouldResize || viewVisibilityChanged || params != null) {
+ if (mFirst || windowShouldResize || insetsChanged
+ || viewVisibilityChanged || params != null) {
if (viewVisibility == View.VISIBLE) {
+ // If this window is giving internal insets to the window
+ // manager, and it is being added or changing its visibility,
+ // then we want to first give the window manager "fake"
+ // insets to cause it to effectively ignore the content of
+ // the window during layout. This avoids it briefly causing
+ // other windows to resize/move based on the raw frame of the
+ // window, waiting until we can finish laying out this window
+ // and get back to the window manager with the ultimately
+ // computed insets.
+ insetsPending = computesInternalInsets
+ && (mFirst || viewVisibilityChanged);
+
if (mWindowAttributes.memoryType == WindowManager.LayoutParams.MEMORY_TYPE_GPU) {
if (params == null) {
params = mWindowAttributes;
@@ -661,7 +771,8 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
final Rect frame = mWinFrame;
boolean initialized = false;
- boolean coveredInsetsChanged = false;
+ boolean contentInsetsChanged = false;
+ boolean visibleInsetsChanged = false;
try {
boolean hadSurface = mSurface.isValid();
int fl = 0;
@@ -673,31 +784,57 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
}
relayoutResult = sWindowSession.relayout(
mWindow, params, host.mMeasuredWidth, host.mMeasuredHeight,
- viewVisibility, frame, mNewCoveredInsets, mSurface);
+ viewVisibility, insetsPending, frame,
+ mPendingContentInsets, mPendingVisibleInsets, mSurface);
if (params != null) {
params.flags = fl;
}
- coveredInsetsChanged = !mNewCoveredInsets.equals(mCoveredInsets);
- if (coveredInsetsChanged) {
- mCoveredInsets.set(mNewCoveredInsets);
- host.fitSystemWindows(mCoveredInsets);
+ if (DEBUG_LAYOUT) Log.v(TAG, "relayout: frame=" + frame.toShortString()
+ + " content=" + mPendingContentInsets.toShortString()
+ + " visible=" + mPendingVisibleInsets.toShortString()
+ + " surface=" + mSurface);
+
+ contentInsetsChanged = !mPendingContentInsets.equals(
+ mAttachInfo.mContentInsets);
+ visibleInsetsChanged = !mPendingVisibleInsets.equals(
+ mAttachInfo.mVisibleInsets);
+ if (contentInsetsChanged) {
+ mAttachInfo.mContentInsets.set(mPendingContentInsets);
+ host.fitSystemWindows(mAttachInfo.mContentInsets);
+ if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
+ + mAttachInfo.mContentInsets);
+ }
+ if (visibleInsetsChanged) {
+ mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+ if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: "
+ + mAttachInfo.mVisibleInsets);
}
- if (!hadSurface && mSurface.isValid()) {
- // If we are creating a new surface, then we need to
- // completely redraw it. Also, when we get to the
- // point of drawing it we will hold off and schedule
- // a new traversal instead. This is so we can tell the
- // window manager about all of the windows being displayed
- // before actually drawing them, so it can display then
- // all at once.
- newSurface = true;
- fullRedrawNeeded = true;
-
- if (mGlWanted && !mUseGL) {
- initializeGL();
- initialized = mGlCanvas != null;
+ if (!hadSurface) {
+ if (mSurface.isValid()) {
+ // If we are creating a new surface, then we need to
+ // completely redraw it. Also, when we get to the
+ // point of drawing it we will hold off and schedule
+ // a new traversal instead. This is so we can tell the
+ // window manager about all of the windows being displayed
+ // before actually drawing them, so it can display then
+ // all at once.
+ newSurface = true;
+ fullRedrawNeeded = true;
+
+ if (mGlWanted && !mUseGL) {
+ initializeGL();
+ initialized = mGlCanvas != null;
+ }
+ }
+ } else if (!mSurface.isValid()) {
+ // If the surface has been removed, then reset the scroll
+ // positions.
+ mLastScrolledFocus = null;
+ mScrollY = mCurScrollY = 0;
+ if (mScroller != null) {
+ mScroller.abortAnimation();
}
}
} catch (RemoteException e) {
@@ -721,10 +858,16 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.mMeasuredWidth
- || mHeight != host.mMeasuredHeight || coveredInsetsChanged) {
+ || mHeight != host.mMeasuredHeight || contentInsetsChanged) {
childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
+ if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth="
+ + mWidth + " measuredWidth=" + host.mMeasuredWidth
+ + " mHeight=" + mHeight
+ + " measuredHeight" + host.mMeasuredHeight
+ + " coveredInsetsChanged=" + contentInsetsChanged);
+
// Ask host how big it wants to be
host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
@@ -749,6 +892,9 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
}
if (measureAgain) {
+ if (DEBUG_LAYOUT) Log.v(TAG,
+ "And hey let's measure once more: width=" + width
+ + " height=" + height);
host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
@@ -756,12 +902,14 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
}
}
- boolean triggerGlobalLayoutListener = mLayoutRequested
+ final boolean didLayout = mLayoutRequested;
+ boolean triggerGlobalLayoutListener = didLayout
|| attachInfo.mRecomputeGlobalAttributes;
- if (mLayoutRequested) {
+ if (didLayout) {
mLayoutRequested = false;
- if (DEBUG_ORIENTATION) Log.v(
- "ViewRoot", "Setting frame " + host + " to (" +
+ mScrollMayChange = true;
+ if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(
+ "ViewRoot", "Laying out " + host + " to (" +
host.mMeasuredWidth + ", " + host.mMeasuredHeight + ")");
long startTime;
if (PROFILE_LAYOUT) {
@@ -780,10 +928,10 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
if ((host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) {
// start out transparent
// TODO: AVOID THAT CALL BY CACHING THE RESULT?
- host.getLocationInWindow(host.mLocation);
- mTransparentRegion.set(host.mLocation[0], host.mLocation[1],
- host.mLocation[0] + host.mRight - host.mLeft,
- host.mLocation[1] + host.mBottom - host.mTop);
+ host.getLocationInWindow(mTmpLocation);
+ mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
+ mTmpLocation[0] + host.mRight - host.mLeft,
+ mTmpLocation[1] + host.mBottom - host.mTop);
host.gatherTransparentRegion(mTransparentRegion);
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
@@ -809,6 +957,24 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
attachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
+ if (computesInternalInsets) {
+ ViewTreeObserver.InternalInsetsInfo insets = attachInfo.mGivenInternalInsets;
+ final Rect givenContent = attachInfo.mGivenInternalInsets.contentInsets;
+ final Rect givenVisible = attachInfo.mGivenInternalInsets.visibleInsets;
+ givenContent.left = givenContent.top = givenContent.right
+ = givenContent.bottom = givenVisible.left = givenVisible.top
+ = givenVisible.right = givenVisible.bottom = 0;
+ attachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
+ if (insetsPending || !mLastGivenInsets.equals(insets)) {
+ mLastGivenInsets.set(insets);
+ try {
+ sWindowSession.setInsets(mWindow, insets.mTouchableInsets,
+ insets.contentInsets, insets.visibleInsets);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
if (mFirst) {
// handle first focus request
if (mView != null && !mView.hasFocus()) {
@@ -841,7 +1007,7 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
}
} else {
// We were supposed to report when we are done drawing. Since we canceled the
- // draw, rememeber it here.
+ // draw, remember it here.
if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
mReportNextDraw = true;
}
@@ -903,6 +1069,21 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
return;
}
+ scrollToRectOrFocus(null, false);
+
+ int yoff;
+ final boolean scrolling = mScroller != null
+ && mScroller.computeScrollOffset();
+ if (scrolling) {
+ yoff = mScroller.getCurrY();
+ } else {
+ yoff = mScrollY;
+ }
+ if (mCurScrollY != yoff) {
+ mCurScrollY = yoff;
+ fullRedrawNeeded = true;
+ }
+
Rect dirty = mDirty;
if (mUseGL) {
if (!dirty.isEmpty()) {
@@ -914,7 +1095,9 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
mGL.glEnable(GL_SCISSOR_TEST);
mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
+ canvas.translate(0, -yoff);
mView.draw(canvas);
+ canvas.translate(0, yoff);
mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
checkEglErrors();
@@ -928,6 +1111,10 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
}
}
}
+ if (scrolling) {
+ mFullRedrawNeeded = true;
+ scheduleTraversals();
+ }
return;
}
@@ -973,11 +1160,18 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
// background. This automatically respects the clip/dirty region
if (!canvas.isOpaque()) {
canvas.drawColor(0, 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
+ // left in the blank areas.
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
+ canvas.translate(0, -yoff);
mView.draw(canvas);
+ canvas.translate(0, yoff);
if (SHOW_FPS) {
int now = (int)SystemClock.elapsedRealtime();
@@ -999,12 +1193,125 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost");
}
}
+
+ if (scrolling) {
+ mFullRedrawNeeded = true;
+ scheduleTraversals();
+ }
}
+ boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
+ final View.AttachInfo attachInfo = mAttachInfo;
+ final Rect ci = attachInfo.mContentInsets;
+ final Rect vi = attachInfo.mVisibleInsets;
+ int scrollY = 0;
+ boolean handled = false;
+
+ if (vi.left > ci.left || vi.top > ci.top
+ || vi.right > ci.right || vi.bottom > ci.bottom) {
+ // We'll assume that we aren't going to change the scroll
+ // offset, since we want to avoid that unless it is actually
+ // going to make the focus visible... otherwise we scroll
+ // all over the place.
+ scrollY = mScrollY;
+ // We can be called for two different situations: during a draw,
+ // to update the scroll position if the focus has changed (in which
+ // case 'rectangle' is null), or in response to a
+ // requestChildRectangleOnScreen() call (in which case 'rectangle'
+ // is non-null and we just want to scroll to whatever that
+ // rectangle is).
+ View focus = mFocusedView;
+ if (focus != mLastScrolledFocus) {
+ // If the focus has changed, then ignore any requests to scroll
+ // to a rectangle; first we want to make sure the entire focus
+ // view is visible.
+ rectangle = null;
+ }
+ if (focus == mLastScrolledFocus && !mScrollMayChange
+ && rectangle == null) {
+ // Optimization: if the focus hasn't changed since last
+ // time, and no layout has happened, then just leave things
+ // as they are.
+ if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Keeping scroll y="
+ + mScrollY + " vi=" + vi.toShortString());
+ } else if (focus != null) {
+ // We need to determine if the currently focused view is
+ // within the visible part of the window and, if not, apply
+ // a pan so it can be seen.
+ mLastScrolledFocus = focus;
+ mScrollMayChange = false;
+ // Try to find the rectangle from the focus view.
+ if (focus.getGlobalVisibleRect(mVisRect, null)) {
+ if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Root w="
+ + mView.getWidth() + " h=" + mView.getHeight()
+ + " ci=" + ci.toShortString()
+ + " vi=" + vi.toShortString());
+ if (rectangle == null) {
+ focus.getFocusedRect(mTempRect);
+ if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Focus " + focus
+ + ": focusRect=" + mTempRect.toShortString());
+ ((ViewGroup) mView).offsetDescendantRectToMyCoords(
+ focus, mTempRect);
+ if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+ "Focus in window: focusRect="
+ + mTempRect.toShortString()
+ + " visRect=" + mVisRect.toShortString());
+ } else {
+ mTempRect.set(rectangle);
+ if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+ "Request scroll to rect: "
+ + mTempRect.toShortString()
+ + " visRect=" + mVisRect.toShortString());
+ }
+ if (mTempRect.intersect(mVisRect)) {
+ if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+ "Focus window visible rect: "
+ + mTempRect.toShortString());
+ if (mTempRect.height() >
+ (mView.getHeight()-vi.top-vi.bottom)) {
+ // If the focus simply is not going to fit, then
+ // best is probably just to leave things as-is.
+ if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+ "Too tall; leaving scrollY=" + scrollY);
+ } else if ((mTempRect.top-scrollY) < vi.top) {
+ scrollY -= vi.top - (mTempRect.top-scrollY);
+ if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+ "Top covered; scrollY=" + scrollY);
+ } else if ((mTempRect.bottom-scrollY)
+ > (mView.getHeight()-vi.bottom)) {
+ scrollY += (mTempRect.bottom-scrollY)
+ - (mView.getHeight()-vi.bottom);
+ if (DEBUG_INPUT_RESIZE) Log.v(TAG,
+ "Bottom covered; scrollY=" + scrollY);
+ }
+ handled = true;
+ }
+ }
+ }
+ }
+
+ if (scrollY != mScrollY) {
+ if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Pan scroll changed: old="
+ + mScrollY + " , new=" + scrollY);
+ if (!immediate) {
+ if (mScroller == null) {
+ mScroller = new Scroller(mView.getContext());
+ }
+ mScroller.startScroll(0, mScrollY, 0, scrollY-mScrollY);
+ } else if (mScroller != null) {
+ mScroller.abortAnimation();
+ }
+ mScrollY = scrollY;
+ }
+
+ return handled;
+ }
+
public void requestChildFocus(View child, View focused) {
checkThread();
if (mFocusedView != focused) {
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mFocusedView, focused);
+ scheduleTraversals();
}
mFocusedView = focused;
}
@@ -1063,6 +1370,7 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
mView.dispatchDetachedFromWindow();
}
mView = null;
+ mAttachInfo.mRootView = null;
if (mUseGL) {
destroyGL();
}
@@ -1081,16 +1389,17 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
}
- private final static int DO_TRAVERSAL = 1000;
- private final static int DIE = 1001;
- private final static int RESIZED = 1002;
- private final static int RESIZED_REPORT = 1003;
- private final static int WINDOW_FOCUS_CHANGED = 1004;
- private final static int DISPATCH_KEY = 1005;
- private final static int DISPATCH_POINTER = 1006;
- private final static int DISPATCH_TRACKBALL = 1007;
- private final static int DISPATCH_APP_VISIBILITY = 1008;
- private final static int DISPATCH_GET_NEW_SURFACE = 1009;
+ public final static int DO_TRAVERSAL = 1000;
+ public final static int DIE = 1001;
+ public final static int RESIZED = 1002;
+ public final static int RESIZED_REPORT = 1003;
+ public final static int WINDOW_FOCUS_CHANGED = 1004;
+ public final static int DISPATCH_KEY = 1005;
+ public final static int DISPATCH_POINTER = 1006;
+ public final static int DISPATCH_TRACKBALL = 1007;
+ public final static int DISPATCH_APP_VISIBILITY = 1008;
+ public final static int DISPATCH_GET_NEW_SURFACE = 1009;
+ public final static int FINISHED_EVENT = 1010;
@Override
public void handleMessage(Message msg) {
@@ -1107,6 +1416,9 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
mProfile = false;
}
break;
+ case FINISHED_EVENT:
+ handleFinishedEvent(msg.arg1, msg.arg2 != 0);
+ break;
case DISPATCH_KEY:
if (LOCAL_LOGV) Log.v(
"ViewRoot", "Dispatching key "
@@ -1124,7 +1436,7 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
}
didFinish = true;
} else {
- didFinish = false;
+ didFinish = event.getAction() == MotionEvent.ACTION_OUTSIDE;
}
try {
@@ -1136,7 +1448,10 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
if (isDown) {
ensureTouchMode(true);
}
-
+ if(Config.LOGV) {
+ captureMotionLog("captureDispatchPointer", event);
+ }
+ event.offsetLocation(0, mCurScrollY);
handled = mView.dispatchTouchEvent(event);
if (!handled && isDown) {
int edgeSlop = ViewConfiguration.getEdgeSlop();
@@ -1207,7 +1522,11 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
handleGetNewSurface();
break;
case RESIZED:
- if (mWinFrame.width() == msg.arg1 && mWinFrame.height() == msg.arg2) {
+ Rect coveredInsets = ((Rect[])msg.obj)[0];
+ Rect visibleInsets = ((Rect[])msg.obj)[1];
+ if (mWinFrame.width() == msg.arg1 && mWinFrame.height() == msg.arg2
+ && mPendingContentInsets.equals(coveredInsets)
+ && mPendingVisibleInsets.equals(visibleInsets)) {
break;
}
// fall through...
@@ -1217,6 +1536,8 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
mWinFrame.right = msg.arg1;
mWinFrame.top = 0;
mWinFrame.bottom = msg.arg2;
+ mPendingContentInsets.set(((Rect[])msg.obj)[0]);
+ mPendingVisibleInsets.set(((Rect[])msg.obj)[1]);
if (msg.what == RESIZED_REPORT) {
mReportNextDraw = true;
}
@@ -1245,6 +1566,18 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
if (mView != null) {
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(),
+ mWindowAttributes.softInputMode, !mHasHadWindowFocus,
+ mWindowAttributes.flags);
+ }
+ mHasHadWindowFocus = true;
+ }
}
} break;
case DIE:
@@ -1387,7 +1720,7 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
didFinish = false;
}
- //Log.i("foo", "Motion event:" + event);
+ if (DEBUG_TRACKBALL) Log.v(TAG, "Motion event:" + event);
boolean handled = false;
try {
@@ -1397,7 +1730,7 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
handled = mView.dispatchTrackballEvent(event);
if (!handled) {
// we could do something here, like changing the focus
- // or someting?
+ // or something?
}
}
} finally {
@@ -1456,8 +1789,8 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
+ " / Y=" + y.position + " step="
+ y.step + " dir=" + y.dir + " acc=" + y.acceleration
+ " move=" + event.getY());
- final float xOff = x.collect(event.getX(), "X");
- final float yOff = y.collect(event.getY(), "Y");
+ final float xOff = x.collect(event.getX(), event.getEventTime(), "X");
+ final float yOff = y.collect(event.getY(), event.getEventTime(), "Y");
// Generate DPAD events based on the trackball movement.
// We pick the axis that has moved the most as the direction of
@@ -1489,9 +1822,9 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
if (keycode != 0) {
if (movement < 0) movement = -movement;
int accelMovement = (int)(movement * accel);
- //Log.i(TAG, "Move: movement=" + movement
- // + " accelMovement=" + accelMovement
- // + " accel=" + accel);
+ if (DEBUG_TRACKBALL) Log.v(TAG, "Move: movement=" + movement
+ + " accelMovement=" + accelMovement
+ + " accel=" + accel);
if (accelMovement > movement) {
if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: "
+ keycode);
@@ -1602,8 +1935,116 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
return false;
}
-
+ /**
+ * log motion events
+ */
+ private static void captureMotionLog(String subTag, MotionEvent ev) {
+ //check dynamic switch
+ if (ev == null ||
+ SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_EVENT, 0) == 0) {
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder(subTag + ": ");
+ sb.append(ev.getDownTime()).append(',');
+ sb.append(ev.getEventTime()).append(',');
+ sb.append(ev.getAction()).append(',');
+ sb.append(ev.getX()).append(',');
+ sb.append(ev.getY()).append(',');
+ sb.append(ev.getPressure()).append(',');
+ sb.append(ev.getSize()).append(',');
+ sb.append(ev.getMetaState()).append(',');
+ sb.append(ev.getXPrecision()).append(',');
+ sb.append(ev.getYPrecision()).append(',');
+ sb.append(ev.getDeviceId()).append(',');
+ sb.append(ev.getEdgeFlags());
+ Log.d(TAG, sb.toString());
+ }
+ /**
+ * log motion events
+ */
+ private static void captureKeyLog(String subTag, KeyEvent ev) {
+ //check dynamic switch
+ if (ev == null ||
+ SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_EVENT, 0) == 0) {
+ return;
+ }
+ StringBuilder sb = new StringBuilder(subTag + ": ");
+ sb.append(ev.getDownTime()).append(',');
+ sb.append(ev.getEventTime()).append(',');
+ sb.append(ev.getAction()).append(',');
+ sb.append(ev.getKeyCode()).append(',');
+ sb.append(ev.getRepeatCount()).append(',');
+ sb.append(ev.getMetaState()).append(',');
+ sb.append(ev.getDeviceId()).append(',');
+ sb.append(ev.getScanCode());
+ Log.d(TAG, sb.toString());
+ }
+
+ int enqueuePendingEvent(Object event, boolean sendDone) {
+ int seq = mPendingEventSeq+1;
+ if (seq < 0) seq = 0;
+ mPendingEventSeq = seq;
+ mPendingEvents.put(seq, event);
+ return sendDone ? seq : -seq;
+ }
+
+ Object retrievePendingEvent(int seq) {
+ if (seq < 0) seq = -seq;
+ Object event = mPendingEvents.get(seq);
+ if (event != null) {
+ mPendingEvents.remove(seq);
+ }
+ return event;
+ }
+
private void deliverKeyEvent(KeyEvent event, boolean sendDone) {
+ boolean handled = false;
+ handled = mView.dispatchKeyEventPreIme(event);
+ if (handled) {
+ if (sendDone) {
+ if (LOCAL_LOGV) Log.v(
+ "ViewRoot", "Telling window manager key is finished");
+ try {
+ sWindowSession.finishKey(mWindow);
+ } catch (RemoteException e) {
+ }
+ }
+ return;
+ }
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null && mView != null && imm.isActive()) {
+ int seq = enqueuePendingEvent(event, sendDone);
+ if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="
+ + seq + " event=" + event);
+ imm.dispatchKeyEvent(mView.getContext(), seq, event,
+ mInputMethodCallback);
+ return;
+ }
+ deliverKeyEventToViewHierarchy(event, sendDone);
+ }
+
+ void handleFinishedEvent(int seq, boolean handled) {
+ final KeyEvent event = (KeyEvent)retrievePendingEvent(seq);
+ if (DEBUG_IMF) Log.v(TAG, "IME finished event: seq=" + seq
+ + " handled=" + handled + " event=" + event);
+ if (event != null) {
+ final boolean sendDone = seq >= 0;
+ if (!handled) {
+ deliverKeyEventToViewHierarchy(event, sendDone);
+ return;
+ } else if (sendDone) {
+ if (LOCAL_LOGV) Log.v(
+ "ViewRoot", "Telling window manager key is finished");
+ try {
+ sWindowSession.finishKey(mWindow);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ private void deliverKeyEventToViewHierarchy(KeyEvent event, boolean sendDone) {
try {
if (mView != null && mAdded) {
final int action = event.getAction();
@@ -1611,8 +2052,11 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
if (checkForLeavingTouchModeAndConsume(event)) {
return;
+ }
+
+ if (Config.LOGV) {
+ captureKeyLog("captureDispatchKeyEvent", event);
}
-
boolean keyHandled = mView.dispatchKeyEvent(event);
if ((!keyHandled && isDown) || (action == KeyEvent.ACTION_MULTIPLE)) {
@@ -1740,10 +2184,11 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
// animation info.
try {
if ((sWindowSession.relayout(
- mWindow, mWindowAttributes,
- mView.mMeasuredWidth, mView.mMeasuredHeight,
- viewVisibility, mWinFrame, mCoveredInsets, mSurface)
- &WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
+ mWindow, mWindowAttributes,
+ mView.mMeasuredWidth, mView.mMeasuredHeight,
+ viewVisibility, false, mWinFrame, mPendingContentInsets,
+ mPendingVisibleInsets, mSurface)
+ &WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
sWindowSession.finishDrawing(mWindow);
}
} catch (RemoteException e) {
@@ -1767,12 +2212,23 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
}
}
- public void dispatchResized(int w, int h, boolean reportDraw) {
- if (DEBUG_DRAW) Log.v(TAG, "Resized " + this + ": w=" + w
- + " h=" + h + " reportDraw=" + reportDraw);
- Message msg = obtainMessage(reportDraw ? RESIZED_REPORT : RESIZED);
+ public void dispatchFinishedEvent(int seq, boolean handled) {
+ Message msg = obtainMessage(FINISHED_EVENT);
+ msg.arg1 = seq;
+ msg.arg2 = handled ? 1 : 0;
+ sendMessage(msg);
+ }
+
+ public void dispatchResized(int w, int h, Rect coveredInsets,
+ Rect visibleInsets, boolean reportDraw) {
+ if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": w=" + w
+ + " h=" + h + " coveredInsets=" + coveredInsets.toShortString()
+ + " visibleInsets=" + visibleInsets.toShortString()
+ + " reportDraw=" + reportDraw);
+ Message msg = obtainMessage(reportDraw ? RESIZED_REPORT :RESIZED);
msg.arg1 = w;
msg.arg2 = h;
+ msg.obj = new Rect[] { new Rect(coveredInsets), new Rect(visibleInsets) };
sendMessage(msg);
}
@@ -1855,6 +2311,30 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
// ViewRoot never intercepts touch event, so this can be a no-op
}
+ public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
+ boolean immediate) {
+ return scrollToRectOrFocus(rectangle, immediate);
+ }
+
+ static class InputMethodCallback extends IInputMethodCallback.Stub {
+ private WeakReference<ViewRoot> mViewRoot;
+
+ public InputMethodCallback(ViewRoot viewRoot) {
+ mViewRoot = new WeakReference<ViewRoot>(viewRoot);
+ }
+
+ public void finishedEvent(int seq, boolean handled) {
+ final ViewRoot viewRoot = mViewRoot.get();
+ if (viewRoot != null) {
+ viewRoot.dispatchFinishedEvent(seq, handled);
+ }
+ }
+
+ public void sessionCreated(IInputMethodSession session) throws RemoteException {
+ // Stub -- not for use in the client.
+ }
+ }
+
static class W extends IWindow.Stub {
private WeakReference<ViewRoot> mViewRoot;
@@ -1862,10 +2342,12 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
mViewRoot = new WeakReference<ViewRoot>(viewRoot);
}
- public void resized(int w, int h, boolean reportDraw) {
+ public void resized(int w, int h, Rect coveredInsets,
+ Rect visibleInsets, boolean reportDraw) {
final ViewRoot viewRoot = mViewRoot.get();
if (viewRoot != null) {
- viewRoot.dispatchResized(w, h, reportDraw);
+ viewRoot.dispatchResized(w, h, coveredInsets,
+ visibleInsets, reportDraw);
}
}
@@ -1961,9 +2443,30 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
* discrete (DPAD) movements based on raw trackball motion.
*/
static final class TrackballAxis {
+ /**
+ * The maximum amount of acceleration we will apply.
+ */
+ static final float MAX_ACCELERATION = 20;
+
+ /**
+ * The maximum amount of time (in milliseconds) between events in order
+ * for us to consider the user to be doing fast trackball movements,
+ * and thus apply an acceleration.
+ */
+ static final long FAST_MOVE_TIME = 100;
+
+ /**
+ * Scaling factor to the time (in milliseconds) between events to how
+ * much to multiple/divide the current acceleration. When movement
+ * 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);
+
float position;
float absPosition;
float acceleration = 1;
+ long lastMoveTime = 0;
int step;
int dir;
int nonAccelMovement;
@@ -1971,6 +2474,7 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
void reset(int _step) {
position = 0;
acceleration = 1;
+ lastMoveTime = 0;
step = _step;
dir = 0;
}
@@ -1985,23 +2489,56 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
* @return Returns the absolute value of the amount of movement
* collected so far.
*/
- float collect(float off, String axis) {
+ float collect(float off, long time, String axis) {
+ long normTime;
if (off > 0) {
+ normTime = (long)(off * FAST_MOVE_TIME);
if (dir < 0) {
if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!");
position = 0;
step = 0;
acceleration = 1;
+ lastMoveTime = 0;
}
dir = 1;
} else if (off < 0) {
+ normTime = (long)((-off) * FAST_MOVE_TIME);
if (dir > 0) {
if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!");
position = 0;
step = 0;
acceleration = 1;
+ lastMoveTime = 0;
}
dir = -1;
+ } else {
+ normTime = 0;
+ }
+
+ // The number of milliseconds between each movement that is
+ // considered "normal" and will not result in any acceleration
+ // or deceleration, scaled by the offset we have here.
+ if (normTime > 0) {
+ long delta = time - lastMoveTime;
+ lastMoveTime = time;
+ float acc = acceleration;
+ if (delta < normTime) {
+ // The user is scrolling rapidly, so increase acceleration.
+ float scale = (normTime-delta) * ACCEL_MOVE_SCALING_FACTOR;
+ if (scale > 1) acc *= scale;
+ if (DEBUG_TRACKBALL) Log.v(TAG, axis + " accelerate: off="
+ + off + " normTime=" + normTime + " delta=" + delta
+ + " scale=" + scale + " acc=" + acc);
+ acceleration = acc < MAX_ACCELERATION ? acc : MAX_ACCELERATION;
+ } else {
+ // The user is scrolling slowly, so decrease acceleration.
+ float scale = (delta-normTime) * ACCEL_MOVE_SCALING_FACTOR;
+ if (scale > 1) acc /= scale;
+ if (DEBUG_TRACKBALL) Log.v(TAG, axis + " deccelerate: off="
+ + off + " normTime=" + normTime + " delta=" + delta
+ + " scale=" + scale + " acc=" + acc);
+ acceleration = acc > 1 ? acc : 1;
+ }
}
position += off;
return (absPosition = Math.abs(position));
@@ -2050,12 +2587,11 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
break;
// After the first two, we generate discrete movements
// consistently with the trackball, applying an acceleration
- // if the trackball is moving quickly. The acceleration is
- // currently very simple, just reducing the amount of
- // trackball motion required as more discrete movements are
- // generated. This should probably be changed to take time
- // more into account, so that quick trackball movements will
- // have increased acceleration.
+ // if the trackball is moving quickly. This is a simple
+ // acceleration on top of what we already compute based
+ // on how quickly the wheel is being turned, to apply
+ // a longer increasing acceleration to continuous movement
+ // in one direction.
default:
if (absPosition < 1) {
return movement;
@@ -2065,7 +2601,7 @@ final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Soun
absPosition = Math.abs(position);
float acc = acceleration;
acc *= 1.1f;
- acceleration = acc < 20 ? acc : acceleration;
+ acceleration = acc < MAX_ACCELERATION ? acc : acceleration;
break;
}
} while (true);
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 2e1e01a..05f5fa2 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -16,6 +16,8 @@
package android.view;
+import android.graphics.Rect;
+
import java.util.ArrayList;
/**
@@ -32,6 +34,7 @@ public final class ViewTreeObserver {
private ArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
private ArrayList<OnPreDrawListener> mOnPreDrawListeners;
private ArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
+ private ArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
private boolean mAlive = true;
@@ -96,6 +99,108 @@ public final class ViewTreeObserver {
}
/**
+ * Parameters used with OnComputeInternalInsetsListener.
+ * {@hide pending API Council approval}
+ */
+ public final static class InternalInsetsInfo {
+ /**
+ * Offsets from the frame of the window at which the content of
+ * windows behind it should be placed.
+ */
+ public final Rect contentInsets = new Rect();
+
+ /**
+ * Offsets from the fram of the window at which windows behind it
+ * are visible.
+ */
+ public final Rect visibleInsets = new Rect();
+
+ /**
+ * Option for {@link #setTouchableInsets(int)}: the entire window frame
+ * can be touched.
+ */
+ public static final int TOUCHABLE_INSETS_FRAME = 0;
+
+ /**
+ * Option for {@link #setTouchableInsets(int)}: the area inside of
+ * the content insets can be touched.
+ */
+ public static final int TOUCHABLE_INSETS_CONTENT = 1;
+
+ /**
+ * Option for {@link #setTouchableInsets(int)}: the area inside of
+ * the visible insets can be touched.
+ */
+ public static final int TOUCHABLE_INSETS_VISIBLE = 2;
+
+ /**
+ * Set which parts of the window can be touched: either
+ * {@link #TOUCHABLE_INSETS_FRAME}, {@link #TOUCHABLE_INSETS_CONTENT},
+ * or {@link #TOUCHABLE_INSETS_VISIBLE}.
+ */
+ public void setTouchableInsets(int val) {
+ mTouchableInsets = val;
+ }
+
+ public int getTouchableInsets() {
+ return mTouchableInsets;
+ }
+
+ int mTouchableInsets;
+
+ void reset() {
+ final Rect givenContent = contentInsets;
+ final Rect givenVisible = visibleInsets;
+ givenContent.left = givenContent.top = givenContent.right
+ = givenContent.bottom = givenVisible.left = givenVisible.top
+ = givenVisible.right = givenVisible.bottom = 0;
+ mTouchableInsets = TOUCHABLE_INSETS_FRAME;
+ }
+
+ @Override public boolean equals(Object o) {
+ try {
+ if (o == null) {
+ return false;
+ }
+ InternalInsetsInfo other = (InternalInsetsInfo)o;
+ if (!contentInsets.equals(other.contentInsets)) {
+ return false;
+ }
+ if (!visibleInsets.equals(other.visibleInsets)) {
+ return false;
+ }
+ return mTouchableInsets == other.mTouchableInsets;
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ void set(InternalInsetsInfo other) {
+ contentInsets.set(other.contentInsets);
+ visibleInsets.set(other.visibleInsets);
+ mTouchableInsets = other.mTouchableInsets;
+ }
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when layout has
+ * completed and the client can compute its interior insets.
+ * {@hide pending API Council approval}
+ */
+ public interface OnComputeInternalInsetsListener {
+ /**
+ * Callback method to be invoked when layout has completed and the
+ * client can compute its interior insets.
+ *
+ * @param inoutInfo Should be filled in by the implementation with
+ * the information about the insets of the window. This is called
+ * with whatever values the previous OnComputeInternalInsetsListener
+ * returned, if there are multiple such listeners in the window.
+ */
+ public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
+ }
+
+ /**
* Creates a new ViewTreeObserver. This constructor should not be called
*/
ViewTreeObserver() {
@@ -141,6 +246,14 @@ public final class ViewTreeObserver {
}
}
+ if (observer.mOnComputeInternalInsetsListeners != null) {
+ if (mOnComputeInternalInsetsListeners != null) {
+ mOnComputeInternalInsetsListeners.addAll(observer.mOnComputeInternalInsetsListeners);
+ } else {
+ mOnComputeInternalInsetsListeners = observer.mOnComputeInternalInsetsListeners;
+ }
+ }
+
observer.kill();
}
@@ -281,6 +394,43 @@ public final class ViewTreeObserver {
mOnTouchModeChangeListeners.remove(victim);
}
+ /**
+ * Register a callback to be invoked when the invoked when it is time to
+ * compute the window's internal insets.
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ * {@hide pending API Council approval}
+ */
+ public void addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener listener) {
+ checkIsAlive();
+
+ if (mOnComputeInternalInsetsListeners == null) {
+ mOnComputeInternalInsetsListeners = new ArrayList<OnComputeInternalInsetsListener>();
+ }
+
+ mOnComputeInternalInsetsListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed internal insets computation callback
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnComputeInternalInsetsListener(OnComputeInternalInsetsListener)
+ * {@hide pending API Council approval}
+ */
+ public void removeOnComputeInternalInsetsListener(OnComputeInternalInsetsListener victim) {
+ checkIsAlive();
+ if (mOnComputeInternalInsetsListeners == null) {
+ return;
+ }
+ mOnComputeInternalInsetsListeners.remove(victim);
+ }
+
private void checkIsAlive() {
if (!mAlive) {
throw new IllegalStateException("This ViewTreeObserver is not alive, call "
@@ -373,4 +523,25 @@ public final class ViewTreeObserver {
}
}
}
+
+ /**
+ * Returns whether there are listeners for computing internal insets.
+ */
+ final boolean hasComputeInternalInsetsListeners() {
+ final ArrayList<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners;
+ return (listeners != null && listeners.size() > 0);
+ }
+
+ /**
+ * Calls all listeners to compute the current insets.
+ */
+ final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) {
+ final ArrayList<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners;
+ if (listeners != null) {
+ final int count = listeners.size();
+ for (int i = count - 1; i >= 0; i--) {
+ listeners.get(i).onComputeInternalInsets(inoutInfo);
+ }
+ }
+ }
}
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index 24f4853..f4d0fde 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -19,10 +19,15 @@ package android.view;
import android.media.ToneGenerator;
import android.media.AudioManager;
import android.media.AudioService;
+import android.media.AudioSystem;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
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;
@@ -69,46 +74,47 @@ public class VolumePanel extends Handler
private static final int MSG_STOP_SOUNDS = 3;
private static final int MSG_VIBRATE = 4;
- private final String RINGTONE_VOLUME_TEXT;
- private final String MUSIC_VOLUME_TEXT;
- private final String INCALL_VOLUME_TEXT;
- private final String ALARM_VOLUME_TEXT;
- private final String UNKNOWN_VOLUME_TEXT;
+ 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;
protected Context mContext;
+ private AudioManager mAudioManager;
protected AudioService mAudioService;
- private Toast mToast;
- private View mView;
- private TextView mMessage;
- private ImageView mOtherStreamIcon;
- private ImageView mRingerStreamIcon;
- private ProgressBar mLevel;
+ private final Toast mToast;
+ private final View mView;
+ private final TextView mMessage;
+ private final TextView mAdditionalMessage;
+ private final ImageView mSmallStreamIcon;
+ private final ImageView mLargeStreamIcon;
+ private final ProgressBar mLevel;
// Synchronize when accessing this
private ToneGenerator mToneGenerators[];
private Vibrator mVibrator;
-
+
public VolumePanel(Context context, AudioService volumeService) {
mContext = context;
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mAudioService = volumeService;
mToast = new Toast(context);
-
- RINGTONE_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_ringtone);
- MUSIC_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_music);
- INCALL_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_call);
- ALARM_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_alarm);
- UNKNOWN_VOLUME_TEXT = context.getString(com.android.internal.R.string.volume_unknown);
-
+
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = mView = inflater.inflate(com.android.internal.R.layout.volume_adjust, null);
mMessage = (TextView) view.findViewById(com.android.internal.R.id.message);
- mOtherStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.other_stream_icon);
- mRingerStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.ringer_stream_icon);
+ mAdditionalMessage =
+ (TextView) view.findViewById(com.android.internal.R.id.additional_message);
+ mSmallStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.other_stream_icon);
+ mLargeStreamIcon = (ImageView) view.findViewById(com.android.internal.R.id.ringer_stream_icon);
mLevel = (ProgressBar) view.findViewById(com.android.internal.R.id.level);
- mToneGenerators = new ToneGenerator[AudioManager.NUM_STREAMS];
+ mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
mVibrator = new Vibrator();
}
@@ -148,13 +154,17 @@ public class VolumePanel extends Handler
protected void onShowVolumeChanged(int streamType, int flags) {
int index = mAudioService.getStreamVolume(streamType);
- String message = UNKNOWN_VOLUME_TEXT;
+ int message = UNKNOWN_VOLUME_TEXT;
+ int additionalMessage = 0;
if (LOGD) {
Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType
+ ", flags: " + flags + "), index: " + index);
}
+ // get max volume for progress bar
+ int max = mAudioService.getStreamMaxVolume(streamType);
+
switch (streamType) {
case AudioManager.STREAM_RING: {
@@ -165,32 +175,60 @@ public class VolumePanel extends Handler
case AudioManager.STREAM_MUSIC: {
message = MUSIC_VOLUME_TEXT;
- setOtherIcon(index);
+ if (mAudioManager.isBluetoothA2dpOn()) {
+ additionalMessage =
+ com.android.internal.R.string.volume_music_hint_playing_through_bluetooth;
+ setLargeIcon(com.android.internal.R.drawable.ic_volume_bluetooth_ad2p);
+ } else {
+ setSmallIcon(index);
+ }
break;
}
case AudioManager.STREAM_VOICE_CALL: {
- message = INCALL_VOLUME_TEXT;
/*
- * For in-call voice call volume, there is no inaudible volume
- * level, so never show the mute icon
+ * 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.
*/
- setOtherIcon(index == 0 ? 1 : index);
+ 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);
+ }
break;
}
case AudioManager.STREAM_ALARM: {
message = ALARM_VOLUME_TEXT;
- setOtherIcon(index);
+ setSmallIcon(index);
+ break;
+ }
+
+ case AudioManager.STREAM_NOTIFICATION: {
+ message = NOTIFICATION_VOLUME_TEXT;
+ setSmallIcon(index);
break;
}
}
- if (!mMessage.getText().equals(message)) {
- mMessage.setText(message);
+ String messageString = Resources.getSystem().getString(message);
+ if (!mMessage.getText().equals(messageString)) {
+ mMessage.setText(messageString);
}
- int max = mAudioService.getStreamMaxVolume(streamType);
+ if (additionalMessage == 0) {
+ mAdditionalMessage.setVisibility(View.GONE);
+ } else {
+ mAdditionalMessage.setVisibility(View.VISIBLE);
+ mAdditionalMessage.setText(Resources.getSystem().getString(additionalMessage));
+ }
+
if (max != mLevel.getMax()) {
mLevel.setMax(max);
}
@@ -230,7 +268,8 @@ public class VolumePanel extends Handler
protected void onStopSounds() {
synchronized (this) {
- for (int i = AudioManager.NUM_STREAMS - 1; i >= 0; i--) {
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int i = numStreamTypes - 1; i >= 0; i--) {
ToneGenerator toneGen = mToneGenerators[i];
if (toneGen != null) {
toneGen.stopTone();
@@ -261,19 +300,41 @@ public class VolumePanel extends Handler
}
}
}
-
- private void setOtherIcon(int index) {
- mRingerStreamIcon.setVisibility(View.GONE);
- mOtherStreamIcon.setVisibility(View.VISIBLE);
+
+ /**
+ * 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);
- mOtherStreamIcon.setImageResource(index == 0
+ mSmallStreamIcon.setImageResource(index == 0
? com.android.internal.R.drawable.ic_volume_off_small
: com.android.internal.R.drawable.ic_volume_small);
}
+ /**
+ * Makes the large image view visible with the given icon.
+ *
+ * @param resId The icon to display.
+ */
+ private void setLargeIcon(int resId) {
+ mSmallStreamIcon.setVisibility(View.GONE);
+ mLargeStreamIcon.setVisibility(View.VISIBLE);
+ mLargeStreamIcon.setImageResource(resId);
+ }
+
+ /**
+ * Makes the ringer icon visible with an icon that is chosen
+ * based on the current ringer mode.
+ *
+ * @param index
+ */
private void setRingerIcon(int index) {
- mOtherStreamIcon.setVisibility(View.GONE);
- mRingerStreamIcon.setVisibility(View.VISIBLE);
+ mSmallStreamIcon.setVisibility(View.GONE);
+ mLargeStreamIcon.setVisibility(View.VISIBLE);
int ringerMode = mAudioService.getRingerMode();
int icon;
@@ -287,14 +348,14 @@ public class VolumePanel extends Handler
} else {
icon = com.android.internal.R.drawable.ic_volume;
}
- mRingerStreamIcon.setImageResource(icon);
+ mLargeStreamIcon.setImageResource(icon);
}
protected void onFreeResources() {
// We'll keep the views, just ditch the cached drawable and hence
// bitmaps
- mOtherStreamIcon.setImageDrawable(null);
- mRingerStreamIcon.setImageDrawable(null);
+ mSmallStreamIcon.setImageDrawable(null);
+ mLargeStreamIcon.setImageDrawable(null);
synchronized (this) {
for (int i = mToneGenerators.length - 1; i >= 0; i--) {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 4aeab2d..a68436b 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -24,6 +24,7 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
+import android.util.Log;
/**
* Abstract base class for a top-level window look and behavior policy. An
@@ -106,6 +107,8 @@ public abstract class Window {
private boolean mHaveWindowFormat = false;
private int mDefaultWindowFormat = PixelFormat.OPAQUE;
+ private boolean mHasSoftInputMode = false;
+
// The current window attributes.
private final WindowManager.LayoutParams mWindowAttributes =
new WindowManager.LayoutParams();
@@ -382,11 +385,7 @@ public abstract class Window {
&& mAppName != null) {
wp.setTitle(mAppName);
}
- if (wp.windowAnimations == 0) {
- wp.windowAnimations = getWindowStyle().getResourceId(
- com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
- }
- }
+ }
if (wp.packageName == null) {
wp.packageName = mContext.getPackageName();
}
@@ -526,6 +525,26 @@ public abstract class Window {
}
/**
+ * Specify an explicit soft input mode to use for the window, as per
+ * {@link WindowManager.LayoutParams#softInputMode
+ * WindowManager.LayoutParams.softInputMode}. Providing anything besides
+ * "unspecified" here will override the input mode the window would
+ * normally retrieve from its theme.
+ */
+ public void setSoftInputMode(int mode) {
+ final WindowManager.LayoutParams attrs = getAttributes();
+ if (mode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
+ attrs.softInputMode = mode;
+ mHasSoftInputMode = true;
+ } else {
+ mHasSoftInputMode = false;
+ }
+ if (mCallback != null) {
+ mCallback.onWindowAttributesChanged(attrs);
+ }
+ }
+
+ /**
* Convenience function to set the flag bits as specified in flags, as
* per {@link #setFlags}.
* @param flags The flag bits to be set.
@@ -572,14 +591,17 @@ public abstract class Window {
}
/**
- * Specify custom window attributes.
+ * Specify custom window attributes. <strong>PLEASE NOTE:</strong> the
+ * layout params you give here should generally be from values previously
+ * retrieved with {@link #getAttributes()}; you probably do not want to
+ * blindly create and apply your own, since this will blow away any values
+ * set by the framework that you are not interested in.
*
* @param a The new window attributes, which will completely override any
* current values.
*/
public void setAttributes(WindowManager.LayoutParams a) {
mWindowAttributes.copyFrom(a);
- mForcedWindowFlags = 0xffffffff;
if (mCallback != null) {
mCallback.onWindowAttributesChanged(mWindowAttributes);
}
@@ -604,6 +626,13 @@ public abstract class Window {
}
/**
+ * Has the app specified their own soft input mode?
+ */
+ protected final boolean hasSoftInputMode() {
+ return mHasSoftInputMode;
+ }
+
+ /**
* Enable extended screen features. This must be called before
* setContentView(). May be called as many times as desired as long as it
* is before setContentView(). If not called, no extended features
@@ -926,8 +955,7 @@ public abstract class Window {
* @see #setFormat
* @see PixelFormat
*/
- protected void setDefaultWindowFormat(int format)
- {
+ protected void setDefaultWindowFormat(int format) {
mDefaultWindowFormat = format;
if (!mHaveWindowFormat) {
final WindowManager.LayoutParams attrs = getAttributes();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 4c1dec5..7d202aa 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -125,6 +125,9 @@ public interface WindowManager extends ViewManager {
* @see #TYPE_APPLICATION_PANEL
* @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
@@ -133,6 +136,12 @@ public interface WindowManager extends ViewManager {
* @see #TYPE_TOAST
* @see #TYPE_SYSTEM_OVERLAY
* @see #TYPE_PRIORITY_PHONE
+ * @see #TYPE_STATUS_BAR_PANEL
+ * @see #TYPE_SYSTEM_DIALOG
+ * @see #TYPE_KEYGUARD_DIALOG
+ * @see #TYPE_SYSTEM_ERROR
+ * @see #TYPE_INPUT_METHOD
+ * @see #TYPE_INPUT_METHOD_DIALOG
*/
public int type;
@@ -193,12 +202,18 @@ public interface WindowManager extends ViewManager {
* {@link #TYPE_APPLICATION_PANEL} panels.
*/
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;
-
+
+ /** Window type: like {@link #TYPE_APPLICATION_PANEL}, but layout
+ * of the window happens as that of a top-level window, <em>not</em>
+ * as a child of its container.
+ */
+ public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
+
/**
* End of types of sub-windows.
*/
public static final int LAST_SUB_WINDOW = 1999;
-
+
/**
* Start of system-specific window types. These are not normally
* created by applications.
@@ -278,10 +293,23 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;
/**
+ * Window type: internal input methods windows, which appear above
+ * the normal UI. Application windows may be resized or panned to keep
+ * the input focus visible while this window is displayed.
+ */
+ public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
+
+ /**
+ * Window type: internal input methods dialog windows, which appear above
+ * the current input method window.
+ */
+ public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
+
+ /**
* End of types of system windows.
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
-
+
/**
* Specifies what type of memory buffers should be used by this window.
* Default is normal.
@@ -330,7 +358,19 @@ public interface WindowManager extends ViewManager {
/** Window flag: blur everything behind this window. */
public static final int FLAG_BLUR_BEHIND = 0x00000004;
- /** Window flag: this window won't ever get focus. */
+ /** Window flag: this window won't ever get key input focus, so the
+ * user can not send key or other button events to it. Those will
+ * instead go to whatever focusable window is behind it. This flag
+ * will also enable {@link #FLAG_NOT_TOUCH_MODAL} whether or not that
+ * is explicitly set.
+ *
+ * <p>Setting this flag also implies that the window will not need to
+ * interact with
+ * a soft input method, so it will be Z-ordered and positioned
+ * independently of any active input method (typically this means it
+ * gets Z-ordered on top of the input method, so it can use the full
+ * screen for its content and cover the input method if needed. You
+ * can use {@link #FLAG_ALT_FOCUSABLE_IM} to modify this behavior. */
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
/** Window flag: this window can never receive touch events. */
@@ -405,6 +445,33 @@ public interface WindowManager extends ViewManager {
* set for you by Window as described in {@link Window#setFlags}.*/
public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
+ /** Window flag: invert the state of {@link #FLAG_NOT_FOCUSABLE} with
+ * respect to how this window interacts with the current method. That
+ * is, if FLAG_NOT_FOCUSABLE is set and this flag is set, then the
+ * window will behave as if it needs to interact with the input method
+ * and thus be placed behind/away from it; if FLAG_NOT_FOCUSABLE is
+ * not set and this flag is set, then the window will behave as if it
+ * doesn't need to interact with the input method and can be placed
+ * to use more space and cover the input method.
+ */
+ public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
+
+ /** Window flag: if you have set {@link #FLAG_NOT_TOUCH_MODAL}, you
+ * can set this flag to receive a single special MotionEvent with
+ * the action
+ * {@link MotionEvent#ACTION_OUTSIDE MotionEvent.ACTION_OUTSIDE} for
+ * touches that occur outside of your window. Note that you will not
+ * receive the full down/move/up gesture, only the location of the
+ * first down as an ACTION_OUTSIDE.
+ */
+ public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
+
+ /** Window flag: set when this window was created from the restored
+ * state of a previous window, indicating this is not the first time
+ * the user has navigated to it.
+ */
+ public static final int FLAG_RESTORED_STATE = 0x00080000;
+
/** Window flag: a special option intended for system dialogs. When
* this flag is set, the window will demand focus unconditionally when
* it is created.
@@ -412,6 +479,90 @@ public interface WindowManager extends ViewManager {
public static final int FLAG_SYSTEM_ERROR = 0x40000000;
/**
+ * Mask for {@link #softInputMode} of the bits that determine the
+ * desired visibility state of the soft input area for this window.
+ */
+ public static final int SOFT_INPUT_MASK_STATE = 0x0f;
+
+ /**
+ * Visibility state for {@link #softInputMode}: no state has been specified.
+ */
+ public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
+
+ /**
+ * Visibility state for {@link #softInputMode}: please don't change the state of
+ * the soft input area.
+ */
+ public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
+
+ /**
+ * Visibility state for {@link #softInputMode}: please hide any soft input
+ * area.
+ */
+ public static final int SOFT_INPUT_STATE_HIDDEN = 2;
+
+ /**
+ * Visibility state for {@link #softInputMode}: please show the soft input area
+ * the first time the window is shown.
+ */
+ public static final int SOFT_INPUT_STATE_FIRST_VISIBLE = 3;
+
+ /**
+ * Visibility state for {@link #softInputMode}: please always show the soft
+ * input area.
+ */
+ public static final int SOFT_INPUT_STATE_VISIBLE = 4;
+
+ /**
+ * Mask for {@link #softInputMode} of the bits that determine the
+ * way that the window should be adjusted to accomodate the soft
+ * input window.
+ */
+ public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
+
+ /** Adjustment option for {@link #softInputMode}: nothing specified.
+ * The system will try to pick one or
+ * the other depending on the contents of the window.
+ */
+ public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
+
+ /** Adjustment option for {@link #softInputMode}: set to allow the
+ * window to be resized when an input
+ * method is shown, so that its contents are not covered by the input
+ * method. This can <em>not<em> be combined with
+ * {@link #SOFT_INPUT_ADJUST_PAN}; if
+ * neither of these are set, then the system will try to pick one or
+ * the other depending on the contents of the window.
+ */
+ public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
+
+ /** Adjustment option for {@link #softInputMode}: set to have a window
+ * pan when an input method is
+ * shown, so it doesn't need to deal with resizing but just panned
+ * by the framework to ensure the current input focus is visible. This
+ * can <em>not<em> be combined with {@link #SOFT_INPUT_ADJUST_RESIZE}; if
+ * neither of these are set, then the system will try to pick one or
+ * the other depending on the contents of the window.
+ */
+ public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
+
+ /**
+ * Desired operating mode for any soft input area. May any combination
+ * of:
+ *
+ * <ul>
+ * <li> One of the visibility states
+ * {@link #SOFT_INPUT_STATE_UNSPECIFIED}, {@link #SOFT_INPUT_STATE_UNCHANGED},
+ * {@link #SOFT_INPUT_STATE_HIDDEN}, {@link #SOFT_INPUT_STATE_FIRST_VISIBLE}, or
+ * {@link #SOFT_INPUT_STATE_VISIBLE}.
+ * <li> One of the adjustment options
+ * {@link #SOFT_INPUT_ADJUST_UNSPECIFIED},
+ * {@link #SOFT_INPUT_ADJUST_RESIZE}, or
+ * {@link #SOFT_INPUT_ADJUST_PAN}.
+ */
+ public int softInputMode;
+
+ /**
* Placement of window within the screen as per {@link Gravity}
*
* @see Gravity
@@ -533,6 +684,7 @@ public interface WindowManager extends ViewManager {
out.writeInt(type);
out.writeInt(memoryType);
out.writeInt(flags);
+ out.writeInt(softInputMode);
out.writeInt(gravity);
out.writeFloat(horizontalMargin);
out.writeFloat(verticalMargin);
@@ -565,6 +717,7 @@ public interface WindowManager extends ViewManager {
type = in.readInt();
memoryType = in.readInt();
flags = in.readInt();
+ softInputMode = in.readInt();
gravity = in.readInt();
horizontalMargin = in.readFloat();
verticalMargin = in.readFloat();
@@ -586,6 +739,7 @@ public interface WindowManager extends ViewManager {
public static final int TITLE_CHANGED = 1<<6;
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 final int copyFrom(LayoutParams o) {
int changes = 0;
@@ -606,6 +760,22 @@ public interface WindowManager extends ViewManager {
y = o.y;
changes |= LAYOUT_CHANGED;
}
+ if (horizontalWeight != o.horizontalWeight) {
+ horizontalWeight = o.horizontalWeight;
+ changes |= LAYOUT_CHANGED;
+ }
+ if (verticalWeight != o.verticalWeight) {
+ verticalWeight = o.verticalWeight;
+ changes |= LAYOUT_CHANGED;
+ }
+ if (horizontalMargin != o.horizontalMargin) {
+ horizontalMargin = o.horizontalMargin;
+ changes |= LAYOUT_CHANGED;
+ }
+ if (verticalMargin != o.verticalMargin) {
+ verticalMargin = o.verticalMargin;
+ changes |= LAYOUT_CHANGED;
+ }
if (type != o.type) {
type = o.type;
changes |= TYPE_CHANGED;
@@ -618,6 +788,10 @@ public interface WindowManager extends ViewManager {
flags = o.flags;
changes |= FLAGS_CHANGED;
}
+ if (softInputMode != o.softInputMode) {
+ softInputMode = o.softInputMode;
+ changes |= SOFT_INPUT_MODE_CHANGED;
+ }
if (gravity != o.gravity) {
gravity = o.gravity;
changes |= LAYOUT_CHANGED;
@@ -677,15 +851,37 @@ public interface WindowManager extends ViewManager {
@Override
public String toString() {
- return "WM.LayoutParams{"
- + Integer.toHexString(System.identityHashCode(this))
- + " (" + x + "," + y + ")("
- + (width==FILL_PARENT?"fill_parent":(width==WRAP_CONTENT?"wrap_content":width))
- + "x"
- + (height==FILL_PARENT?"fill_parent":(height==WRAP_CONTENT?"wrap_content":height))
- + ") gr=#" + Integer.toHexString(gravity)
- + " ty=" + type + " fl=#" + Integer.toHexString(flags)
- + " fmt=" + format + "}";
+ StringBuilder sb = new StringBuilder(256);
+ sb.append("WM.LayoutParams{");
+ sb.append("(");
+ sb.append(x);
+ sb.append(',');
+ sb.append(y);
+ sb.append(")(");
+ sb.append((width==FILL_PARENT?"fill":(width==WRAP_CONTENT?"wrap":width)));
+ sb.append('x');
+ sb.append((height==FILL_PARENT?"fill":(height==WRAP_CONTENT?"wrap":height)));
+ sb.append(")");
+ if (softInputMode != 0) {
+ sb.append(" sim=#");
+ sb.append(Integer.toHexString(softInputMode));
+ }
+ if (gravity != 0) {
+ sb.append(" gr=#");
+ sb.append(Integer.toHexString(gravity));
+ }
+ sb.append(" ty=");
+ sb.append(type);
+ sb.append(" fl=#");
+ sb.append(Integer.toHexString(flags));
+ sb.append(" fmt=");
+ sb.append(format);
+ if (windowAnimations != 0) {
+ sb.append(" wanim=0x");
+ sb.append(Integer.toHexString(windowAnimations));
+ }
+ sb.append('}');
+ return sb.toString();
}
private CharSequence mTitle = "";
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index fbecf46..755d7b8 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -22,6 +22,7 @@ import android.util.AndroidRuntimeException;
import android.util.Config;
import android.util.Log;
import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
final class WindowLeaked extends AndroidRuntimeException {
public WindowLeaked(String msg) {
@@ -128,7 +129,7 @@ public class WindowManagerImpl implements WindowManager {
root.mAddNesting++;
// Update layout parameters.
view.setLayoutParams(wparams);
- root.setLayoutParams(wparams);
+ root.setLayoutParams(wparams, true);
return;
}
@@ -144,7 +145,7 @@ public class WindowManagerImpl implements WindowManager {
}
}
- root = new ViewRoot();
+ root = new ViewRoot(view.getContext());
root.mAddNesting = 1;
view.setLayoutParams(wparams);
@@ -191,7 +192,7 @@ public class WindowManagerImpl implements WindowManager {
int index = findViewLocked(view, true);
ViewRoot root = mRoots[index];
mParams[index] = wparams;
- root.setLayoutParams(wparams);
+ root.setLayoutParams(wparams, false);
}
}
@@ -236,6 +237,10 @@ public class WindowManagerImpl implements WindowManager {
return view;
}
+ InputMethodManager imm = InputMethodManager.getInstance(view.getContext());
+ if (imm != null) {
+ imm.windowDismissed(mViews[index].getWindowToken());
+ }
root.die(false);
finishRemoveViewLocked(view, index);
return view;
@@ -294,6 +299,26 @@ public class WindowManagerImpl implements WindowManager {
}
}
+ public WindowManager.LayoutParams getRootViewLayoutParameter(View view) {
+ ViewParent vp = view.getParent();
+ while (vp != null && !(vp instanceof ViewRoot)) {
+ vp = vp.getParent();
+ }
+
+ if (vp == null) return null;
+
+ ViewRoot vr = (ViewRoot)vp;
+
+ int N = mRoots.length;
+ for (int i = 0; i < N; ++i) {
+ if (mRoots[i] == vr) {
+ return mParams[i];
+ }
+ }
+
+ return null;
+ }
+
public void closeAll() {
closeAll(null, null, null);
}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 93e1a0b..a6def51 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -109,45 +109,91 @@ public interface WindowManagerPolicy {
* getFrame() if so desired. Must be called with the window manager
* lock held.
*
- * @param parentLeft The left edge of the parent container this window
- * is in, used for computing its position.
- * @param parentTop The top edge of the parent container this window
- * is in, used for computing its position.
- * @param parentRight The right edge of the parent container this window
- * is in, used for computing its position.
- * @param parentBottom The bottom edge of the parent container this window
- * is in, used for computing its position.
- * @param displayLeft The left edge of the available display, used
- * for constraining the overall dimensions of the window.
- * @param displayTop The left edge of the available display, used
- * for constraining the overall dimensions of the window.
- * @param displayRight The right edge of the available display, used
- * for constraining the overall dimensions of the window.
- * @param displayBottom The bottom edge of the available display, used
- * for constraining the overall dimensions of the window.
+ * @param parentFrame The frame of the parent container this window
+ * is in, used for computing its basic position.
+ * @param displayFrame The frame of the overall display in which this
+ * window can appear, used for constraining the overall dimensions
+ * of the window.
+ * @param contentFrame The frame within the display in which we would
+ * like active content to appear. This will cause windows behind to
+ * be resized to match the given content frame.
+ * @param visibleFrame The frame within the display that the window
+ * is actually visible, used for computing its visible insets to be
+ * given to windows behind.
+ * This can be used as a hint for scrolling (avoiding resizing)
+ * the window to make certain that parts of its content
+ * are visible.
*/
- public void computeFrameLw(int parentLeft, int parentRight, int parentBottom,
- int parentHeight, int displayLeft, int displayTop,
- int displayRight, int displayBottom);
+ public void computeFrameLw(Rect parentFrame, Rect displayFrame,
+ Rect contentFrame, Rect visibleFrame);
/**
- * Set the window's frame to an exact value. Must be called with the
+ * Retrieve the current frame of the window. Must be called with the
* window manager lock held.
*
- * @param left Left edge of the window.
- * @param top Top edge of the window.
- * @param right Right edge (exclusive) of the window.
- * @param bottom Bottom edge (exclusive) of the window.
+ * @return Rect The rectangle holding the window frame.
*/
- public void setFrameLw(int left, int top, int right, int bottom);
+ public Rect getFrameLw();
/**
- * Retrieve the current frame of the window. Must be called with the
+ * Retrieve the frame of the display that this window was last
+ * laid out in. Must be called with the
* window manager lock held.
*
- * @return Rect The rectangle holding the window frame.
+ * @return Rect The rectangle holding the display frame.
*/
- public Rect getFrameLw();
+ public Rect getDisplayFrameLw();
+
+ /**
+ * Retrieve the frame of the content area that this window was last
+ * laid out in. This is the area in which the content of the window
+ * should be placed. It will be smaller than the display frame to
+ * account for screen decorations such as a status bar or soft
+ * keyboard. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The rectangle holding the content frame.
+ */
+ public Rect getContentFrameLw();
+
+ /**
+ * Retrieve the frame of the visible area that this window was last
+ * laid out in. This is the area of the screen in which the window
+ * will actually be fully visible. It will be smaller than the
+ * content frame to account for transient UI elements blocking it
+ * such as an input method's candidates UI. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The rectangle holding the visible frame.
+ */
+ public Rect getVisibleFrameLw();
+
+ /**
+ * Returns true if this window is waiting to receive its given
+ * internal insets from the client app, and so should not impact the
+ * layout of other windows.
+ */
+ public boolean getGivenInsetsPendingLw();
+
+ /**
+ * Retrieve the insets given by this window's client for the content
+ * area of windows behind it. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The left, top, right, and bottom insets, relative
+ * to the window's frame, of the actual contents.
+ */
+ public Rect getGivenContentInsetsLw();
+
+ /**
+ * Retrieve the insets given by this window's client for the visible
+ * area of windows behind it. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The left, top, right, and bottom insets, relative
+ * to the window's frame, of the actual visible area.
+ */
+ public Rect getGivenVisibleInsetsLw();
/**
* Retrieve the current LayoutParams of the window.
@@ -158,6 +204,11 @@ public interface WindowManagerPolicy {
public WindowManager.LayoutParams getAttrs();
/**
+ * Get the layer at which this window's surface will be Z-ordered.
+ */
+ public int getSurfaceLayer();
+
+ /**
* Return the token for the application (actually activity) that owns
* this window. May return null for system windows.
*
@@ -240,18 +291,6 @@ public interface WindowManagerPolicy {
* lock held.
*/
public void showLw();
-
- /**
- * Sets insets on the window that represent the area within the window that is covered
- * by system windows (e.g. status bar). Must be called with the window
- * manager lock held.
- *
- * @param l
- * @param t
- * @param r
- * @param b
- */
- public void setCoveredInsetsLw(int l, int t, int r, int b);
}
/** No transition happening. */
@@ -506,14 +545,15 @@ public interface WindowManagerPolicy {
/**
- * Return the insets for the areas covered by system windows. These values are computed on the
- * mose recent layout, so they are not guaranteed to be correct.
+ * Return the insets for the areas covered by system windows. These values
+ * are computed on the most recent layout, so they are not guaranteed to
+ * be correct.
*
* @param attrs The LayoutParams of the window.
- * @param coveredInset The areas covered by system windows, expressed as positive insets
+ * @param contentInset The areas covered by system windows, expressed as positive insets
*
*/
- public void getCoveredInsetHintLw(WindowManager.LayoutParams attrs, Rect coveredInset);
+ public void getContentInsetHintLw(WindowManager.LayoutParams attrs, Rect contentInset);
/**
* Called when layout of the windows is finished. After this function has
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 39fe561..9f3650b 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -582,6 +582,16 @@ public abstract class Animation {
}
/**
+ * Compute a hint at how long the entire animation may last, in milliseconds.
+ * Animations can be written to cause themselves to run for a different
+ * duration than what is computed here, but generally this should be
+ * accurate.
+ */
+ public long computeDurationHint() {
+ return (getStartOffset() + getDuration()) * (getRepeatCount() + 1);
+ }
+
+ /**
* Gets the transformation to apply at a specified point in time. Implementations of this
* method should always replace the specified Transformation or document they are doing
* otherwise.
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 3c5920f..688da70 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -224,6 +224,23 @@ public class AnimationSet extends Animation {
}
/**
+ * The duration hint of an animation set is the maximum of the duration
+ * hints of all of its component animations.
+ *
+ * @see android.view.animation.Animation#computeDurationHint
+ */
+ public long computeDurationHint() {
+ long duration = 0;
+ final int count = mAnimations.size();
+ final ArrayList<Animation> animations = mAnimations;
+ for (int i = count - 1; i >= 0; --i) {
+ final long d = animations.get(i).computeDurationHint();
+ if (d > duration) duration = d;
+ }
+ return duration;
+ }
+
+ /**
* The transformation of an animation set is the concatenation of all of its
* component animations.
*
diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java
index c7a0cc8..f9e85bf 100644
--- a/core/java/android/view/animation/Transformation.java
+++ b/core/java/android/view/animation/Transformation.java
@@ -134,6 +134,14 @@ public class Transformation {
@Override
public String toString() {
- return "Transformation{alpha=" + mAlpha + " matrix=" + mMatrix + "}";
+ return "Transformation{alpha=" + mAlpha + " matrix="
+ + mMatrix.toShortString() + "}";
+ }
+
+ /**
+ * Return a string representation of the transformation in a compact form.
+ */
+ public String toShortString() {
+ return "{alpha=" + mAlpha + " matrix=" + mMatrix.toShortString() + "}";
}
}
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
new file mode 100644
index 0000000..4416ee5
--- /dev/null
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -0,0 +1,98 @@
+/*
+ * 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.inputmethod;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewRoot;
+
+/**
+ * Base class for implementors of the InputConnection interface, taking care
+ * of implementing common system-oriented parts of the functionality.
+ */
+public abstract class BaseInputConnection implements InputConnection {
+ final InputMethodManager mIMM;
+ final Handler mH;
+ final View mTargetView;
+
+ BaseInputConnection(InputMethodManager mgr) {
+ mIMM = mgr;
+ mTargetView = null;
+ mH = null;
+ }
+
+ public BaseInputConnection(View targetView) {
+ mIMM = (InputMethodManager)targetView.getContext().getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ mH = targetView.getHandler();
+ mTargetView = targetView;
+ }
+
+ /**
+ * Provides standard implementation for sending a key event to the window
+ * attached to the input connection's view.
+ */
+ public boolean sendKeyEvent(KeyEvent event) {
+ synchronized (mIMM.mH) {
+ Handler h = mH;
+ if (h == null) {
+ if (mIMM.mServedView != null) {
+ h = mIMM.mServedView.getHandler();
+ }
+ }
+ if (h != null && mTargetView != null) {
+ h.post(new DispatchKey(event, mTargetView.getRootView()));
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Provides standard implementation for hiding the status icon associated
+ * with the current input method.
+ */
+ public boolean hideStatusIcon() {
+ mIMM.updateStatusIcon(0, null);
+ 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;
+ }
+
+ static class DispatchKey implements Runnable {
+ KeyEvent mEvent;
+ View mView;
+
+ DispatchKey(KeyEvent event, View v) {
+ mEvent = event;
+ mView = v;
+ }
+
+ public void run() {
+ mView.dispatchKeyEvent(mEvent);
+ }
+ }
+} \ No newline at end of file
diff --git a/core/java/android/view/inputmethod/CompletionInfo.aidl b/core/java/android/view/inputmethod/CompletionInfo.aidl
new file mode 100644
index 0000000..e601054
--- /dev/null
+++ b/core/java/android/view/inputmethod/CompletionInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+parcelable CompletionInfo;
diff --git a/core/java/android/view/inputmethod/CompletionInfo.java b/core/java/android/view/inputmethod/CompletionInfo.java
new file mode 100644
index 0000000..3a8fe72
--- /dev/null
+++ b/core/java/android/view/inputmethod/CompletionInfo.java
@@ -0,0 +1,131 @@
+/*
+ * 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information about a single text completion that an editor has reported to
+ * an input method.
+ */
+public final class CompletionInfo implements Parcelable {
+ static final String TAG = "CompletionInfo";
+
+ final long mId;
+ final int mPosition;
+ final CharSequence mText;
+ final CharSequence mLabel;
+
+ /**
+ * Create a simple completion with just text, no label.
+ */
+ public CompletionInfo(long id, int index, CharSequence text) {
+ mId = id;
+ mPosition = index;
+ mText = text;
+ mLabel = null;
+ }
+
+ /**
+ * Create a full completion with both text and label.
+ */
+ public CompletionInfo(long id, int index, CharSequence text, CharSequence label) {
+ mId = id;
+ mPosition = index;
+ mText = text;
+ mLabel = label;
+ }
+
+ CompletionInfo(Parcel source) {
+ mId = source.readLong();
+ mPosition = source.readInt();
+ mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ }
+
+ /**
+ * Return the abstract identifier for this completion, typically
+ * corresponding to the id associated with it in the original adapter.
+ */
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * Return the original position of this completion, typically
+ * corresponding to its position in the original adapter.
+ */
+ public int getPosition() {
+ return mPosition;
+ }
+
+ /**
+ * Return the actual text associated with this completion. This is the
+ * real text that will be inserted into the editor if the user selects it.
+ */
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * Return the user-visible label for the completion, or null if the plain
+ * text should be shown. If non-null, this will be what the user sees as
+ * the completion option instead of the actual text.
+ */
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ @Override
+ public String toString() {
+ return "CompletionInfo{#" + mPosition + " \"" + mText
+ + "\" id=" + mId + " label=" + mLabel + "}";
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mId);
+ dest.writeInt(mPosition);
+ TextUtils.writeToParcel(mText, dest, flags);
+ TextUtils.writeToParcel(mLabel, dest, flags);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<CompletionInfo> CREATOR
+ = new Parcelable.Creator<CompletionInfo>() {
+ public CompletionInfo createFromParcel(Parcel source) {
+ return new CompletionInfo(source);
+ }
+
+ public CompletionInfo[] newArray(int size) {
+ return new CompletionInfo[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/view/inputmethod/DefaultInputMethod.java b/core/java/android/view/inputmethod/DefaultInputMethod.java
new file mode 100644
index 0000000..da5cab5
--- /dev/null
+++ b/core/java/android/view/inputmethod/DefaultInputMethod.java
@@ -0,0 +1,239 @@
+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) {
+ }
+
+ 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() {
+ }
+}
+
+// ----------------------------------------------------------------------
+
+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) {
+ mSession.updateSelection(oldSelStart, oldSelEnd,
+ newSelStart, newSelEnd);
+ }
+
+ 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() {
+ }
+
+ public void hideSoftInput() {
+ }
+}
diff --git a/core/java/android/view/inputmethod/EditorInfo.aidl b/core/java/android/view/inputmethod/EditorInfo.aidl
new file mode 100644
index 0000000..48068f0
--- /dev/null
+++ b/core/java/android/view/inputmethod/EditorInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+parcelable EditorInfo;
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
new file mode 100644
index 0000000..c050691
--- /dev/null
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -0,0 +1,126 @@
+package android.view.inputmethod;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.InputType;
+import android.text.TextUtils;
+
+/**
+ * An EditorInfo describes several attributes of a text editing object
+ * that an input method is communicating with (typically an EditText), most
+ * importantly the type of text content it contains.
+ */
+public class EditorInfo implements InputType, Parcelable {
+ /**
+ * The content type of the text box, whose bits are defined by
+ * {@link InputType}.
+ *
+ * @see InputType
+ * @see #TYPE_MASK_CLASS
+ * @see #TYPE_MASK_VARIATION
+ * @see #TYPE_MASK_FLAGS
+ */
+ public int inputType = TYPE_CLASS_TEXT;
+
+ /**
+ * A string supplying additional information about the content type that
+ * is 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}
+ * attribute of a TextView.
+ */
+ public String privateContentType = null;
+
+ /**
+ * The text offset of the start of the selection at the time editing
+ * began; -1 if not known.
+ */
+ public int initialSelStart = -1;
+
+ /**
+ * The text offset of the end of the selection at the time editing
+ * began; -1 if not known.
+ */
+ public int initialSelEnd = -1;
+
+ /**
+ * The capitalization mode of the first character being edited in the
+ * text. Values may be any combination of
+ * {@link TextUtils#CAP_MODE_CHARACTERS TextUtils.CAP_MODE_CHARACTERS},
+ * {@link TextUtils#CAP_MODE_WORDS TextUtils.CAP_MODE_WORDS}, and
+ * {@link TextUtils#CAP_MODE_SENTENCES TextUtils.CAP_MODE_SENTENCES}, though
+ * you should generally just take a non-zero value to mean start out in
+ * caps mode.
+ */
+ public int initialCapsMode = 0;
+
+ /**
+ * The "hint" text of the text view, typically shown in-line when the
+ * text is empty to tell the user what to enter.
+ */
+ public CharSequence hintText;
+
+ /**
+ * A label to show to the user describing the text they are writing.
+ */
+ public CharSequence label;
+
+ /**
+ * 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
+ * that they don't conflict with others. This field is can be
+ * filled in from the {@link android.R.attr#editorExtras}
+ * attribute of a TextView.
+ */
+ public Bundle extras;
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(inputType);
+ dest.writeString(privateContentType);
+ dest.writeInt(initialSelStart);
+ dest.writeInt(initialSelEnd);
+ dest.writeInt(initialCapsMode);
+ TextUtils.writeToParcel(hintText, dest, flags);
+ TextUtils.writeToParcel(label, dest, flags);
+ dest.writeBundle(extras);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<EditorInfo> CREATOR = new Parcelable.Creator<EditorInfo>() {
+ public EditorInfo createFromParcel(Parcel source) {
+ EditorInfo res = new EditorInfo();
+ res.inputType = source.readInt();
+ res.privateContentType = source.readString();
+ 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.extras = source.readBundle();
+ return res;
+ }
+
+ public EditorInfo[] newArray(int size) {
+ return new EditorInfo[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+}
diff --git a/core/java/android/view/inputmethod/ExtractedText.aidl b/core/java/android/view/inputmethod/ExtractedText.aidl
new file mode 100644
index 0000000..95e56d7
--- /dev/null
+++ b/core/java/android/view/inputmethod/ExtractedText.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+parcelable ExtractedText;
diff --git a/core/java/android/view/inputmethod/ExtractedText.java b/core/java/android/view/inputmethod/ExtractedText.java
new file mode 100644
index 0000000..0ca3c79
--- /dev/null
+++ b/core/java/android/view/inputmethod/ExtractedText.java
@@ -0,0 +1,82 @@
+package android.view.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information about text that has been extracted for use by an input method.
+ */
+public class ExtractedText implements Parcelable {
+ /**
+ * The text that has been extracted.
+ */
+ public CharSequence text;
+
+ /**
+ * The offset in the overall text at which the extracted text starts.
+ */
+ public int startOffset;
+
+ /**
+ * The offset where the selection currently starts within the extracted
+ * text. The real selection start position is at
+ * <var>startOffset</var>+<var>selectionStart</var>.
+ */
+ public int selectionStart;
+
+ /**
+ * The offset where the selection currently ends within the extracted
+ * text. The real selection end position is at
+ * <var>startOffset</var>+<var>selectionEnd</var>.
+ */
+ public int selectionEnd;
+
+ /**
+ * Bit for {@link #flags}: set if the text being edited can only be on
+ * a single line.
+ */
+ public static final int FLAG_SINGLE_LINE = 0x0001;
+
+ /**
+ * Additional bit flags of information about the edited text.
+ */
+ public int flags;
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ TextUtils.writeToParcel(text, dest, flags);
+ dest.writeInt(startOffset);
+ dest.writeInt(selectionStart);
+ dest.writeInt(selectionEnd);
+ dest.writeInt(flags);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<ExtractedText> CREATOR = new Parcelable.Creator<ExtractedText>() {
+ public ExtractedText createFromParcel(Parcel source) {
+ ExtractedText res = new ExtractedText();
+ res.text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ res.startOffset = source.readInt();
+ res.selectionStart = source.readInt();
+ res.selectionEnd = source.readInt();
+ res.flags = source.readInt();
+ return res;
+ }
+
+ public ExtractedText[] newArray(int size) {
+ return new ExtractedText[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/view/inputmethod/ExtractedTextRequest.aidl b/core/java/android/view/inputmethod/ExtractedTextRequest.aidl
new file mode 100644
index 0000000..c69acc7
--- /dev/null
+++ b/core/java/android/view/inputmethod/ExtractedTextRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+parcelable ExtractedTextRequest;
diff --git a/core/java/android/view/inputmethod/ExtractedTextRequest.java b/core/java/android/view/inputmethod/ExtractedTextRequest.java
new file mode 100644
index 0000000..d962329
--- /dev/null
+++ b/core/java/android/view/inputmethod/ExtractedTextRequest.java
@@ -0,0 +1,61 @@
+package android.view.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Description of what an input method would like from an application when
+ * extract text from its input editor.
+ */
+public class ExtractedTextRequest implements Parcelable {
+ /**
+ * Arbitrary integer that can be supplied in the request, which will be
+ * delivered back when reporting updates.
+ */
+ public int token;
+
+ /**
+ * Hint for the maximum number of lines to return.
+ */
+ public int hintMaxLines;
+
+ /**
+ * Hint for the maximum number of characters to return.
+ */
+ public int hintMaxChars;
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(token);
+ dest.writeInt(hintMaxLines);
+ dest.writeInt(hintMaxChars);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<ExtractedTextRequest> CREATOR
+ = new Parcelable.Creator<ExtractedTextRequest>() {
+ public ExtractedTextRequest createFromParcel(Parcel source) {
+ ExtractedTextRequest res = new ExtractedTextRequest();
+ res.token = source.readInt();
+ res.hintMaxLines = source.readInt();
+ res.hintMaxChars = source.readInt();
+ return res;
+ }
+
+ public ExtractedTextRequest[] newArray(int size) {
+ return new ExtractedTextRequest[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/view/inputmethod/InputBinding.aidl b/core/java/android/view/inputmethod/InputBinding.aidl
new file mode 100644
index 0000000..ea09d8b
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputBinding.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+parcelable InputBinding;
diff --git a/core/java/android/view/inputmethod/InputBinding.java b/core/java/android/view/inputmethod/InputBinding.java
new file mode 100644
index 0000000..f4209ef
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputBinding.java
@@ -0,0 +1,152 @@
+/*
+ * 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;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Information given to an {@link InputMethod} about a client connecting
+ * to it.
+ */
+public final class InputBinding implements Parcelable {
+ static final String TAG = "InputBinding";
+
+ /**
+ * The connection back to the client.
+ */
+ final InputConnection mConnection;
+
+ /**
+ * A remotable token for the connection back to the client.
+ */
+ final IBinder mConnectionToken;
+
+ /**
+ * The UID where this binding came from.
+ */
+ final int mUid;
+
+ /**
+ * The PID where this binding came from.
+ */
+ final int mPid;
+
+ /**
+ * Constructor.
+ *
+ * @param conn The interface for communicating back with the application.
+ * @param connToken A remoteable token for communicating across processes.
+ * @param uid The user id of the client of this binding.
+ * @param pid The process id of where the binding came from.
+ */
+ public InputBinding(InputConnection conn, IBinder connToken,
+ int uid, int pid) {
+ mConnection = conn;
+ mConnectionToken = connToken;
+ mUid = uid;
+ mPid = pid;
+ }
+
+ /**
+ * Constructor from an existing InputBinding taking a new local input
+ * connection interface.
+ *
+ * @param conn The new connection interface.
+ * @param binding Existing binding to copy.
+ */
+ public InputBinding(InputConnection conn, InputBinding binding) {
+ mConnection = conn;
+ mConnectionToken = binding.getConnectionToken();
+ mUid = binding.getUid();
+ mPid = binding.getPid();
+ }
+
+ InputBinding(Parcel source) {
+ mConnection = null;
+ mConnectionToken = source.readStrongBinder();
+ mUid = source.readInt();
+ mPid = source.readInt();
+ }
+
+ /**
+ * Return the connection for interacting back with the application.
+ */
+ public InputConnection getConnection() {
+ return mConnection;
+ }
+
+ /**
+ * Return the token for the connection back to the application. You can
+ * not use this directly, it must be converted to a {@link InputConnection}
+ * for you.
+ */
+ public IBinder getConnectionToken() {
+ return mConnectionToken;
+ }
+
+ /**
+ * Return the user id of the client associated with this binding.
+ */
+ public int getUid() {
+ return mUid;
+ }
+
+ /**
+ * Return the process id where this binding came from.
+ */
+ public int getPid() {
+ return mPid;
+ }
+
+ @Override
+ public String toString() {
+ return "InputBinding{" + mConnectionToken
+ + " / uid " + mUid + " / pid " + mPid + "}";
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mConnectionToken);
+ dest.writeInt(mUid);
+ dest.writeInt(mPid);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<InputBinding> CREATOR = new Parcelable.Creator<InputBinding>() {
+ public InputBinding createFromParcel(Parcel source) {
+ return new InputBinding(source);
+ }
+
+ public InputBinding[] newArray(int size) {
+ return new InputBinding[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
new file mode 100644
index 0000000..5f8ba1f
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -0,0 +1,249 @@
+/*
+ * 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;
+
+import android.os.Bundle;
+import android.text.Spanned;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+/**
+ * The InputConnection interface is the communication channel from an
+ * {@link InputMethod} back to the application that is receiving its input. It
+ * is used to perform such things as reading text around the cursor,
+ * committing text to the text box, and sending raw key events to the application.
+ */
+public interface InputConnection {
+ /**
+ * 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
+ * (such as its process crashing) or the client is taking too long to
+ * respond with the text (it is given a couple seconds to return).
+ * In either case, a null is returned.
+ *
+ * @param n The expected length of the text.
+ *
+ * @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);
+
+ /**
+ * Get <var>n</var> characters of text after the current cursor position.
+ *
+ * <p>This method may fail either if the input connection has become invalid
+ * (such as its process crashing) or the client is taking too long to
+ * respond with the text (it is given a couple seconds to return).
+ * In either case, a null is returned.
+ *
+ * @param n The expected length of the text.
+ *
+ * @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);
+
+ /**
+ * Retrieve the current capitalization mode in effect at the current
+ * cursor position in the text. See
+ * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode} for
+ * more information.
+ *
+ * <p>This method may fail either if the input connection has become invalid
+ * (such as its process crashing) or the client is taking too long to
+ * respond with the text (it is given a couple seconds to return).
+ * In either case, a 0 is returned.
+ *
+ * @param reqModes The desired modes to retrieve, as defined by
+ * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}. These
+ * constants are defined so that you can simply pass the current
+ * {@link EditorInfo#inputType TextBoxAttribute.contentType} value
+ * directly in to here.
+ *
+ * @return Returns the caps mode flags that are in effect.
+ */
+ 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,
+ * and optionally the input connection can send updates to the
+ * input method when its text changes.
+ *
+ * <p>This method may fail either if the input connection has become invalid
+ * (such as its process crashing) or the client is taking too long to
+ * respond with the text (it is given a couple seconds to return).
+ * In either case, a null is returned.
+ *
+ * @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}.
+ *
+ * @return Returns an ExtractedText object describing the state of the
+ * text view and containing the extracted text itself.
+ */
+ public ExtractedText getExtractedText(ExtractedTextRequest request,
+ int flags);
+
+ /**
+ * Delete <var>leftLength</var> characters of text before the current cursor
+ * position, and delete <var>rightLength</var> characters of text after the
+ * current cursor position, excluding composing text.
+ *
+ * @param leftLength The number of characters to be deleted before the
+ * current cursor position.
+ * @param rightLength The number of characters to be deleted after the
+ * current cursor position.
+ *
+ * @return Returns true on success, false if the input connection is no longer
+ * valid.
+ */
+ boolean deleteSurroundingText(int leftLength, int rightLength);
+
+ /**
+ * Set composing text around the current cursor position with the given text,
+ * and set the new cursor position. Any composing text set previously will
+ * be removed automatically.
+ *
+ * @param text The composing text with styles if necessary. If no style
+ * object attached to the text, the default style for composing text
+ * is used. See {#link android.text.Spanned} for how to attach style
+ * 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>.
+ *
+ * @return Returns true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean setComposingText(CharSequence text, int newCursorPosition);
+
+ /**
+ * Commit text to the text box and set the new cursor position.
+ * Any composing text set previously will be removed
+ * automatically.
+ *
+ * @param text The committed text.
+ * @param newCursorPosition The new cursor position within the
+ * <var>text</var>.
+ *
+ *
+ * @return Returns true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean commitText(CharSequence text, int newCursorPosition);
+
+ /**
+ * Commit a completion the user has selected from the possible ones
+ * previously reported to {@link InputMethodSession#displayCompletions
+ * InputMethodSession.displayCompletions()}. This will result in the
+ * same behavior as if the user had selected the completion from the
+ * actual UI.
+ *
+ * @param text The committed completion.
+ *
+ * @return Returns true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean commitCompletion(CompletionInfo text);
+
+ /**
+ * Send a key event to the process that is currently attached through
+ * this input connection. The event will be dispatched like a normal
+ * key event, to the currently focused; this generally is the view that
+ * is providing this InputConnection, but due to the asynchronous nature
+ * of this protocol that can not be guaranteed and the focus may have
+ * changed by the time the event is received.
+ *
+ * <p>
+ * This method can be used to send key events to the application. For
+ * example, an on-screen keyboard may use this method to simulate a hardware
+ * keyboard. There are three types of standard keyboards, numeric (12-key),
+ * predictive (20-key) and ALPHA (QWERTY). You can specify the keyboard type
+ * by specify the device id of the key event.
+ *
+ * <p>
+ * You will usually want to set the flag
+ * {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD} on all
+ * key event objects you give to this API; the flag will not be set
+ * for you.
+ *
+ * @param event The key event.
+ *
+ * @return Returns true on success, false if the input connection is no longer
+ * valid.
+ *
+ * @see KeyEvent
+ * @see KeyCharacterMap#NUMERIC
+ * @see KeyCharacterMap#PREDICTIVE
+ * @see KeyCharacterMap#ALPHA
+ */
+ public boolean sendKeyEvent(KeyEvent event);
+
+ /**
+ * Clear the given meta key pressed states in the given input connection.
+ *
+ * @param states The states to be cleared, may be one or more bits as
+ * per {@link KeyEvent#getMetaState() KeyEvent.getMetaState()}.
+ *
+ * @return Returns true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean clearMetaKeyStates(int states);
+
+ /**
+ * 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
+ * because the InputConnection protocol is asynchronous, you have no way
+ * to get a result back or know if the client understood the command; you
+ * can use the information in {@link EditorInfo} to determine if
+ * a client supports a particular command.
+ *
+ * @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.
+ * @return Returns true if the command was sent (whether or not the
+ * associated editor understood it), false if the input connection is no longer
+ * 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
new file mode 100644
index 0000000..f150ad6
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -0,0 +1,95 @@
+/*
+ * 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;
+
+import android.os.Bundle;
+import android.view.KeyEvent;
+
+/**
+ * Wrapper around InputConnection interface, calling through to another
+ * implementation of it.
+ */
+public class InputConnectionWrapper implements InputConnection {
+ InputConnection mBase;
+
+ /**
+ * Create a new wrapper around an existing InputConnection implementation.
+ */
+ public InputConnectionWrapper(InputConnection base) {
+ mBase = base;
+ }
+
+ /**
+ * Return the base InputConnection that this class is wrapping.
+ */
+ InputConnection getBase() {
+ return mBase;
+ }
+
+ public CharSequence getTextBeforeCursor(int n) {
+ return mBase.getTextBeforeCursor(n);
+ }
+
+ public CharSequence getTextAfterCursor(int n) {
+ return mBase.getTextAfterCursor(n);
+ }
+
+ public int getCursorCapsMode(int reqModes) {
+ return mBase.getCursorCapsMode(reqModes);
+ }
+
+ public ExtractedText getExtractedText(ExtractedTextRequest request,
+ int flags) {
+ return mBase.getExtractedText(request, flags);
+ }
+
+ public boolean deleteSurroundingText(int leftLength, int rightLength) {
+ return mBase.deleteSurroundingText(leftLength, rightLength);
+ }
+
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ return mBase.setComposingText(text, newCursorPosition);
+ }
+
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ return mBase.commitText(text, newCursorPosition);
+ }
+
+ public boolean commitCompletion(CompletionInfo text) {
+ return mBase.commitCompletion(text);
+ }
+
+ public boolean sendKeyEvent(KeyEvent event) {
+ return mBase.sendKeyEvent(event);
+ }
+
+ public boolean clearMetaKeyStates(int states) {
+ return mBase.clearMetaKeyStates(states);
+ }
+
+ public boolean performPrivateCommand(String action, Bundle data) {
+ return mBase.performPrivateCommand(action, data);
+ }
+
+ public boolean showStatusIcon(String packageName, int resId) {
+ return mBase.showStatusIcon(packageName, resId);
+ }
+
+ public boolean hideStatusIcon() {
+ return mBase.hideStatusIcon();
+ }
+}
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
new file mode 100644
index 0000000..259e759
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -0,0 +1,170 @@
+/*
+ * 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;
+
+import android.graphics.Rect;
+import android.inputmethodservice.InputMethodService;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * The InputMethod interface represents an input method which can generate key
+ * events and text, such as digital, email addresses, CJK characters, other
+ * language characters, and etc., while handling various input events, and send
+ * the text back to the application that requests text input.
+ *
+ * <p>Applications will not normally use this interface themselves, instead
+ * relying on the standard interaction provided by
+ * {@link android.widget.TextView} and {@link android.widget.EditText}.
+ *
+ * <p>Those implementing input methods should normally do so by deriving from
+ * {@link InputMethodService} or one of its subclasses. When implementing
+ * an input method, the service component containing it must also supply
+ * a {@link #SERVICE_META_DATA} meta-data field, referencing an XML resource
+ * providing details about the input method. All input methods also must
+ * require that clients hold the
+ * {@link android.Manifest.permission#BIND_INPUT_METHOD} in order to interact
+ * with the service; if this is not required, the system will not use that
+ * input method, because it can not trust that it is not compromised.
+ */
+public interface InputMethod {
+ /**
+ * This is the interface name that a service implementing an input
+ * method should say that it supports -- that is, this is the action it
+ * uses for its intent filter. (Note: this name is used because this
+ * interface should be moved to the view package.)
+ */
+ public static final String SERVICE_INTERFACE = "android.view.InputMethod";
+
+ /**
+ * Name under which an InputMethod service component publishes information
+ * about itself. This meta-data must reference an XML resource containing
+ * an
+ * <code>&lt;{@link android.R.styleable#InputMethod input-method}&gt;</code>
+ * tag.
+ */
+ public static final String SERVICE_META_DATA = "android.view.im";
+
+ public interface SessionCallback {
+ public void sessionCreated(InputMethodSession session);
+ }
+
+ /**
+ * Called first thing after an input method is created, this supplies a
+ * unique token for the session it has with the system service. It is
+ * needed to identify itself with the service to validate its operations.
+ * This token <strong>must not</strong> be passed to applications, since
+ * it grants special priviledges that should not be given to applications.
+ *
+ * <p>Note: to protect yourself from malicious clients, you should only
+ * accept the first token given to you. Any after that may come from the
+ * client.
+ */
+ public void attachToken(IBinder token);
+
+ /**
+ * Bind a new application environment in to the input method, so that it
+ * can later start and stop input processing.
+ * Typically this method is called when this input method is enabled in an
+ * application for the first time.
+ *
+ * @param binding Information about the application window that is binding
+ * to the input method.
+ *
+ * @see InputBinding
+ * @see #unbindInput()
+ */
+ public void bindInput(InputBinding binding);
+
+ /**
+ * Unbind an application environment, called when the information previously
+ * set by {@link #bindInput} is no longer valid for this input method.
+ *
+ * <p>
+ * Typically this method is called when the application changes to be
+ * non-foreground.
+ */
+ public void unbindInput();
+
+ /**
+ * This method is called when the application starts to receive text and it
+ * 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)
+ * that requests input.
+ *
+ * @see EditorInfo
+ */
+ public void startInput(EditorInfo attribute);
+
+ /**
+ * This method is called when the state of this input method needs to be
+ * reset.
+ *
+ * <p>
+ * Typically, this method is called when the input focus is moved from one
+ * text box to another.
+ *
+ * @param attribute The attribute of the text box (typically, a EditText)
+ * that requests input.
+ *
+ * @see EditorInfo
+ */
+ public void restartInput(EditorInfo attribute);
+
+ /**
+ * Create a new {@link InputMethodSession} that can be handed to client
+ * applications for interacting with the input method. You can later
+ * use {@link #revokeSession(InputMethodSession)} to destroy the session
+ * so that it can no longer be used by any clients.
+ *
+ * @param callback Interface that is called with the newly created session.
+ */
+ public void createSession(SessionCallback callback);
+
+ /**
+ * Control whether a particular input method session is active.
+ *
+ * @param session The {@link InputMethodSession} previously provided through
+ * SessionCallback.sessionCreated() that is to be changed.
+ */
+ public void setSessionEnabled(InputMethodSession session, boolean enabled);
+
+ /**
+ * Disable and destroy a session that was previously created with
+ * {@link #createSession(android.view.inputmethod.InputMethod.SessionCallback)}.
+ * After this call, the given session interface is no longer active and
+ * calls on it will fail.
+ *
+ * @param session The {@link InputMethodSession} previously provided through
+ * SessionCallback.sessionCreated() that is to be revoked.
+ */
+ public void revokeSession(InputMethodSession session);
+
+ /**
+ * Request that any soft input part of the input method be shown to the user.
+ */
+ public void showSoftInput();
+
+ /**
+ * Request that any soft input part of the input method be hidden from the user.
+ */
+ public void hideSoftInput();
+}
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.aidl b/core/java/android/view/inputmethod/InputMethodInfo.aidl
new file mode 100644
index 0000000..5f4d6b6
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethodInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+parcelable InputMethodInfo;
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
new file mode 100644
index 0000000..e8f4b54
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -0,0 +1,300 @@
+/*
+ * 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;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Printer;
+import android.util.Xml;
+
+import java.io.IOException;
+
+/**
+ * This class is used to specify meta information of an input method.
+ */
+public final class InputMethodInfo implements Parcelable {
+ static final String TAG = "InputMethodMetaInfo";
+
+ /**
+ * The Service that implements this input method component.
+ */
+ final ResolveInfo mService;
+
+ /**
+ * The unique string Id to identify the input method. This is generated
+ * from the input method component.
+ */
+ final String mId;
+
+ /**
+ * The input method setting activity's name, used by the system settings to
+ * launch the setting activity of this input method.
+ */
+ final String mSettingsActivityName;
+
+ /**
+ * The resource in the input method's .apk that holds a boolean indicating
+ * whether it should be considered the default input method for this
+ * system. This is a resource ID instead of the final value so that it
+ * can change based on the configuration (in particular locale).
+ */
+ final int mIsDefaultResId;
+
+ /**
+ * Constructor.
+ *
+ * @param context The Context in which we are parsing the input method.
+ * @param service The ResolveInfo returned from the package manager about
+ * this input method's component.
+ */
+ public InputMethodInfo(Context context, ResolveInfo service)
+ throws XmlPullParserException, IOException {
+ mService = service;
+ ServiceInfo si = service.serviceInfo;
+ mId = new ComponentName(si.packageName, si.name).flattenToShortString();
+
+ PackageManager pm = context.getPackageManager();
+ String settingsActivityComponent = null;
+ int isDefaultResId = 0;
+
+ XmlResourceParser parser = null;
+ try {
+ parser = si.loadXmlMetaData(pm, InputMethod.SERVICE_META_DATA);
+ if (parser == null) {
+ throw new XmlPullParserException("No "
+ + InputMethod.SERVICE_META_DATA + " meta-data");
+ }
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ String nodeName = parser.getName();
+ if (!"input-method".equals(nodeName)) {
+ throw new XmlPullParserException(
+ "Meta-data does not start with input-method tag");
+ }
+
+ TypedArray sa = context.getResources().obtainAttributes(attrs,
+ com.android.internal.R.styleable.InputMethod);
+ settingsActivityComponent = sa.getString(
+ com.android.internal.R.styleable.InputMethod_settingsActivity);
+ isDefaultResId = sa.getResourceId(
+ com.android.internal.R.styleable.InputMethod_isDefault, 0);
+ sa.recycle();
+ } finally {
+ if (parser != null) parser.close();
+ }
+
+ mSettingsActivityName = settingsActivityComponent;
+ mIsDefaultResId = isDefaultResId;
+ }
+
+ InputMethodInfo(Parcel source) {
+ mId = source.readString();
+ mSettingsActivityName = source.readString();
+ mIsDefaultResId = source.readInt();
+ mService = ResolveInfo.CREATOR.createFromParcel(source);
+ }
+
+ /**
+ * Temporary API for creating a built-in input method.
+ */
+ public InputMethodInfo(String packageName, String className,
+ CharSequence label, String settingsActivity) {
+ ResolveInfo ri = new ResolveInfo();
+ ServiceInfo si = new ServiceInfo();
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ai.enabled = true;
+ si.applicationInfo = ai;
+ si.enabled = true;
+ si.packageName = packageName;
+ si.name = className;
+ si.exported = true;
+ si.nonLocalizedLabel = label;
+ ri.serviceInfo = si;
+ mService = ri;
+ mId = new ComponentName(si.packageName, si.name).flattenToShortString();
+ mSettingsActivityName = settingsActivity;
+ mIsDefaultResId = 0;
+ }
+
+ /**
+ * Return a unique ID for this input method. The ID is generated from
+ * the package and class name implementing the method.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Return the .apk package that implements this input method.
+ */
+ public String getPackageName() {
+ return mService.serviceInfo.packageName;
+ }
+
+ /**
+ * Return the class name of the service component that implements
+ * this input method.
+ */
+ public String getServiceName() {
+ return mService.serviceInfo.name;
+ }
+
+ /**
+ * Return the component of the service that implements this input
+ * method.
+ */
+ public ComponentName getComponent() {
+ return new ComponentName(mService.serviceInfo.packageName,
+ mService.serviceInfo.name);
+ }
+
+ /**
+ * Load the user-displayed label for this input method.
+ *
+ * @param pm Supply a PackageManager used to load the input method's
+ * resources.
+ */
+ public CharSequence loadLabel(PackageManager pm) {
+ return mService.loadLabel(pm);
+ }
+
+ /**
+ * Load the user-displayed icon for this input method.
+ *
+ * @param pm Supply a PackageManager used to load the input method's
+ * resources.
+ */
+ public Drawable loadIcon(PackageManager pm) {
+ return mService.loadIcon(pm);
+ }
+
+ /**
+ * Return the class name of an activity that provides a settings UI for
+ * the input method. You can launch this activity be starting it with
+ * an {@link android.content.Intent} whose action is MAIN and with an
+ * explicit {@link android.content.ComponentName}
+ * composed of {@link #getPackageName} and the class name returned here.
+ *
+ * <p>A null will be returned if there is no settings activity associated
+ * with the input method.
+ */
+ public String getSettingsActivity() {
+ return mSettingsActivityName;
+ }
+
+ /**
+ * Return the resource identifier of a resource inside of this input
+ * method's .apk that determines whether it should be considered a
+ * default input method for the system.
+ */
+ public int getIsDefaultResourceId() {
+ 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);
+ pw.println(prefix + "mIsDefaultResId=0x"
+ + Integer.toHexString(mIsDefaultResId));
+ pw.println(prefix + "Service:");
+ mService.dump(pw, prefix + " ");
+ }
+
+ @Override
+ public String toString() {
+ return "InputMethodMetaInfo{" + mId
+ + ", settings: "
+ + mSettingsActivityName + "}";
+ }
+
+ /**
+ * Used to test whether the given parameter object is an
+ * {@link InputMethodInfo} and its Id is the same to this one.
+ *
+ * @return true if the given parameter object is an
+ * {@link InputMethodInfo} and its Id is the same to this one.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (o == null) return false;
+
+ if (!(o instanceof InputMethodInfo)) return false;
+
+ InputMethodInfo obj = (InputMethodInfo) o;
+ return mId.equals(obj.mId);
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mId);
+ dest.writeString(mSettingsActivityName);
+ dest.writeInt(mIsDefaultResId);
+ mService.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<InputMethodInfo> CREATOR = new Parcelable.Creator<InputMethodInfo>() {
+ public InputMethodInfo createFromParcel(Parcel source) {
+ return new InputMethodInfo(source);
+ }
+
+ public InputMethodInfo[] newArray(int size) {
+ return new InputMethodInfo[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
new file mode 100644
index 0000000..da82593
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -0,0 +1,900 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.internal.view.IInputConnectionWrapper;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodCallback;
+import com.android.internal.view.IInputMethodClient;
+import com.android.internal.view.IInputMethodManager;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.InputBindResult;
+
+import java.util.List;
+
+/**
+ * Public interface to the global input method manager. You can retrieve
+ * an instance of this interface with
+ * {@link Context#getSystemService(String) Context.getSystemService()}.
+ */
+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;
+
+ final IInputMethodManager mService;
+ final Looper mMainLooper;
+
+ // For scheduling work on the main thread. This also serves as our
+ // global lock.
+ final H mH;
+
+ // The currently active input connection.
+ final MutableInputConnectionWrapper mInputConnectionWrapper;
+ final IInputContext mIInputContext;
+
+ /**
+ * True if this input method client is active, initially false.
+ */
+ boolean mActive = false;
+
+ /**
+ * The current base input connection, used when mActive is true.
+ */
+ InputConnection mCurrentInputConnection;
+
+ // -----------------------------------------------------------
+
+ /**
+ * 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.
+ */
+ View mLastServedView;
+ /**
+ * This is set when we are in the process of connecting, to determine
+ * when we have actually finished.
+ */
+ boolean mServedConnecting;
+ /**
+ * This is non-null when we have connected the served view; it holds
+ * the attributes that were last retrieved from the served view and given
+ * to the input connection.
+ */
+ EditorInfo mCurrentTextBoxAttribute;
+ /**
+ * The InputConnection that was last retrieved from the served view.
+ */
+ InputConnection mServedInputConnection;
+ /**
+ * The completions that were last provided by the served view.
+ */
+ CompletionInfo[] mCompletions;
+
+ // Cursor position on the screen.
+ Rect mTmpCursorRect = new Rect();
+ Rect mCursorRect = new Rect();
+ int mCursorSelStart;
+ int mCursorSelEnd;
+
+ // -----------------------------------------------------------
+
+ /**
+ * Sequence number of this binding, as returned by the server.
+ */
+ int mBindSequence = -1;
+ /**
+ * ID of the method we are bound to.
+ */
+ String mCurId;
+ /**
+ * The actual instance of the method to make calls on it.
+ */
+ IInputMethodSession mCurMethod;
+
+ // -----------------------------------------------------------
+
+ static final int MSG_CHECK_FOCUS = 1;
+
+ class H extends Handler {
+ H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_CHECK_FOCUS:
+ checkFocus();
+ return;
+ }
+ }
+ }
+
+ static class NoOpInputConnection implements InputConnection {
+
+ public boolean clearMetaKeyStates(int states) {
+ return false;
+ }
+
+ public boolean commitCompletion(CompletionInfo text) {
+ return false;
+ }
+
+ 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 showStatusIcon(String packageName, int resId) {
+ return false;
+ }
+ }
+
+ final NoOpInputConnection mNoOpInputConnection = new NoOpInputConnection();
+
+ final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
+ 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();
+ }
+
+ 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();
+ }
+ }
+
+ public void setActive(boolean active) {
+ mActive = active;
+ mInputConnectionWrapper.setBaseInputConnection(active
+ ? mCurrentInputConnection : mNoOpInputConnection);
+ }
+ };
+
+ final InputConnection mDummyInputConnection = new BaseInputConnection(this) {
+ 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;
+ }
+ };
+
+ InputMethodManager(IInputMethodManager service, Looper looper) {
+ mService = service;
+ mMainLooper = looper;
+ mH = new H(looper);
+ mInputConnectionWrapper = new MutableInputConnectionWrapper(mNoOpInputConnection);
+ mIInputContext = new IInputConnectionWrapper(looper,
+ mInputConnectionWrapper);
+ setCurrentInputConnection(mDummyInputConnection);
+
+ if (mInstance == null) {
+ mInstance = this;
+ }
+ }
+
+ /**
+ * Retrieve the global InputMethodManager instance, creating it if it
+ * doesn't already exist.
+ * @hide
+ */
+ static public InputMethodManager getInstance(Context context) {
+ synchronized (mInstanceSync) {
+ if (mInstance != null) {
+ return mInstance;
+ }
+ IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
+ IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
+ mInstance = new InputMethodManager(service, context.getMainLooper());
+ }
+ return mInstance;
+ }
+
+ /**
+ * Private optimization: retrieve the global InputMethodManager instance,
+ * if it exists.
+ * @hide
+ */
+ static public InputMethodManager peekInstance() {
+ return mInstance;
+ }
+
+ /** @hide */
+ public IInputMethodClient getClient() {
+ return mClient;
+ }
+
+ /** @hide */
+ public IInputContext getInputContext() {
+ return mIInputContext;
+ }
+
+ public List<InputMethodInfo> getInputMethodList() {
+ try {
+ return mService.getInputMethodList();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public List<InputMethodInfo> getEnabledInputMethodList() {
+ try {
+ return mService.getEnabledInputMethodList();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void updateStatusIcon(int iconId, String iconPackage) {
+ try {
+ mService.updateStatusIcon(iconId, iconPackage);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Return true if the given view is the currently active view for the
+ * input method.
+ */
+ public boolean isActive(View view) {
+ synchronized (mH) {
+ return mServedView == view && mCurrentTextBoxAttribute != null;
+ }
+ }
+
+ /**
+ * Return true if any view is currently active in the input method.
+ */
+ public boolean isActive() {
+ synchronized (mH) {
+ return mServedView != null && mCurrentTextBoxAttribute != null;
+ }
+ }
+
+ /**
+ * Return true if the currently served view is accepting full text edits.
+ * If false, it has no input connection, so can only handle raw key events.
+ */
+ public boolean isAcceptingText() {
+ return mServedInputConnection != null;
+ }
+
+ /**
+ * Reset all of the state associated with being bound to an input method.
+ */
+ void clearBindingLocked() {
+ clearConnectionLocked();
+ mBindSequence = -1;
+ mCurId = null;
+ mCurMethod = null;
+ }
+
+ /**
+ * 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() {
+ synchronized (mH) {
+ if (mServedView != null) {
+ if (DEBUG) Log.v(TAG, "FINISH INPUT: " + mServedView);
+ updateStatusIcon(0, null);
+
+ if (mCurrentTextBoxAttribute != null) {
+ try {
+ mService.finishInput(mClient);
+ } catch (RemoteException e) {
+ }
+ }
+
+ mServedView = null;
+ mCompletions = null;
+ mServedConnecting = false;
+ clearConnectionLocked();
+ }
+ }
+ }
+
+ public void displayCompletions(View view, CompletionInfo[] completions) {
+ synchronized (mH) {
+ if (mServedView != view) {
+ return;
+ }
+
+ mCompletions = completions;
+ if (mCurMethod != null) {
+ try {
+ mCurMethod.displayCompletions(mCompletions);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ public void updateExtractedText(View view, int token, ExtractedText text) {
+ synchronized (mH) {
+ if (mServedView != view) {
+ return;
+ }
+
+ if (mCurMethod != null) {
+ try {
+ mCurMethod.updateExtractedText(token, text);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * 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
+ * start performing input into it.
+ *
+ * @param view The currently focused view, which would like to receive
+ * soft keyboard input.
+ */
+ public void showSoftInput(View view) {
+ synchronized (mH) {
+ if (mServedView != view) {
+ return;
+ }
+
+ try {
+ mService.showSoftInput(mClient);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * 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
+ * have the input window hidden.
+ *
+ * @param windowToken The token of the window that is making the request,
+ * as returned by {@link View#getWindowToken() View.getWindowToken()}.
+ */
+ public void hideSoftInputFromWindow(IBinder windowToken) {
+ synchronized (mH) {
+ if (mServedView == null || mServedView.getWindowToken() != windowToken) {
+ return;
+ }
+
+ try {
+ mService.hideSoftInput(mClient);
+ } 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
+ * within your view changes outside of the normal input method or key
+ * input flow, such as when an application calls TextView.setText().
+ *
+ * @param view The view whose text has changed.
+ */
+ public void restartInput(View view) {
+ synchronized (mH) {
+ if (mServedView != view) {
+ return;
+ }
+
+ mServedConnecting = true;
+ }
+
+ startInputInner();
+ }
+
+ void startInputInner() {
+ final View view;
+ synchronized (mH) {
+ view = mServedView;
+
+ // Make sure we have a window token for the served view.
+ if (DEBUG) Log.v(TAG, "Starting input: view=" + view);
+ if (view == null) {
+ if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
+ return;
+ }
+ }
+
+ // Now we need to get an input connection from the served view.
+ // This is complicated in a couple ways: we can't be holding our lock
+ // when calling out to the view, and we need to make sure we call into
+ // the view on the same thread that is driving its view hierarchy.
+ Handler vh = view.getHandler();
+ if (vh == null) {
+ // If the view doesn't have a handler, something has changed out
+ // from under us, so just bail.
+ if (DEBUG) Log.v(TAG, "ABORT input: no handler for view!");
+ return;
+ }
+ if (vh.getLooper() != Looper.myLooper()) {
+ // The view is running on a different thread than our own, so
+ // we need to reschedule our work for over there.
+ if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
+ vh.post(new Runnable() {
+ public void run() {
+ startInputInner();
+ }
+ });
+ }
+
+ // 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();
+ InputConnection ic = view.createInputConnection(tba);
+ if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
+
+ synchronized (mH) {
+ // Now that we are locked again, validate that our state hasn't
+ // changed.
+ if (mServedView != view || !mServedConnecting) {
+ // Something else happened, so abort.
+ if (DEBUG) Log.v(TAG,
+ "Starting input: finished by someone else (view="
+ + mServedView + " conn=" + mServedConnecting + ")");
+ return;
+ }
+
+ // If we already have a text box, then this view is already
+ // connected so we want to restart it.
+ final boolean initial = mCurrentTextBoxAttribute == null;
+
+ // Hook 'em up and let 'er rip.
+ mCurrentTextBoxAttribute = tba;
+ mServedConnecting = false;
+ mServedInputConnection = ic;
+ if (ic != null) {
+ mCursorSelStart = tba.initialSelStart;
+ mCursorSelEnd = tba.initialSelEnd;
+ mCursorRect.setEmpty();
+ setCurrentInputConnection(ic);
+ } else {
+ setCurrentInputConnection(mDummyInputConnection);
+ }
+
+ try {
+ if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic="
+ + ic + " tba=" + tba);
+ InputBindResult res = mService.startInput(mClient, tba, initial,
+ mCurMethod == null);
+ if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
+ if (res != null) {
+ if (res.id != null) {
+ mBindSequence = res.sequence;
+ mCurMethod = res.method;
+ } else {
+ // This means there is no input method available.
+ if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
+ return;
+ }
+ }
+ if (mCurMethod != null && mCompletions != null) {
+ try {
+ mCurMethod.displayCompletions(mCompletions);
+ } catch (RemoteException e) {
+ }
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+
+ /**
+ * When the focused window is dismissed, this method is called to finish the
+ * input method started before.
+ * @hide
+ */
+ public void windowDismissed(IBinder appWindowToken) {
+ synchronized (mH) {
+ if (mServedView != null &&
+ mServedView.getWindowToken() == appWindowToken) {
+ finishInputLocked();
+ }
+ }
+ }
+
+ /**
+ * Call this when a view receives focus.
+ * @hide
+ */
+ public void focusIn(View view) {
+ synchronized (mH) {
+ if (DEBUG) Log.v(TAG, "focusIn: " + view);
+ // Okay we have a new view that is being served.
+ mServedView = view;
+ mCompletions = null;
+ mServedConnecting = true;
+ }
+
+ startInputInner();
+ }
+
+ /**
+ * Call this when a view loses focus.
+ * @hide
+ */
+ public void focusOut(View view) {
+ synchronized (mH) {
+ if (DEBUG) Log.v(TAG, "focusOut: " + view
+ + " mServedView=" + mServedView
+ + " winFocus=" + view.hasWindowFocus());
+ if (mServedView == view && view.hasWindowFocus()) {
+ mLastServedView = view;
+ mH.removeMessages(MSG_CHECK_FOCUS);
+ mH.sendEmptyMessage(MSG_CHECK_FOCUS);
+ }
+ }
+ }
+
+ void checkFocus() {
+ synchronized (mH) {
+ if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
+ + " last=" + mLastServedView);
+ if (mServedView == mLastServedView) {
+ 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();
+ }
+ mLastServedView = null;
+ }
+ }
+
+ void closeCurrentInput() {
+ try {
+ mService.hideSoftInput(mClient);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Called by ViewRoot the first time it gets window focus.
+ */
+ public void onWindowFocus(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));
+ try {
+ mService.windowGainedFocus(mClient, focusedView != null,
+ softInputMode, first, windowFlags);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Report the current selection range.
+ */
+ public void updateSelection(View view, int selStart, int selEnd) {
+ synchronized (mH) {
+ if (mServedView != view || mCurrentTextBoxAttribute == null
+ || mCurMethod == null) {
+ return;
+ }
+
+ if (mCursorSelStart != selStart || mCursorSelEnd != selEnd) {
+ if (DEBUG) Log.d(TAG, "updateSelection");
+
+ try {
+ if (DEBUG) Log.v(TAG, "SELECTION CHANGE: " + mCurMethod);
+ mCurMethod.updateSelection(mCursorSelStart, mCursorSelEnd,
+ selStart, selEnd);
+ mCursorSelStart = selStart;
+ mCursorSelEnd = selEnd;
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if the current input method wants to watch the location
+ * of the input editor's cursor in its window.
+ */
+ public boolean isWatchingCursor(View view) {
+ return false;
+ }
+
+ /**
+ * Report the current cursor location in its window.
+ */
+ public void updateCursor(View view, int left, int top, int right, int bottom) {
+ synchronized (mH) {
+ if (mServedView != view || mCurrentTextBoxAttribute == null
+ || mCurMethod == null) {
+ return;
+ }
+
+ mTmpCursorRect.set(left, top, right, bottom);
+ if (!mCursorRect.equals(mTmpCursorRect)) {
+ if (DEBUG) Log.d(TAG, "updateCursor");
+
+ try {
+ if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod);
+ mCurMethod.updateCursor(mTmpCursorRect);
+ mCursorRect.set(mTmpCursorRect);
+ } 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
+ * when it was started, which allows it to perform this operation on
+ * itself.
+ * @param id The unique identifier for the new input method to be switched to.
+ */
+ public void setInputMethod(IBinder token, String id) {
+ try {
+ mService.setInputMethod(token, id);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Close/hide the input method's soft input area, so the user no longer
+ * sees it or 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.
+ */
+ public void hideSoftInputFromInputMethod(IBinder token) {
+ try {
+ mService.hideMySoftInput(token);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void dispatchKeyEvent(Context context, int seq, KeyEvent key,
+ IInputMethodCallback callback) {
+ synchronized (mH) {
+ if (DEBUG) Log.d(TAG, "dispatchKeyEvent");
+
+ if (mCurMethod == null || mCurrentTextBoxAttribute == null) {
+ try {
+ callback.finishedEvent(seq, false);
+ } catch (RemoteException e) {
+ }
+ return;
+ }
+
+ if (key.getAction() == KeyEvent.ACTION_DOWN
+ && key.getKeyCode() == KeyEvent.KEYCODE_SYM) {
+ showInputMethodPicker();
+ try {
+ callback.finishedEvent(seq, true);
+ } catch (RemoteException e) {
+ }
+ return;
+ }
+ try {
+ if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod);
+ mCurMethod.dispatchKeyEvent(seq, key, callback);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e);
+ try {
+ callback.finishedEvent(seq, false);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ void dispatchTrackballEvent(Context context, int seq, MotionEvent motion,
+ IInputMethodCallback callback) {
+ synchronized (mH) {
+ if (DEBUG) Log.d(TAG, "dispatchTrackballEvent");
+
+ if (mCurMethod == null || mCurrentTextBoxAttribute == null) {
+ try {
+ callback.finishedEvent(seq, false);
+ } catch (RemoteException e) {
+ }
+ return;
+ }
+
+ try {
+ if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod);
+ mCurMethod.dispatchTrackballEvent(seq, motion, callback);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e);
+ try {
+ callback.finishedEvent(seq, false);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+
+ public void showInputMethodPicker() {
+ synchronized (mH) {
+ try {
+ mService.showInputMethodPickerFromClient(mClient);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
new file mode 100644
index 0000000..603da13
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -0,0 +1,137 @@
+/*
+ * 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;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * The InputMethodSession interface provides the per-client functionality
+ * of {@link InputMethod} that is safe to expose to applications.
+ *
+ * <p>Applications will not normally use this interface themselves, instead
+ * relying on the standard interaction provided by
+ * {@link android.widget.TextView} and {@link android.widget.EditText}.
+ */
+public interface InputMethodSession {
+
+ public interface EventCallback {
+ void finishedEvent(int seq, boolean handled);
+ }
+
+ /**
+ * This method is called when the application would like to stop
+ * receiving text input.
+ */
+ public void finishInput();
+
+ /**
+ * This method is called when the selection or cursor in the current
+ * target input field has changed.
+ *
+ * @param oldSelStart The previous text offset of the cursor selection
+ * start position.
+ * @param oldSelEnd The previous text offset of the cursor selection
+ * end position.
+ * @param newSelStart The new text offset of the cursor selection
+ * start position.
+ * @param newSelEnd The new text offset of the cursor selection
+ * end position.
+ */
+ public void updateSelection(int oldSelStart, int oldSelEnd,
+ int newSelStart, int newSelEnd);
+
+ /**
+ * This method is called when cursor location of the target input field
+ * has changed within its window. This is not normally called, but will
+ * only be reported if requested by the input method.
+ *
+ * @param newCursor The rectangle of the cursor currently being shown in
+ * the input field's window coordinates.
+ */
+ public void updateCursor(Rect newCursor);
+
+ /**
+ * Called by a text editor that performs auto completion, to tell the
+ * input method about the completions it has available. This can be used
+ * by the input method to display them to the user to select the text to
+ * be inserted.
+ *
+ * @param completions Array of text completions that are available, starting with
+ * the best. If this array is null, any existing completions will be
+ * removed.
+ */
+ public void displayCompletions(CompletionInfo[] completions);
+
+ /**
+ * Called by a text editor to report its new extracted text when its
+ * contents change. This will only be called if the input method
+ * calls {@link InputConnection#getExtractedText(ExtractedTextRequest, int)
+ * InputConnection.getExtractedText()} with the option to report updates.
+ *
+ * @param token The input method supplied token for identifying its request.
+ * @param text The new extracted text.
+ */
+ public void updateExtractedText(int token, ExtractedText text);
+
+ /**
+ * This method is called when a key is pressed. When done with the event,
+ * the implementation must call back on <var>callback</var> with its
+ * result.
+ *
+ * <p>
+ * If the input method wants to handle this event, return true, otherwise
+ * return false and the caller (i.e. the application) will handle the event.
+ *
+ * @param event The key event.
+ *
+ * @return Whether the input method wants to handle this event.
+ *
+ * @see #dispatchKeyUp
+ * @see android.view.KeyEvent
+ */
+ public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback);
+
+ /**
+ * This method is called when there is a track ball event.
+ *
+ * <p>
+ * If the input method wants to handle this event, return true, otherwise
+ * return false and the caller (i.e. the application) will handle the event.
+ *
+ * @param event The motion event.
+ *
+ * @return Whether the input method wants to handle this event.
+ *
+ * @see android.view.MotionEvent
+ */
+ public void dispatchTrackballEvent(int seq, MotionEvent event, EventCallback callback);
+
+ /**
+ * Process a private command sent from the application to the input method.
+ * This can be used to provide domain-specific features that are
+ * only known between certain input methods and their clients.
+ *
+ * @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 appPrivateCommand(String action, Bundle data);
+}
diff --git a/core/java/android/view/inputmethod/MutableInputConnectionWrapper.java b/core/java/android/view/inputmethod/MutableInputConnectionWrapper.java
new file mode 100644
index 0000000..025a059
--- /dev/null
+++ b/core/java/android/view/inputmethod/MutableInputConnectionWrapper.java
@@ -0,0 +1,38 @@
+/*
+ * 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/view/inputmethod/package.html b/core/java/android/view/inputmethod/package.html
new file mode 100644
index 0000000..348aba6
--- /dev/null
+++ b/core/java/android/view/inputmethod/package.html
@@ -0,0 +1,11 @@
+<html>
+<body>
+Framework classes for interaction between views and input methods (such
+as soft keyboards). In most cases the main classes here are not needed for
+most applications, since they are dealt with for you by
+{@link android.widget.TextView}. When implementing a custom text editor,
+however, you will need to implement the
+{@link android.view.inputmethod.InputConnection} class to allow the current
+input method to interact with your view.
+</body>
+</html>
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index e99c444..1dd37be 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -26,10 +26,10 @@ import android.os.Handler;
import android.os.Message;
import android.util.Config;
import android.util.Log;
+import android.util.TypedValue;
import junit.framework.Assert;
-import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
@@ -52,9 +52,7 @@ class BrowserFrame extends Handler {
private final WebViewDatabase mDatabase;
private final WebViewCore mWebViewCore;
private boolean mLoadInitFromJava;
- private String mCurrentUrl;
private int mLoadType;
- private String mCompletedUrl;
private boolean mFirstLayoutDone = true;
private boolean mCommitted = true;
@@ -114,9 +112,7 @@ class BrowserFrame extends Handler {
CookieSyncManager.createInstance(context);
}
AssetManager am = context.getAssets();
- nativeCreateFrame(am, proxy.getBackForwardList());
- // Create a native FrameView and attach it to the native frame.
- nativeCreateView(w);
+ nativeCreateFrame(w, am, proxy.getBackForwardList());
mSettings = settings;
mContext = context;
@@ -142,11 +138,7 @@ class BrowserFrame extends Handler {
stringByEvaluatingJavaScriptFromString(
url.substring("javascript:".length()));
} else {
- if (!nativeLoadUrl(url)) {
- reportError(android.net.http.EventHandler.ERROR_BAD_URL,
- mContext.getString(com.android.internal.R.string.httpErrorBadUrl),
- url);
- }
+ nativeLoadUrl(url);
}
mLoadInitFromJava = false;
}
@@ -186,6 +178,17 @@ class BrowserFrame extends Handler {
}
/**
+ * Go back or forward the number of steps given.
+ * @param steps A negative or positive number indicating the direction
+ * and number of steps to move.
+ */
+ public void goBackOrForward(int steps) {
+ mLoadInitFromJava = true;
+ nativeGoBackOrForward(steps);
+ mLoadInitFromJava = false;
+ }
+
+ /**
* native callback
* Report an error to an activity.
* @param errorCode The HTTP error code.
@@ -216,34 +219,12 @@ class BrowserFrame extends Handler {
return mLoadType;
}
- /* package */String currentUrl() {
- return mCurrentUrl;
- }
-
- /* package */void didFirstLayout(String url) {
- // this is common case
- if (url.equals(mCurrentUrl)) {
- if (!mFirstLayoutDone) {
- mFirstLayoutDone = true;
- // ensure {@link WebViewCore#webkitDraw} is called as we were
- // blocking the update in {@link #loadStarted}
- mWebViewCore.contentInvalidate();
- }
- } else if (url.equals(mCompletedUrl)) {
- /*
- * FIXME: when loading http://www.google.com/m,
- * mCurrentUrl will be http://www.google.com/m,
- * mCompletedUrl will be http://www.google.com/m#search
- * and url will be http://www.google.com/m#search.
- * This is probably a bug in WebKit. If url matches mCompletedUrl,
- * also set mFirstLayoutDone to be true and update.
- */
- if (!mFirstLayoutDone) {
- mFirstLayoutDone = true;
- // ensure {@link WebViewCore#webkitDraw} is called as we were
- // blocking the update in {@link #loadStarted}
- mWebViewCore.contentInvalidate();
- }
+ /* package */void didFirstLayout() {
+ if (!mFirstLayoutDone) {
+ mFirstLayoutDone = true;
+ // ensure {@link WebViewCore#webkitDraw} is called as we were
+ // blocking the update in {@link #loadStarted}
+ mWebViewCore.contentDraw();
}
mWebViewCore.mEndScaleZoom = true;
}
@@ -258,8 +239,6 @@ class BrowserFrame extends Handler {
mIsMainFrame = isMainFrame;
if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
- mCurrentUrl = url;
- mCompletedUrl = null;
mLoadType = loadType;
if (isMainFrame) {
@@ -310,7 +289,6 @@ class BrowserFrame extends Handler {
// mIsMainFrame and isMainFrame are better be equal!!!
if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
- mCompletedUrl = url;
if (isMainFrame) {
mCallbackProxy.switchOutDrawHistory();
mCallbackProxy.onPageFinished(url);
@@ -355,8 +333,8 @@ class BrowserFrame extends Handler {
WebAddress uri = new WebAddress(
mCallbackProxy.getBackForwardList().getCurrentItem()
.getUrl());
- String host = uri.mHost;
- String[] up = mDatabase.getUsernamePassword(host);
+ String schemePlusHost = uri.mScheme + uri.mHost;
+ String[] up = mDatabase.getUsernamePassword(schemePlusHost);
if (up != null && up[0] != null) {
setUsernamePassword(up[0], up[1]);
}
@@ -441,7 +419,13 @@ class BrowserFrame extends Handler {
if (mLoadInitFromJava == true) {
return false;
}
- return mCallbackProxy.shouldOverrideUrlLoading(url);
+ if (mCallbackProxy.shouldOverrideUrlLoading(url)) {
+ // if the url is hijacked, reset the state of the BrowserFrame
+ didFirstLayout();
+ return true;
+ } else {
+ return false;
+ }
}
public void addJavascriptInterface(Object obj, String interfaceName) {
@@ -462,7 +446,7 @@ class BrowserFrame extends Handler {
* @param method The http method.
* @param headers The http headers.
* @param postData If the method is "POST" postData is sent as the request
- * body.
+ * body. Is null when empty.
* @param cacheMode The cache mode to use when loading this resource.
* @param isHighPriority True if this resource needs to be put at the front
* of the network queue.
@@ -473,7 +457,7 @@ class BrowserFrame extends Handler {
String url,
String method,
HashMap headers,
- String postData,
+ byte[] postData,
int cacheMode,
boolean isHighPriority,
boolean synchronous) {
@@ -497,41 +481,47 @@ class BrowserFrame extends Handler {
}
WebAddress uri = new WebAddress(mCallbackProxy
.getBackForwardList().getCurrentItem().getUrl());
- String host = uri.mHost;
+ String schemePlusHost = uri.mScheme + uri.mHost;
String[] ret = getUsernamePassword();
- if (ret != null && postData != null && ret[0].length() > 0
- && ret[1].length() > 0
- && postData.contains(URLEncoder.encode(ret[0]))
- && postData.contains(URLEncoder.encode(ret[1]))) {
- String[] saved = mDatabase.getUsernamePassword(host);
- if (saved != null) {
- // null username implies that user has chosen not to
- // save password
- if (saved[0] != null) {
- // non-null username implies that user has
- // chosen to save password, so update the
- // recorded password
- mDatabase.setUsernamePassword(host, ret[0],
- ret[1]);
+ // Has the user entered a username/password pair and is
+ // there some POST data
+ if (ret != null && postData != null &&
+ ret[0].length() > 0 && ret[1].length() > 0) {
+ // Check to see if the username & password appear in
+ // the post data (there could be another form on the
+ // page and that was posted instead.
+ String postString = new String(postData);
+ if (postString.contains(URLEncoder.encode(ret[0])) &&
+ postString.contains(URLEncoder.encode(ret[1]))) {
+ String[] saved = mDatabase.getUsernamePassword(
+ schemePlusHost);
+ if (saved != null) {
+ // null username implies that user has chosen not to
+ // save password
+ if (saved[0] != null) {
+ // non-null username implies that user has
+ // chosen to save password, so update the
+ // recorded password
+ mDatabase.setUsernamePassword(
+ schemePlusHost, ret[0], ret[1]);
+ }
+ } else {
+ // CallbackProxy will handle creating the resume
+ // message
+ mCallbackProxy.onSavePassword(schemePlusHost, ret[0],
+ ret[1], null);
}
- } else {
- // CallbackProxy will handle creating the resume
- // message
- mCallbackProxy.onSavePassword(host, ret[0], ret[1],
- null);
}
}
} catch (ParseException ex) {
// if it is bad uri, don't save its password
}
- }
- if (postData == null) {
- postData = "";
+
}
}
// is this resource the main-frame top-level page?
- boolean isMainFramePage = mIsMainFrame && url.equals(mCurrentUrl);
+ boolean isMainFramePage = mIsMainFrame;
if (Config.LOGV) {
Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method="
@@ -561,8 +551,8 @@ class BrowserFrame extends Handler {
CacheManager.endCacheTransaction();
}
- FrameLoader loader = new FrameLoader(loadListener,
- mSettings.getUserAgentString(), method, isHighPriority);
+ FrameLoader loader = new FrameLoader(loadListener, mSettings,
+ method, isHighPriority);
loader.setHeaders(headers);
loader.setPostData(postData);
loader.setCacheMode(cacheMode); // Set the load mode to the mode used
@@ -666,36 +656,51 @@ class BrowserFrame extends Handler {
return mSettings.getUserAgentString();
}
+ // these ids need to be in sync with enum RAW_RES_ID in WebFrame
+ private static final int NODOMAIN = 1;
+ private static final int LOADERROR = 2;
+
+ String getRawResFilename(int id) {
+ int resid;
+ switch (id) {
+ case NODOMAIN:
+ resid = com.android.internal.R.raw.nodomain;
+ break;
+
+ case LOADERROR:
+ resid = com.android.internal.R.raw.loaderror;
+ break;
+
+ default:
+ Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
+ return new String();
+ }
+ TypedValue value = new TypedValue();
+ mContext.getResources().getValue(resid, value, true);
+ return value.string.toString();
+ }
+
//==========================================================================
// native functions
//==========================================================================
/**
- * Create a new native frame.
+ * Create a new native frame for a given WebView
+ * @param w A WebView that the frame draws into.
* @param am AssetManager to use to get assets.
* @param list The native side will add and remove items from this list as
* the native list changes.
*/
- private native void nativeCreateFrame(AssetManager am,
+ private native void nativeCreateFrame(WebViewCore w, AssetManager am,
WebBackForwardList list);
/**
- * Create a native view attached to a WebView.
- * @param w A WebView that the frame draws into.
- */
- private native void nativeCreateView(WebViewCore w);
-
- private native void nativeCallPolicyFunction(int policyFunction,
- int decision);
- /**
* Destroy the native frame.
*/
public native void nativeDestroyFrame();
- /**
- * Detach the view from the frame.
- */
- private native void nativeDetachView();
+ private native void nativeCallPolicyFunction(int policyFunction,
+ int decision);
/**
* Reload the current main frame.
@@ -707,7 +712,7 @@ class BrowserFrame extends Handler {
* @param steps A negative or positive number indicating the direction
* and number of steps to move.
*/
- public native void goBackOrForward(int steps);
+ private native void nativeGoBackOrForward(int steps);
/**
* stringByEvaluatingJavaScriptFromString will execute the
@@ -738,7 +743,7 @@ class BrowserFrame extends Handler {
/**
* Returns false if the url is bad.
*/
- private native boolean nativeLoadUrl(String url);
+ private native void nativeLoadUrl(String url);
private native void nativeLoadData(String baseUrl, String data,
String mimeType, String encoding, String failUrl);
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index f5a09b8..d12940d 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -62,8 +62,18 @@ public final class CacheManager {
// Reference count the enable/disable transaction
private static int mRefCount;
+ // trimCacheIfNeeded() is called when a page is fully loaded. But JavaScript
+ // can load the content, e.g. in a slideshow, continuously, so we need to
+ // trim the cache on a timer base too. endCacheTransaction() is called on a
+ // timer base. We share the same timer with less frequent update.
+ private static int mTrimCacheCount = 0;
+ private static final int TRIM_CACHE_INTERVAL = 5;
+
private static WebViewDatabase mDataBase;
private static File mBaseDir;
+
+ // Flag to clear the cache when the CacheManager is initialized
+ private static boolean mClearCacheOnInit = false;
public static class CacheResult {
// these fields are saved to the database
@@ -145,16 +155,37 @@ public final class CacheManager {
static void init(Context context) {
mDataBase = WebViewDatabase.getInstance(context);
mBaseDir = new File(context.getCacheDir(), "webviewCache");
+ if (createCacheDirectory() && mClearCacheOnInit) {
+ removeAllCacheFiles();
+ mClearCacheOnInit = false;
+ }
+ }
+
+ /**
+ * Create the cache directory if it does not already exist.
+ *
+ * @return true if the cache directory didn't exist and was created.
+ */
+ static private boolean createCacheDirectory() {
if (!mBaseDir.exists()) {
if(!mBaseDir.mkdirs()) {
Log.w(LOGTAG, "Unable to create webviewCache directory");
- return;
+ return false;
}
FileUtils.setPermissions(
mBaseDir.toString(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-1, -1);
+ // If we did create the directory, we need to flush
+ // the cache database. The directory could be recreated
+ // because the system flushed all the data/cache directories
+ // to free up disk space.
+ WebViewCore.endCacheTransaction();
+ mDataBase.clearCache();
+ WebViewCore.startCacheTransaction();
+ return true;
}
+ return false;
}
/**
@@ -224,7 +255,12 @@ public final class CacheManager {
// only called from WebCore thread
// make sure to call startCacheTransaction/endCacheTransaction in pair
public static boolean endCacheTransaction() {
- return mDataBase.endCacheTransaction();
+ boolean ret = mDataBase.endCacheTransaction();
+ if (++mTrimCacheCount >= TRIM_CACHE_INTERVAL) {
+ mTrimCacheCount = 0;
+ trimCacheIfNeeded();
+ }
+ return ret;
}
/**
@@ -319,7 +355,20 @@ public final class CacheManager {
try {
ret.outStream = new FileOutputStream(ret.outFile);
} catch (FileNotFoundException e) {
- return null;
+ // This can happen with the system did a purge and our
+ // subdirectory has gone, so lets try to create it again
+ if (createCacheDirectory()) {
+ try {
+ ret.outStream = new FileOutputStream(ret.outFile);
+ } catch (FileNotFoundException e2) {
+ // We failed to create the file again, so there
+ // is something else wrong. Return null.
+ return null;
+ }
+ } else {
+ // Failed to create cache directory
+ return null;
+ }
}
ret.mimeType = mimeType;
}
@@ -371,14 +420,25 @@ public final class CacheManager {
*/
// only called from WebCore thread
static boolean removeAllCacheFiles() {
+ // Note, this is called before init() when the database is
+ // created or upgraded.
+ if (mBaseDir == null) {
+ // Init() has not been called yet, so just flag that
+ // we need to clear the cache when init() is called.
+ mClearCacheOnInit = true;
+ return true;
+ }
// delete cache in a separate thread to not block UI.
final Runnable clearCache = new Runnable() {
public void run() {
// delete all cache files
try {
String[] files = mBaseDir.list();
- for (int i = 0; i < files.length; i++) {
- new File(mBaseDir, files[i]).delete();
+ // if mBaseDir doesn't exist, files can be null.
+ if (files != null) {
+ for (int i = 0; i < files.length; i++) {
+ new File(mBaseDir, files[i]).delete();
+ }
}
} catch (SecurityException e) {
// Ignore SecurityExceptions.
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 7c296cc..cae94c9 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -354,12 +354,12 @@ class CallbackProxy extends Handler {
case SAVE_PASSWORD:
Bundle bundle = msg.getData();
- String host = bundle.getString("host");
+ String schemePlusHost = bundle.getString("host");
String username = bundle.getString("username");
String password = bundle.getString("password");
// If the client returned false it means that the notify message
// will not be sent and we should notify WebCore ourselves.
- if (!mWebView.onSavePassword(host, username, password,
+ if (!mWebView.onSavePassword(schemePlusHost, username, password,
(Message) msg.obj)) {
synchronized (this) {
notify();
@@ -700,8 +700,8 @@ class CallbackProxy extends Handler {
// functions just need to operate within the UI thread.
//--------------------------------------------------------------------------
- public boolean onSavePassword(String host, String username, String password,
- Message resumeMsg) {
+ public boolean onSavePassword(String schemePlusHost, String username,
+ String password, Message resumeMsg) {
// resumeMsg should be null at this point because we want to create it
// within the CallbackProxy.
if (Config.DEBUG) {
@@ -711,7 +711,7 @@ class CallbackProxy extends Handler {
Message msg = obtainMessage(SAVE_PASSWORD, resumeMsg);
Bundle bundle = msg.getData();
- bundle.putString("host", host);
+ bundle.putString("host", schemePlusHost);
bundle.putString("username", username);
bundle.putString("password", password);
synchronized (this) {
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 176471f..00b17d2 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -151,13 +151,34 @@ public final class CookieManager {
}
boolean domainMatch(String urlHost) {
- return urlHost.equals(domain) ||
- (domain.startsWith(".") &&
- urlHost.endsWith(domain.substring(1)));
+ if (domain.startsWith(".")) {
+ if (urlHost.endsWith(domain.substring(1))) {
+ int len = domain.length();
+ int urlLen = urlHost.length();
+ if (urlLen > len - 1) {
+ // make sure bar.com doesn't match .ar.com
+ return urlHost.charAt(urlLen - len) == PERIOD;
+ }
+ return true;
+ }
+ return false;
+ } else {
+ // exact match if domain is not leading w/ dot
+ return urlHost.equals(domain);
+ }
}
boolean pathMatch(String urlPath) {
- return urlPath.startsWith (path);
+ if (urlPath.startsWith(path)) {
+ int len = path.length();
+ int urlLen = urlPath.length();
+ if (urlLen > len) {
+ // make sure /wee doesn't match /we
+ return urlPath.charAt(len) == PATH_DELIM;
+ }
+ return true;
+ }
+ return false;
}
public String toString() {
@@ -232,7 +253,7 @@ public final class CookieManager {
* a system private class.
*/
public synchronized void setCookie(WebAddress uri, String value) {
- if (value != null && value.length() > 4096) {
+ if (value != null && value.length() > MAX_COOKIE_LENGTH) {
return;
}
if (!mAcceptCookie || uri == null) {
@@ -246,25 +267,19 @@ public final class CookieManager {
if (hostAndPath == null) {
return;
}
+
+ // For default path, when setting a cookie, the spec says:
+ //Path: Defaults to the path of the request URL that generated the
+ // Set-Cookie response, up to, but not including, the
+ // right-most /.
+ if (hostAndPath[1].length() > 1) {
+ int index = hostAndPath[1].lastIndexOf(PATH_DELIM);
+ hostAndPath[1] = hostAndPath[1].substring(0,
+ index > 0 ? index : index + 1);
+ }
ArrayList<Cookie> cookies = null;
try {
- /* Google is setting cookies like the following to detect whether
- * a browser supports cookie. We need to skip the leading "www" for
- * the default host. Otherwise the second cookie will make the first
- * cookie expired.
- *
- * url: https://www.google.com/accounts/ServiceLoginAuth
- * value: LSID=xxxxxxxxxxxxx;Path=/accounts;
- * Expires=Tue, 13-Mar-2018 01:41:39 GMT
- *
- * url: https://www.google.com/accounts/ServiceLoginAuth
- * value:LSID=EXPIRED;Domain=www.google.com;Path=/accounts;
- * Expires=Mon, 01-Jan-1990 00:00:00 GMT
- */
- if (hostAndPath[0].startsWith("www.")) {
- hostAndPath[0] = hostAndPath[0].substring(3);
- }
cookies = parseCookie(hostAndPath[0], hostAndPath[1], value);
} catch (RuntimeException ex) {
Log.e(LOGTAG, "parse cookie failed for: " + value);
@@ -622,26 +637,17 @@ public final class CookieManager {
/*
* find cookie path, e.g. for http://www.google.com, the path is "/"
- * for http://www.google.com/lab/, the path is "/lab/"
- * for http://www.google.com/lab/foo, the path is "/lab/"
- * for http://www.google.com/lab?hl=en, the path is "/lab/"
- * for http://www.google.com/lab.asp?hl=en, the path is "/"
+ * for http://www.google.com/lab/, the path is "/lab"
+ * for http://www.google.com/lab/foo, the path is "/lab/foo"
+ * for http://www.google.com/lab?hl=en, the path is "/lab"
+ * for http://www.google.com/lab.asp?hl=en, the path is "/lab.asp"
* Note: the path from URI has at least one "/"
+ * See:
+ * http://www.unix.com.ua/rfc/rfc2109.html
*/
index = ret[1].indexOf(QUESTION_MARK);
if (index != -1) {
ret[1] = ret[1].substring(0, index);
- if (ret[1].charAt(ret[1].length() - 1) != PATH_DELIM) {
- index = ret[1].lastIndexOf(PATH_DELIM);
- if (ret[1].lastIndexOf('.') > index) {
- ret[1] = ret[1].substring(0, index + 1);
- } else {
- ret[1] += PATH_DELIM;
- }
- }
- } else if (ret[1].charAt(ret[1].length() - 1) != PATH_DELIM) {
- ret[1] = ret[1].substring(0,
- ret[1].lastIndexOf(PATH_DELIM) + 1);
}
return ret;
} else
@@ -687,10 +693,6 @@ public final class CookieManager {
String cookieString) {
ArrayList<Cookie> ret = new ArrayList<Cookie>();
- // domain needs at least two PERIOD,
- if (host.indexOf(PERIOD) == host.lastIndexOf(PERIOD)) {
- host = PERIOD + host;
- }
int index = 0;
int length = cookieString.length();
while (true) {
@@ -841,15 +843,14 @@ public final class CookieManager {
"illegal format for max-age: " + value);
}
} else if (name.equals(PATH)) {
- // make sure path ends with PATH_DELIM
- if (value.length() > 1 &&
- value.charAt(value.length() - 1) != PATH_DELIM) {
- cookie.path = value + PATH_DELIM;
- } else {
- cookie.path = value;
- }
+ cookie.path = value;
} else if (name.equals(DOMAIN)) {
int lastPeriod = value.lastIndexOf(PERIOD);
+ if (lastPeriod == 0) {
+ // disallow cookies set for TLDs like [.com]
+ cookie.domain = null;
+ continue;
+ }
try {
Integer.parseInt(value.substring(lastPeriod + 1));
// no wildcard for ip address match
@@ -862,15 +863,22 @@ public final class CookieManager {
// ignore the exception, value is a host name
}
value = value.toLowerCase();
- if (value.endsWith(host) || host.endsWith(value)) {
- // domain needs at least two PERIOD
- if (value.indexOf(PERIOD) == lastPeriod) {
- value = PERIOD + value;
+ if (value.charAt(0) != PERIOD) {
+ // pre-pended dot to make it as a domain cookie
+ value = PERIOD + value;
+ lastPeriod++;
+ }
+ if (host.endsWith(value.substring(1))) {
+ int len = value.length();
+ int hostLen = host.length();
+ if (hostLen > (len - 1)
+ && host.charAt(hostLen - len) != PERIOD) {
+ // make sure the bar.com doesn't match .ar.com
+ cookie.domain = null;
+ continue;
}
// disallow cookies set on ccTLDs like [.co.uk]
- int len = value.length();
- if ((value.charAt(0) == PERIOD)
- && (len == lastPeriod + 3)
+ if ((len == lastPeriod + 3)
&& (len >= 6 && len <= 8)) {
String s = value.substring(1, lastPeriod);
if (Arrays.binarySearch(BAD_COUNTRY_2LDS, s) >= 0) {
@@ -880,7 +888,7 @@ public final class CookieManager {
}
cookie.domain = value;
} else {
- // no cross-site cookie
+ // no cross-site or more specific sub-domain cookie
cookie.domain = null;
}
}
diff --git a/core/java/android/webkit/DateSorter.java b/core/java/android/webkit/DateSorter.java
index 3dc15c1..750403b 100644
--- a/core/java/android/webkit/DateSorter.java
+++ b/core/java/android/webkit/DateSorter.java
@@ -17,7 +17,7 @@
package android.webkit;
import android.content.Context;
-import android.util.Log;
+import android.content.res.Resources;
import java.util.Calendar;
import java.util.Date;
@@ -40,6 +40,8 @@ public class DateSorter {
private long [] mBins = new long[DAY_COUNT];
private String [] mLabels = new String[DAY_COUNT];
+
+ private static final int NUM_DAYS_AGO = 5;
Date mDate = new Date();
Calendar mCal = Calendar.getInstance();
@@ -48,6 +50,7 @@ public class DateSorter {
* @param context Application context
*/
public DateSorter(Context context) {
+ Resources resources = context.getResources();
Calendar c = Calendar.getInstance();
beginningOfDay(c);
@@ -56,9 +59,9 @@ public class DateSorter {
mBins[0] = c.getTimeInMillis(); // Today
c.roll(Calendar.DAY_OF_YEAR, -1);
mBins[1] = c.getTimeInMillis(); // Yesterday
- c.roll(Calendar.DAY_OF_YEAR, -4);
+ c.roll(Calendar.DAY_OF_YEAR, -(NUM_DAYS_AGO - 1));
mBins[2] = c.getTimeInMillis(); // Five days ago
- c.roll(Calendar.DAY_OF_YEAR, 5); // move back to today
+ c.roll(Calendar.DAY_OF_YEAR, NUM_DAYS_AGO); // move back to today
c.roll(Calendar.MONTH, -1);
mBins[3] = c.getTimeInMillis(); // One month ago
c.roll(Calendar.MONTH, -1);
@@ -67,14 +70,14 @@ public class DateSorter {
// build labels
mLabels[0] = context.getText(com.android.internal.R.string.today).toString();
mLabels[1] = context.getText(com.android.internal.R.string.yesterday).toString();
- mLabels[2] = context.getString(com.android.internal.R.string.daysDurationPastPlural, 5);
- mLabels[3] = context.getText(com.android.internal.R.string.oneMonthDurationPast).toString();
- StringBuilder sb = new StringBuilder();
- sb.append(context.getText(com.android.internal.R.string.before)).append(" ");
- sb.append(context.getText(com.android.internal.R.string.oneMonthDurationPast));
- mLabels[4] = sb.toString();
-
+ int resId = com.android.internal.R.plurals.num_days_ago;
+ String format = resources.getQuantityString(resId, NUM_DAYS_AGO);
+ mLabels[2] = String.format(format, NUM_DAYS_AGO);
+
+ mLabels[3] = context.getText(com.android.internal.R.string.oneMonthDurationPast).toString();
+ mLabels[4] = context.getText(com.android.internal.R.string.beforeOneMonthDurationPast)
+ .toString();
}
/**
diff --git a/core/java/android/webkit/FileLoader.java b/core/java/android/webkit/FileLoader.java
index 6696bae..10343b2 100644
--- a/core/java/android/webkit/FileLoader.java
+++ b/core/java/android/webkit/FileLoader.java
@@ -16,6 +16,8 @@
package android.webkit;
+import com.android.internal.R;
+
import android.content.Context;
import android.content.res.AssetManager;
import android.net.http.EventHandler;
@@ -35,6 +37,7 @@ class FileLoader extends StreamLoader {
private String mPath; // Full path to the file to load
private Context mContext; // Application context, used for asset loads
private boolean mIsAsset; // Indicates if the load is an asset or not
+ private boolean mAllowFileAccess; // Allow/block file system access
/**
* Construct a FileLoader with the file URL specified as the content
@@ -44,12 +47,15 @@ class FileLoader extends StreamLoader {
* @param loadListener LoadListener to pass the content to
* @param context Context to use to access the asset.
* @param asset true if url points to an asset.
+ * @param allowFileAccess true if this WebView is allowed to access files
+ * on the file system.
*/
FileLoader(String url, LoadListener loadListener, Context context,
- boolean asset) {
+ boolean asset, boolean allowFileAccess) {
super(loadListener);
mIsAsset = asset;
mContext = context;
+ mAllowFileAccess = allowFileAccess;
// clean the Url
int index = url.indexOf('?');
@@ -73,35 +79,27 @@ class FileLoader extends StreamLoader {
mDataStream = mContext.getAssets().open(mPath,
AssetManager.ACCESS_STREAMING);
} else {
- mHandler.error(EventHandler.FILE_ERROR,
- mContext.getString(
- com.android.internal.R.string.httpErrorFileNotFound));
- return false;
-/*
- if (!mPath.startsWith(
- Environment.getExternalStorageDirectory().getPath())) {
+ if (!mAllowFileAccess) {
mHandler.error(EventHandler.FILE_ERROR,
- mContext.getString(
- com.android.internal.R.string.httpErrorFileNotFound));
+ mContext.getString(R.string.httpErrorFileNotFound));
return false;
}
+
mDataStream = new FileInputStream(mPath);
mContentLength = (new File(mPath)).length();
-*/
}
mHandler.status(1, 1, 0, "OK");
} catch (java.io.FileNotFoundException ex) {
mHandler.error(
EventHandler.FILE_NOT_FOUND_ERROR,
- mContext.getString(com.android.internal.R.string.httpErrorFileNotFound) +
+ mContext.getString(R.string.httpErrorFileNotFound) +
" " + ex.getMessage());
return false;
} catch (java.io.IOException ex) {
mHandler.error(EventHandler.FILE_ERROR,
- mContext.getString(
- com.android.internal.R.string.httpErrorFileNotFound) +
+ mContext.getString(R.string.httpErrorFileNotFound) +
" " + ex.getMessage());
return false;
}
@@ -121,10 +119,13 @@ class FileLoader extends StreamLoader {
* @param loadListener LoadListener to pass the content to
* @param context Context to use to access the asset.
* @param asset true if url points to an asset.
+ * @param allowFileAccess true if this FileLoader can load files from the
+ * file system.
*/
public static void requestUrl(String url, LoadListener loadListener,
- Context context, boolean asset) {
- FileLoader loader = new FileLoader(url, loadListener, context, asset);
+ Context context, boolean asset, boolean allowFileAccess) {
+ FileLoader loader = new FileLoader(url, loadListener, context, asset,
+ allowFileAccess);
loader.load();
}
diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java
index ebfebd0..7a3bbe6 100644
--- a/core/java/android/webkit/FrameLoader.java
+++ b/core/java/android/webkit/FrameLoader.java
@@ -28,16 +28,16 @@ import java.util.Map;
class FrameLoader {
- protected LoadListener mListener;
- protected Map<String, String> mHeaders;
- protected String mMethod;
- protected String mPostData;
- protected boolean mIsHighPriority;
- protected Network mNetwork;
- protected int mCacheMode;
- protected String mReferrer;
- protected String mUserAgent;
- protected String mContentType;
+ private final LoadListener mListener;
+ private final String mMethod;
+ private final boolean mIsHighPriority;
+ private final WebSettings mSettings;
+ private Map<String, String> mHeaders;
+ private byte[] mPostData;
+ private Network mNetwork;
+ private int mCacheMode;
+ private String mReferrer;
+ private String mContentType;
private static final int URI_PROTOCOL = 0x100;
@@ -53,43 +53,14 @@ class FrameLoader {
private static final String LOGTAG = "webkit";
- /*
- * Construct the Accept_Language once. If the user changes language, then
- * the phone will be rebooted.
- */
- private static String ACCEPT_LANGUAGE;
- static {
- // Set the accept-language to the current locale plus US if we are in a
- // different locale than US.
- java.util.Locale l = java.util.Locale.getDefault();
- ACCEPT_LANGUAGE = "";
- if (l.getLanguage() != null) {
- ACCEPT_LANGUAGE += l.getLanguage();
- if (l.getCountry() != null) {
- ACCEPT_LANGUAGE += "-" + l.getCountry();
- }
- }
- if (!l.equals(java.util.Locale.US)) {
- ACCEPT_LANGUAGE += ", ";
- java.util.Locale us = java.util.Locale.US;
- if (us.getLanguage() != null) {
- ACCEPT_LANGUAGE += us.getLanguage();
- if (us.getCountry() != null) {
- ACCEPT_LANGUAGE += "-" + us.getCountry();
- }
- }
- }
- }
-
-
- FrameLoader(LoadListener listener, String userAgent,
+ FrameLoader(LoadListener listener, WebSettings settings,
String method, boolean highPriority) {
mListener = listener;
mHeaders = null;
mMethod = method;
mIsHighPriority = highPriority;
mCacheMode = WebSettings.LOAD_NORMAL;
- mUserAgent = userAgent;
+ mSettings = settings;
}
public void setReferrer(String ref) {
@@ -97,7 +68,7 @@ class FrameLoader {
if (URLUtil.isNetworkUrl(ref)) mReferrer = ref;
}
- public void setPostData(String postData) {
+ public void setPostData(byte[] postData) {
mPostData = postData;
}
@@ -140,12 +111,15 @@ class FrameLoader {
}
if (URLUtil.isNetworkUrl(url)){
+ if (mSettings.getBlockNetworkLoads()) {
+ mListener.error(EventHandler.ERROR_BAD_URL,
+ mListener.getContext().getString(
+ com.android.internal.R.string.httpErrorBadUrl));
+ return false;
+ }
mNetwork = Network.getInstance(mListener.getContext());
- return handleHTTPLoad(false);
- } else if (URLUtil.isCookielessProxyUrl(url)) {
- mNetwork = Network.getInstance(mListener.getContext());
- return handleHTTPLoad(true);
- } else if (handleLocalFile(url, mListener)) {
+ return handleHTTPLoad();
+ } else if (handleLocalFile(url, mListener, mSettings)) {
return true;
}
if (Config.LOGV) {
@@ -160,14 +134,15 @@ class FrameLoader {
}
/* package */
- static boolean handleLocalFile(String url, LoadListener loadListener) {
+ static boolean handleLocalFile(String url, LoadListener loadListener,
+ WebSettings settings) {
if (URLUtil.isAssetUrl(url)) {
FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
- true);
+ true, settings.getAllowFileAccess());
return true;
} else if (URLUtil.isFileUrl(url)) {
FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
- false);
+ false, settings.getAllowFileAccess());
return true;
} else if (URLUtil.isContentUrl(url)) {
// Send the raw url to the ContentLoader because it will do a
@@ -186,21 +161,12 @@ class FrameLoader {
return false;
}
- protected boolean handleHTTPLoad(boolean proxyUrl) {
+ private boolean handleHTTPLoad() {
if (mHeaders == null) {
mHeaders = new HashMap<String, String>();
}
populateStaticHeaders();
-
- if (!proxyUrl) {
- // Don't add private information if this is a proxy load, ie don't
- // add cookies and authentication
- populateHeaders();
- } else {
- // If this is a proxy URL, fix it to be a network load
- mListener.setUrl("http://"
- + mListener.url().substring(URLUtil.PROXY_BASE.length()));
- }
+ populateHeaders();
// response was handled by UrlIntercept, don't issue HTTP request
if (handleUrlIntercept()) return true;
@@ -246,7 +212,7 @@ class FrameLoader {
* This function is used by handleUrlInterecpt and handleCache to
* setup a load from the byte stream in a CacheResult.
*/
- protected void startCacheLoad(CacheResult result) {
+ private void startCacheLoad(CacheResult result) {
if (Config.LOGV) {
Log.v(LOGTAG, "FrameLoader: loading from cache: "
+ mListener.url());
@@ -264,7 +230,7 @@ class FrameLoader {
*
* Returns true if the response was handled by UrlIntercept.
*/
- protected boolean handleUrlIntercept() {
+ 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(
@@ -284,7 +250,7 @@ class FrameLoader {
* correctly.
* Returns true if the response was handled from the cache
*/
- protected boolean handleCache() {
+ private boolean handleCache() {
switch (mCacheMode) {
// This mode is normally used for a reload, it instructs the http
// loader to not use the cached content.
@@ -357,11 +323,12 @@ class FrameLoader {
}
mHeaders.put("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7");
- if (ACCEPT_LANGUAGE.length() > 0) {
- mHeaders.put("Accept-Language", ACCEPT_LANGUAGE);
+ String acceptLanguage = mSettings.getAcceptLanguage();
+ if (acceptLanguage.length() > 0) {
+ mHeaders.put("Accept-Language", acceptLanguage);
}
-
- mHeaders.put("User-Agent", mUserAgent);
+
+ mHeaders.put("User-Agent", mSettings.getUserAgentString());
}
/**
diff --git a/core/java/android/webkit/HttpDateTime.java b/core/java/android/webkit/HttpDateTime.java
index b22f2ba..c6ec2d2 100644
--- a/core/java/android/webkit/HttpDateTime.java
+++ b/core/java/android/webkit/HttpDateTime.java
@@ -16,7 +16,7 @@
package android.webkit;
-import android.pim.Time;
+import android.text.format.Time;
import java.util.Calendar;
import java.util.regex.Matcher;
diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java
index 1cfea99..a0049ac 100644
--- a/core/java/android/webkit/JWebCoreJavaBridge.java
+++ b/core/java/android/webkit/JWebCoreJavaBridge.java
@@ -191,4 +191,5 @@ final class JWebCoreJavaBridge extends Handler {
private native void nativeFinalize();
private native void sharedTimerFired();
private native void setDeferringTimers(boolean defer);
+ public native void setNetworkOnLine(boolean online);
}
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
index 86947a2..c45ab29 100644
--- a/core/java/android/webkit/LoadListener.java
+++ b/core/java/android/webkit/LoadListener.java
@@ -99,7 +99,7 @@ class LoadListener extends Handler implements EventHandler {
// cache. It is needed if the cache returns a redirect
private String mMethod;
private Map<String, String> mRequestHeaders;
- private String mPostData;
+ private byte[] mPostData;
private boolean mIsHighPriority;
// Flag to indicate that this load is synchronous.
private boolean mSynchronous;
@@ -220,7 +220,8 @@ class LoadListener extends Handler implements EventHandler {
*/
Message contMsg = obtainMessage(MSG_LOCATION_CHANGED);
Message stopMsg = obtainMessage(MSG_CONTENT_FINISHED);
- //TODO, need to call mCallbackProxy and request UI.
+ mBrowserFrame.getCallbackProxy().onFormResubmission(
+ stopMsg, contMsg);
break;
}
@@ -286,15 +287,16 @@ class LoadListener extends Handler implements EventHandler {
if (newMimeType != null) {
mMimeType = newMimeType;
}
- } else if (mMimeType.equalsIgnoreCase("text/vnd.wap.wml") ||
- mMimeType.
- equalsIgnoreCase("application/vnd.wap.xhtml+xml")) {
+ } else if (mMimeType.equalsIgnoreCase("text/vnd.wap.wml")) {
// As we don't support wml, render it as plain text
mMimeType = "text/plain";
} else {
// XXX: Until the servers send us either correct xhtml or
// text/html, treat application/xhtml+xml as text/html.
- if (mMimeType.equalsIgnoreCase("application/xhtml+xml")) {
+ // It seems that xhtml+xml and vnd.wap.xhtml+xml mime
+ // subtypes are used interchangeably. So treat them the same.
+ if (mMimeType.equalsIgnoreCase("application/xhtml+xml") ||
+ mMimeType.equals("application/vnd.wap.xhtml+xml")) {
mMimeType = "text/html";
}
}
@@ -527,23 +529,21 @@ class LoadListener extends Handler implements EventHandler {
} else {
sendMessageInternal(obtainMessage(MSG_LOCATION_CHANGED));
}
-
- break;
+ return;
case HTTP_AUTH:
case HTTP_PROXY_AUTH:
+ // According to rfc2616, the response for HTTP_AUTH must include
+ // WWW-Authenticate header field and the response for
+ // HTTP_PROXY_AUTH must include Proxy-Authenticate header field.
if (mAuthHeader != null &&
(Network.getInstance(mContext).isValidProxySet() ||
!mAuthHeader.isProxy())) {
Network.getInstance(mContext).handleAuthRequest(this);
- } else {
- final int stringId =
- com.android.internal.R.string.httpErrorUnsupportedAuthScheme;
- error(EventHandler.ERROR_UNSUPPORTED_AUTH_SCHEME,
- getContext().getText(stringId).toString());
+ return;
}
- break;
-
+ break; // use default
+
case HTTP_NOT_MODIFIED:
// Server could send back NOT_MODIFIED even if we didn't
// ask for it, so make sure we have a valid CacheLoader
@@ -554,16 +554,18 @@ class LoadListener extends Handler implements EventHandler {
if (Config.LOGV) {
Log.v(LOGTAG, "LoadListener cache load url=" + url());
}
- break;
- } // Fall through to default if there is no CacheLoader
+ return;
+ }
+ break; // use default
case HTTP_NOT_FOUND:
// Not an error, the server can send back content.
default:
- sendMessageInternal(obtainMessage(MSG_CONTENT_FINISHED));
- detachRequestHandle();
break;
}
+
+ sendMessageInternal(obtainMessage(MSG_CONTENT_FINISHED));
+ detachRequestHandle();
}
/**
@@ -725,7 +727,7 @@ class LoadListener extends Handler implements EventHandler {
* @param isHighPriority
*/
void setRequestData(String method, Map<String, String> headers,
- String postData, boolean isHighPriority) {
+ byte[] postData, boolean isHighPriority) {
mMethod = method;
mRequestHeaders = headers;
mPostData = postData;
@@ -878,37 +880,16 @@ class LoadListener extends Handler implements EventHandler {
int statusCode = mStatusCode == HTTP_NOT_MODIFIED
? HTTP_OK : mStatusCode;
// pass content-type content-length and content-encoding
- int nativeResponse = nativeCreateResponse(mUrl, statusCode, mStatusText,
+ final int nativeResponse = nativeCreateResponse(
+ mUrl, statusCode, mStatusText,
mMimeType, mContentLength, mEncoding,
mCacheResult == null ? 0 : mCacheResult.expires / 1000);
if (mHeaders != null) {
- // "content-disposition",
- String value = mHeaders.getContentDisposition();
- if (value != null) {
- nativeSetResponseHeader(nativeResponse,
- Headers.CONTENT_DISPOSITION, value);
- }
-
- // location
- value = mHeaders.getLocation();
- if (value != null) {
- nativeSetResponseHeader(nativeResponse,
- Headers.LOCATION, value);
- }
-
- // refresh (paypal.com are using this)
- value = mHeaders.getRefresh();
- if (value != null) {
- nativeSetResponseHeader(nativeResponse,
- Headers.REFRESH, value);
- }
-
- // Content-Type
- value = mHeaders.getContentType();
- if (value != null) {
- nativeSetResponseHeader(nativeResponse,
- Headers.CONTENT_TYPE, value);
- }
+ mHeaders.getHeaders(new Headers.HeaderCallback() {
+ public void header(String name, String value) {
+ nativeSetResponseHeader(nativeResponse, name, value);
+ }
+ });
}
return nativeResponse;
}
@@ -1048,7 +1029,6 @@ class LoadListener extends Handler implements EventHandler {
cancel();
return;
} else if (!URLUtil.isNetworkUrl(redirectTo)) {
- cancel();
final String text = mContext
.getString(com.android.internal.R.string.open_permission_deny)
+ "\n" + redirectTo;
@@ -1250,7 +1230,12 @@ class LoadListener extends Handler implements EventHandler {
*/
void setUrl(String url) {
if (url != null) {
- mUrl = URLUtil.stripAnchor(url);
+ 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)) {
try {
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index 2700aa5..85cb8c0 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -65,7 +65,7 @@ public /* package */ class MimeTypeMap {
// if the filename contains special characters, we don't
// consider it valid for our matching purposes:
if (filename.length() > 0 &&
- Pattern.matches("[a-zA-Z_0-9\\.\\-]+", filename)) {
+ Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)]+", filename)) {
int dotPos = filename.lastIndexOf('.');
if (0 <= dotPos) {
return filename.substring(dotPos + 1);
diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java
index ea42e58..74622b3 100644
--- a/core/java/android/webkit/Network.java
+++ b/core/java/android/webkit/Network.java
@@ -155,7 +155,7 @@ class Network {
*/
public boolean requestURL(String method,
Map<String, String> headers,
- String postData,
+ byte [] postData,
LoadListener loader,
boolean isHighPriority) {
@@ -178,9 +178,8 @@ class Network {
InputStream bodyProvider = null;
int bodyLength = 0;
if (postData != null) {
- byte[] data = postData.getBytes();
- bodyLength = data.length;
- bodyProvider = new ByteArrayInputStream(data);
+ bodyLength = postData.length;
+ bodyProvider = new ByteArrayInputStream(postData);
}
RequestQueue q = mRequestQueue;
diff --git a/core/java/android/webkit/TextDialog.java b/core/java/android/webkit/TextDialog.java
index 95209c7..4e9370c 100644
--- a/core/java/android/webkit/TextDialog.java
+++ b/core/java/android/webkit/TextDialog.java
@@ -124,6 +124,10 @@ import android.widget.AutoCompleteTextView;
int flags = paint.getFlags() | Paint.SUBPIXEL_TEXT_FLAG |
Paint.ANTI_ALIAS_FLAG & ~Paint.DEV_KERN_TEXT_FLAG;
paint.setFlags(flags);
+ // Set the text color to black, regardless of the theme. This ensures
+ // that other applications that use embedded WebViews will properly
+ // display the text in textfields.
+ setTextColor(Color.BLACK);
}
@Override
@@ -395,6 +399,7 @@ import android.widget.AutoCompleteTextView;
* focus to the host.
*/
/* package */ void remove() {
+ mHandler.removeMessages(LONGPRESS);
mWebView.removeView(this);
mWebView.requestFocus();
}
@@ -532,6 +537,7 @@ import android.widget.AutoCompleteTextView;
mPreChange = text.toString();
Editable edit = (Editable) getText();
edit.replace(0, edit.length(), text);
+ updateCachedTextfield();
}
/**
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index 43666c1..0e8144e 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -145,6 +145,7 @@ public final class URLUtil {
/**
* @return True iff the url is an proxy url to allow cookieless network
* requests from a file url.
+ * @deprecated Cookieless proxy is no longer supported.
*/
public static boolean isCookielessProxyUrl(String url) {
return (null != url) && url.startsWith(PROXY_BASE);
diff --git a/core/java/android/webkit/WebBackForwardList.java b/core/java/android/webkit/WebBackForwardList.java
index c86b21d..9dea5ec 100644
--- a/core/java/android/webkit/WebBackForwardList.java
+++ b/core/java/android/webkit/WebBackForwardList.java
@@ -133,7 +133,7 @@ public class WebBackForwardList implements Cloneable, Serializable {
}
/* Remove the item at the given index. Called by JNI only. */
- private void removeHistoryItem(int index) {
+ private synchronized void removeHistoryItem(int index) {
// XXX: This is a special case. Since the callback is only triggered
// when removing the first item, we can assert that the index is 0.
// This lets us change the current index without having to query the
diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java
index 5570af8..a408e06 100644
--- a/core/java/android/webkit/WebHistoryItem.java
+++ b/core/java/android/webkit/WebHistoryItem.java
@@ -33,6 +33,8 @@ public class WebHistoryItem implements Cloneable {
private String mTitle;
// The base url of this item.
private String mUrl;
+ // The original requested url of this item.
+ private String mOriginalUrl;
// The favicon for this item.
private Bitmap mFavicon;
// The pre-flattened data used for saving the state.
@@ -95,6 +97,18 @@ public class WebHistoryItem implements Cloneable {
}
/**
+ * Return the original url of this history item. This was the requested
+ * url, the final url may be different as there might have been
+ * redirects while loading the site.
+ * @return The original url of this history item.
+ *
+ * @hide pending API Council approval
+ */
+ public String getOriginalUrl() {
+ return mOriginalUrl;
+ }
+
+ /**
* Return the document title of this history item.
* @return The document title of this history item.
* Note: The VM ensures 32-bit atomic read/write operations so we don't have
@@ -154,8 +168,10 @@ public class WebHistoryItem implements Cloneable {
private native void inflate(int nativeFrame, byte[] data);
/* Called by jni when the item is updated */
- private void update(String url, String title, Bitmap favicon, byte[] data) {
+ private void update(String url, String originalUrl, String title,
+ Bitmap favicon, byte[] data) {
mUrl = url;
+ mOriginalUrl = originalUrl;
mTitle = title;
mFavicon = favicon;
mFlattenedData = data;
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index de64b30..1a7c4ff 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 java.lang.SecurityException;
+import android.content.pm.PackageManager;
import java.util.Locale;
@@ -111,6 +113,7 @@ public class WebSettings {
// retrieve the values. After setXXX, postSync() needs to be called.
// XXX: The default values need to match those in WebSettings.cpp
private LayoutAlgorithm mLayoutAlgorithm = LayoutAlgorithm.NARROW_COLUMNS;
+ private Context mContext;
private TextSize mTextSize = TextSize.NORMAL;
private String mStandardFontFamily = "sans-serif";
private String mFixedFontFamily = "monospace";
@@ -119,7 +122,9 @@ public class WebSettings {
private String mCursiveFontFamily = "cursive";
private String mFantasyFontFamily = "fantasy";
private String mDefaultTextEncoding = "Latin-1";
- private String mUserAgent = ANDROID_USERAGENT;
+ private String mUserAgent;
+ private boolean mUseDefaultUserAgent;
+ private String mAcceptLanguage;
private String mPluginsPath = "";
private int mMinimumFontSize = 8;
private int mMinimumLogicalFontSize = 8;
@@ -127,12 +132,14 @@ public class WebSettings {
private int mDefaultFixedFontSize = 13;
private boolean mLoadsImagesAutomatically = true;
private boolean mBlockNetworkImage = false;
+ private boolean mBlockNetworkLoads = false;
private boolean mJavaScriptEnabled = false;
private boolean mPluginsEnabled = false;
private boolean mJavaScriptCanOpenWindowsAutomatically = false;
private boolean mUseDoubleTree = false;
private boolean mUseWideViewport = false;
private boolean mSupportMultipleWindows = false;
+ private boolean mShrinksStandaloneImagesToFit = false;
// Don't need to synchronize the get/set methods as they
// are basic types, also none of these values are used in
// native WebCore code.
@@ -144,6 +151,7 @@ public class WebSettings {
private boolean mNeedInitialFocus = true;
private boolean mNavDump = false;
private boolean mSupportZoom = true;
+ private boolean mAllowFileAccess = true;
// Class to handle messages before WebCore is ready.
private class EventHandler {
@@ -212,57 +220,112 @@ public class WebSettings {
// User agent strings.
private static final String DESKTOP_USERAGENT =
- "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/522+ " +
- "(KHTML, like Gecko) Safari/419.3";
- private static final String IPHONE_USERAGENT = "Mozilla/5.0 (iPhone; U; " +
- "CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) " +
- "Version/3.0 Mobile/1A543 Safari/419.3";
- private static String ANDROID_USERAGENT;
-
+ "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en)"
+ + " AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2"
+ + " Safari/525.20.1";
+ private static final String IPHONE_USERAGENT =
+ "Mozilla/5.0 (iPhone; U; CPU iPhone 2_1 like Mac OS X; en)"
+ + " AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2"
+ + " Mobile/5F136 Safari/525.20.1";
+ private static Locale sLocale;
+ private static Object sLockForLocaleSettings;
+
/**
* Package constructor to prevent clients from creating a new settings
* instance.
*/
- WebSettings(Context context) {
- if (ANDROID_USERAGENT == null) {
- StringBuffer arg = new StringBuffer();
- // Add version
- final String version = Build.VERSION.RELEASE;
- if (version.length() > 0) {
- arg.append(version);
- } else {
- // default to "1.0"
- arg.append("1.0");
+ WebSettings(Context context) {
+ mEventHandler = new EventHandler();
+ mContext = context;
+
+ if (sLockForLocaleSettings == null) {
+ sLockForLocaleSettings = new Object();
+ sLocale = Locale.getDefault();
+ }
+ mAcceptLanguage = getCurrentAcceptLanguage();
+ mUserAgent = getCurrentUserAgent();
+ mUseDefaultUserAgent = true;
+
+ verifyNetworkAccess();
+ }
+
+ /**
+ * Looks at sLocale and returns current AcceptLanguage String.
+ * @return Current AcceptLanguage String.
+ */
+ private String getCurrentAcceptLanguage() {
+ Locale locale;
+ synchronized(sLockForLocaleSettings) {
+ locale = sLocale;
+ }
+ StringBuffer buffer = new StringBuffer();
+ final String language = locale.getLanguage();
+ if (language != null) {
+ buffer.append(language);
+ final String country = locale.getCountry();
+ if (country != null) {
+ buffer.append("-");
+ buffer.append(country);
}
- arg.append("; ");
- // Initialize the mobile user agent with the default locale.
- final Locale l = Locale.getDefault();
- final String language = l.getLanguage();
- if (language != null) {
- arg.append(language.toLowerCase());
- final String country = l.getCountry();
+ }
+ if (!locale.equals(Locale.US)) {
+ buffer.append(", ");
+ java.util.Locale us = Locale.US;
+ if (us.getLanguage() != null) {
+ buffer.append(us.getLanguage());
+ final String country = us.getCountry();
if (country != null) {
- arg.append("-");
- arg.append(country.toLowerCase());
+ buffer.append("-");
+ buffer.append(country);
}
- } else {
- // default to "en"
- arg.append("en");
}
- // Add device name
- final String device = Build.DEVICE;
- if (device.length() > 0) {
- arg.append("; ");
- arg.append(device);
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * Looks at sLocale and mContext and returns current UserAgent String.
+ * @return Current UserAgent String.
+ */
+ private synchronized String getCurrentUserAgent() {
+ Locale locale;
+ synchronized(sLockForLocaleSettings) {
+ locale = sLocale;
+ }
+ StringBuffer buffer = new StringBuffer();
+ // Add version
+ final String version = Build.VERSION.RELEASE;
+ if (version.length() > 0) {
+ buffer.append(version);
+ } else {
+ // default to "1.0"
+ buffer.append("1.0");
+ }
+ buffer.append("; ");
+ final String language = locale.getLanguage();
+ if (language != null) {
+ buffer.append(language.toLowerCase());
+ final String country = locale.getCountry();
+ if (country != null) {
+ buffer.append("-");
+ buffer.append(country.toLowerCase());
}
- final String base = context.getResources().getText(
- com.android.internal.R.string.web_user_agent).toString();
- ANDROID_USERAGENT = String.format(base, arg);
- mUserAgent = ANDROID_USERAGENT;
+ } else {
+ // default to "en"
+ buffer.append("en");
}
- mEventHandler = new EventHandler();
+
+ final String device = Build.DEVICE;
+ if (device.length() > 0) {
+ buffer.append("; ");
+ buffer.append(device);
+ }
+ final String base = mContext.getResources().getText(
+ com.android.internal.R.string.web_user_agent).toString();
+ return String.format(base, buffer);
}
-
+
/**
* Enables dumping the pages navigation cache to a text file.
*/
@@ -292,6 +355,21 @@ public class WebSettings {
}
/**
+ * Enable or disable file access within WebView. File access is enabled by
+ * default.
+ */
+ public void setAllowFileAccess(boolean allow) {
+ mAllowFileAccess = allow;
+ }
+
+ /**
+ * Returns true if this WebView supports file access.
+ */
+ public boolean getAllowFileAccess() {
+ return mAllowFileAccess;
+ }
+
+ /**
* Store whether the WebView is saving form data.
*/
public void setSaveFormData(boolean save) {
@@ -377,34 +455,48 @@ public class WebSettings {
* Tell the WebView about user-agent string.
* @param ua 0 if the WebView should use an Android user-agent string,
* 1 if the WebView should use a desktop user-agent string.
- * 2 if the WebView should use an iPhone user-agent string.
+ *
+ * @deprecated Please use setUserAgentString instead.
*/
+ @Deprecated
public synchronized void setUserAgent(int ua) {
- if (ua == 0 && !ANDROID_USERAGENT.equals(mUserAgent)) {
- mUserAgent = ANDROID_USERAGENT;
- postSync();
- } else if (ua == 1 && !DESKTOP_USERAGENT.equals(mUserAgent)) {
- mUserAgent = DESKTOP_USERAGENT;
- postSync();
- } else if (ua == 2 && !IPHONE_USERAGENT.equals(mUserAgent)) {
- mUserAgent = IPHONE_USERAGENT;
- postSync();
+ String uaString = null;
+ if (ua == 1) {
+ if (DESKTOP_USERAGENT.equals(mUserAgent)) {
+ return; // do nothing
+ } else {
+ uaString = DESKTOP_USERAGENT;
+ }
+ } else if (ua == 2) {
+ if (IPHONE_USERAGENT.equals(mUserAgent)) {
+ return; // do nothing
+ } else {
+ uaString = IPHONE_USERAGENT;
+ }
+ } else if (ua != 0) {
+ return; // do nothing
}
+ setUserAgentString(uaString);
}
/**
* Return user-agent as int
* @return int 0 if the WebView is using an Android user-agent string.
* 1 if the WebView is using a desktop user-agent string.
- * 2 if the WebView is using an iPhone user-agent string.
+ * -1 if the WebView is using user defined user-agent string.
+ *
+ * @deprecated Please use getUserAgentString instead.
*/
+ @Deprecated
public synchronized int getUserAgent() {
if (DESKTOP_USERAGENT.equals(mUserAgent)) {
return 1;
} else if (IPHONE_USERAGENT.equals(mUserAgent)) {
return 2;
+ } else if (mUseDefaultUserAgent) {
+ return 0;
}
- return 0;
+ return -1;
}
/**
@@ -706,6 +798,40 @@ public class WebSettings {
public synchronized boolean getBlockNetworkImage() {
return mBlockNetworkImage;
}
+
+ /**
+ * @hide
+ * Tell the WebView to block all network load requests.
+ * @param flag True if the WebView should block all network loads
+ */
+ public synchronized void setBlockNetworkLoads(boolean flag) {
+ if (mBlockNetworkLoads != flag) {
+ mBlockNetworkLoads = flag;
+ verifyNetworkAccess();
+ }
+ }
+
+ /**
+ * @hide
+ * Return true if the WebView will block all network loads.
+ * @return True if the WebView blocks all network loads.
+ */
+ public synchronized boolean getBlockNetworkLoads() {
+ return mBlockNetworkLoads;
+ }
+
+
+ private void verifyNetworkAccess() {
+ if (!mBlockNetworkLoads) {
+ if (mContext.checkPermission("android.permission.INTERNET",
+ android.os.Process.myPid(), 0) !=
+ PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException
+ ("Permission denied - " +
+ "application missing INTERNET permission");
+ }
+ }
+ }
/**
* Tell the WebView to enable javascript execution.
@@ -806,11 +932,69 @@ public class WebSettings {
return mDefaultTextEncoding;
}
- /* Package api to grab the user agent string. */
- /*package*/ synchronized String getUserAgentString() {
+ /**
+ * Set the WebView's user-agent string. If the string "ua" is null or empty,
+ * it will use the system default user-agent string.
+ */
+ public synchronized void setUserAgentString(String ua) {
+ if (ua == null || ua.length() == 0) {
+ synchronized(sLockForLocaleSettings) {
+ Locale currentLocale = Locale.getDefault();
+ if (!sLocale.equals(currentLocale)) {
+ sLocale = currentLocale;
+ mAcceptLanguage = getCurrentAcceptLanguage();
+ }
+ }
+ ua = getCurrentUserAgent();
+ mUseDefaultUserAgent = true;
+ } else {
+ mUseDefaultUserAgent = false;
+ }
+
+ if (!ua.equals(mUserAgent)) {
+ mUserAgent = ua;
+ postSync();
+ }
+ }
+
+ /**
+ * Return the WebView's user-agent string.
+ */
+ public synchronized String getUserAgentString() {
+ if (DESKTOP_USERAGENT.equals(mUserAgent) ||
+ IPHONE_USERAGENT.equals(mUserAgent) ||
+ !mUseDefaultUserAgent) {
+ return mUserAgent;
+ }
+
+ boolean doPostSync = false;
+ synchronized(sLockForLocaleSettings) {
+ Locale currentLocale = Locale.getDefault();
+ if (!sLocale.equals(currentLocale)) {
+ sLocale = currentLocale;
+ mUserAgent = getCurrentUserAgent();
+ mAcceptLanguage = getCurrentAcceptLanguage();
+ doPostSync = true;
+ }
+ }
+ if (doPostSync) {
+ postSync();
+ }
return mUserAgent;
}
+ /* package api to grab the Accept Language string. */
+ /*package*/ synchronized String getAcceptLanguage() {
+ synchronized(sLockForLocaleSettings) {
+ Locale currentLocale = Locale.getDefault();
+ if (!sLocale.equals(currentLocale)) {
+ sLocale = currentLocale;
+ mAcceptLanguage = getCurrentAcceptLanguage();
+ }
+ }
+ return mAcceptLanguage;
+ }
+
/**
* Tell the WebView whether it needs to set a node to have focus when
* {@link WebView#requestFocus(int, android.graphics.Rect)} is called.
@@ -863,6 +1047,20 @@ public class WebSettings {
public int getCacheMode() {
return mOverrideCacheMode;
}
+
+ /**
+ * If set, webkit alternately shrinks and expands images viewed outside
+ * of an HTML page to fit the screen. This conflicts with attempts by
+ * the UI to zoom in and out of an image, so it is set false by default.
+ * @param shrink Set true to let webkit shrink the standalone image to fit.
+ * {@hide}
+ */
+ public void setShrinksStandaloneImagesToFit(boolean shrink) {
+ if (mShrinksStandaloneImagesToFit != shrink) {
+ mShrinksStandaloneImagesToFit = shrink;
+ postSync();
+ }
+ }
/**
* Transfer messages from the queue to the new WebCoreThread. Called from
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 6623257..7467b83 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -56,6 +56,7 @@ import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
+import android.view.inputmethod.InputMethodManager;
import android.webkit.WebViewCore.EventHub;
import android.widget.AbsoluteLayout;
import android.widget.AdapterView;
@@ -94,6 +95,12 @@ public class WebView extends AbsoluteLayout
implements ViewTreeObserver.OnGlobalFocusChangeListener,
ViewGroup.OnHierarchyChangeListener {
+ // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
+ // the screen all-the-time. Good for profiling our drawing code
+ static private final boolean AUTO_REDRAW_HACK = false;
+ // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
+ private boolean mAutoRedraw;
+
// keep debugging parameters near the top of the file
static final String LOGTAG = "webview";
static final boolean DEBUG = false;
@@ -112,7 +119,8 @@ public class WebView extends AbsoluteLayout
(com.android.internal.R.id.zoomMagnify);
}
- public void show(boolean canZoomOut) {
+ 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);
}
@@ -211,6 +219,17 @@ public class WebView extends AbsoluteLayout
private long mLastTouchTime;
/**
+ * Time of the last time sending touch event to WebViewCore
+ */
+ private long mLastSentTouchTime;
+
+ /**
+ * The minimum elapsed time before sending another ACTION_MOVE event to
+ * WebViewCore
+ */
+ private static final int TOUCH_SENT_INTERVAL = 100;
+
+ /**
* Helper class to get velocity for fling
*/
VelocityTracker mVelocityTracker;
@@ -226,6 +245,7 @@ public class WebView extends AbsoluteLayout
private static final int TOUCH_SHORTPRESS_MODE = 5;
private static final int TOUCH_DOUBLECLICK_MODE = 6;
private static final int TOUCH_DONE_MODE = 7;
+ private static final int TOUCH_SELECT_MODE = 8;
// touch mode values specific to scale+scroll
private static final int FIRST_SCROLL_ZOOM = 9;
private static final int SCROLL_ZOOM_ANIMATION_IN = 9;
@@ -234,6 +254,9 @@ public class WebView extends AbsoluteLayout
private static final int LAST_SCROLL_ZOOM = 11;
// end of touch mode values specific to scale+scroll
+ // Whether to forward the touch events to WebCore
+ private boolean mForwardTouchEvents = false;
+
// 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;
@@ -337,7 +360,9 @@ public class WebView extends AbsoluteLayout
static final int NOTIFY_FOCUS_SET_MSG_ID = 20;
static final int MARK_NODE_INVALID_ID = 21;
static final int UPDATE_CLIPBOARD = 22;
- static final int LONG_PRESS_TRACKBALL = 24;
+ static final int LONG_PRESS_ENTER = 23;
+ static final int PREVENT_TOUCH_ID = 24;
+ static final int WEBCORE_NEED_TOUCH_EVENTS = 25;
// width which view is considered to be fully zoomed out
static final int ZOOM_OUT_WIDTH = 1024;
@@ -524,23 +549,23 @@ public class WebView extends AbsoluteLayout
setVerticalScrollBarEnabled(true);
}
- /* package */ boolean onSavePassword(String host, String username,
+ /* package */ boolean onSavePassword(String schemePlusHost, String username,
String password, final Message resumeMsg) {
boolean rVal = false;
if (resumeMsg == null) {
// null resumeMsg implies saving password silently
- mDatabase.setUsernamePassword(host, username, password);
+ mDatabase.setUsernamePassword(schemePlusHost, username, password);
} else {
final Message remember = mPrivateHandler.obtainMessage(
REMEMBER_PASSWORD);
- remember.getData().putString("host", host);
+ remember.getData().putString("host", schemePlusHost);
remember.getData().putString("username", username);
remember.getData().putString("password", password);
remember.obj = resumeMsg;
final Message neverRemember = mPrivateHandler.obtainMessage(
NEVER_REMEMBER_PASSWORD);
- neverRemember.getData().putString("host", host);
+ neverRemember.getData().putString("host", schemePlusHost);
neverRemember.getData().putString("username", username);
neverRemember.getData().putString("password", password);
neverRemember.obj = resumeMsg;
@@ -749,14 +774,36 @@ public class WebView extends AbsoluteLayout
public static void disablePlatformNotifications() {
Network.disablePlatformNotifications();
}
+
+ /**
+ * Inform WebView of the network state. This is used to set
+ * the javascript property window.navigator.isOnline and
+ * generates the online/offline event as specified in HTML5, sec. 5.7.7
+ * @param networkUp boolean indicating if network is available
+ *
+ * @hide pending API Council approval
+ */
+ public void setNetworkAvailable(boolean networkUp) {
+ BrowserFrame.sJavaBridge.setNetworkOnLine(networkUp);
+ }
/**
- * Save the state of this WebView used in Activity.onSaveInstanceState.
+ * Save the state of this WebView used in
+ * {@link android.app.Activity#onSaveInstanceState}. Please note that this
+ * method no longer stores the display data for this WebView. The previous
+ * behavior could potentially leak files if {@link #restoreState} was never
+ * called. See {@link #savePicture} and {@link #restorePicture} for saving
+ * and restoring the display data.
* @param outState The Bundle to store the WebView state.
* @return The same copy of the back/forward list used to save the state. If
* saveState fails, the returned list will be null.
+ * @see #savePicture
+ * @see #restorePicture
*/
public WebBackForwardList saveState(Bundle outState) {
+ if (outState == null) {
+ return null;
+ }
// We grab a copy of the back/forward list because a client of WebView
// may have invalidated the history list by calling clearHistory.
WebBackForwardList list = copyBackForwardList();
@@ -782,29 +829,6 @@ public class WebView extends AbsoluteLayout
return null;
}
history.add(data);
- if (i == currentIndex) {
- Picture p = capturePicture();
- String path = mContext.getDir("thumbnails", 0).getPath()
- + File.separatorChar + hashCode() + "_pic.save";
- File f = new File(path);
- try {
- final FileOutputStream out = new FileOutputStream(f);
- p.writeToStream(out);
- out.close();
- } catch (FileNotFoundException e){
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (RuntimeException e) {
- e.printStackTrace();
- }
- if (f.length() > 0) {
- outState.putString("picture", path);
- outState.putInt("scrollX", mScrollX);
- outState.putInt("scrollY", mScrollY);
- outState.putFloat("scale", mActualScale);
- }
- }
}
outState.putSerializable("history", history);
if (mCertificate != null) {
@@ -815,16 +839,103 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Save the current display data to the Bundle given. Used in conjunction
+ * with {@link #saveState}.
+ * @param b A Bundle to store the display data.
+ * @param dest The file to store the serialized picture data. Will be
+ * overwritten with this WebView's picture data.
+ * @return True if the picture was successfully saved.
+ */
+ public boolean savePicture(Bundle b, File dest) {
+ if (dest == null || b == null) {
+ return false;
+ }
+ final Picture p = capturePicture();
+ try {
+ final FileOutputStream out = new FileOutputStream(dest);
+ p.writeToStream(out);
+ out.close();
+ } catch (FileNotFoundException e){
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ }
+ if (dest.length() > 0) {
+ b.putInt("scrollX", mScrollX);
+ b.putInt("scrollY", mScrollY);
+ b.putFloat("scale", mActualScale);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Restore the display data that was save in {@link #savePicture}. Used in
+ * conjunction with {@link #restoreState}.
+ * @param b A Bundle containing the saved display data.
+ * @param src The file where the picture data was stored.
+ * @return True if the picture was successfully restored.
+ */
+ public boolean restorePicture(Bundle b, File src) {
+ if (src == null || b == null) {
+ return false;
+ }
+ if (src.exists()) {
+ Picture p = null;
+ try {
+ final FileInputStream in = new FileInputStream(src);
+ p = Picture.createFromStream(in);
+ in.close();
+ } catch (FileNotFoundException e){
+ e.printStackTrace();
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ if (p != null) {
+ int sx = b.getInt("scrollX", 0);
+ int sy = b.getInt("scrollY", 0);
+ float scale = b.getFloat("scale", 1.0f);
+ mDrawHistory = true;
+ mHistoryPicture = p;
+ mScrollX = sx;
+ mScrollY = sy;
+ mHistoryWidth = Math.round(p.getWidth() * scale);
+ mHistoryHeight = Math.round(p.getHeight() * scale);
+ // as getWidth() / getHeight() of the view are not
+ // available yet, set up mActualScale, so that when
+ // onSizeChanged() is called, the rest will be set
+ // correctly
+ mActualScale = scale;
+ invalidate();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Restore the state of this WebView from the given map used in
- * Activity.onThaw. This method should be called to restore the state of
- * the WebView before using the object. If it is called after the WebView
- * has had a chance to build state (load pages, create a back/forward list,
- * etc.) there may be undesirable side-effects.
+ * {@link android.app.Activity#onRestoreInstanceState}. This method should
+ * be called to restore the state of the WebView before using the object. If
+ * it is called after the WebView has had a chance to build state (load
+ * pages, create a back/forward list, etc.) there may be undesirable
+ * side-effects. Please note that this method no longer restores the
+ * display data for this WebView. See {@link #savePicture} and {@link
+ * #restorePicture} for saving and restoring the display data.
* @param inState The incoming Bundle of state.
* @return The restored back/forward list or null if restoreState failed.
+ * @see #savePicture
+ * @see #restorePicture
*/
public WebBackForwardList restoreState(Bundle inState) {
WebBackForwardList returnList = null;
+ if (inState == null) {
+ return returnList;
+ }
if (inState.containsKey("index") && inState.containsKey("history")) {
mCertificate = SslCertificate.restoreState(
inState.getBundle("certificate"));
@@ -853,42 +964,6 @@ public class WebView extends AbsoluteLayout
WebHistoryItem item = new WebHistoryItem(data);
list.addHistoryItem(item);
}
- if (inState.containsKey("picture")) {
- String path = inState.getString("picture");
- File f = new File(path);
- if (f.exists()) {
- Picture p = null;
- try {
- final FileInputStream in = new FileInputStream(f);
- p = Picture.createFromStream(in);
- in.close();
- f.delete();
- } catch (FileNotFoundException e){
- e.printStackTrace();
- } catch (RuntimeException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- if (p != null) {
- int sx = inState.getInt("scrollX", 0);
- int sy = inState.getInt("scrollY", 0);
- float scale = inState.getFloat("scale", 1.0f);
- mDrawHistory = true;
- mHistoryPicture = p;
- mScrollX = sx;
- mScrollY = sy;
- mHistoryWidth = Math.round(p.getWidth() * scale);
- mHistoryHeight = Math.round(p.getHeight() * scale);
- // as getWidth() / getHeight() of the view are not
- // available yet, set up mActualScale, so that when
- // onSizeChanged() is called, the rest will be set
- // correctly
- mActualScale = scale;
- invalidate();
- }
- }
- }
// Grab the most recent copy to return to the caller.
returnList = copyBackForwardList();
// Update the copy to have the correct index.
@@ -929,14 +1004,22 @@ public class WebView extends AbsoluteLayout
* Load the given data into the WebView, use the provided URL as the base
* URL for the content. The base URL is the URL that represents the page
* that is loaded through this interface. As such, it is used for the
- * history entry and to resolve any relative URLs.
- * The failUrl is used if browser fails to load the data provided. If it
- * is empty or null, and the load fails, then no history entry is created.
+ * history entry and to resolve any relative URLs. The failUrl is used if
+ * browser fails to load the data provided. If it is empty or null, and the
+ * load fails, then no history entry is created.
+ * <p>
+ * Note for post 1.0. Due to the change in the WebKit, the access to asset
+ * files through "file:///android_asset/" for the sub resources is more
+ * restricted. If you provide null or empty string as baseUrl, you won't be
+ * able to access asset files. If the baseUrl is anything other than
+ * http(s)/ftp(s)/about/javascript as scheme, you can access asset files for
+ * sub resources.
+ *
* @param baseUrl Url to resolve relative paths with, if null defaults to
- * "about:blank"
+ * "about:blank"
* @param data A String of data in the given encoding.
* @param mimeType The MIMEType of the data. i.e. text/html. If null,
- * defaults to "text/html"
+ * defaults to "text/html"
* @param encoding The encoding of the data. i.e. utf-8, us-ascii
* @param failUrl URL to use if the content fails to load or null.
*/
@@ -1133,7 +1216,7 @@ public class WebView extends AbsoluteLayout
public void clearView() {
mContentWidth = 0;
mContentHeight = 0;
- mWebViewCore.clearContentPicture();
+ mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
}
/**
@@ -1197,7 +1280,7 @@ public class WebView extends AbsoluteLayout
clearTextEntry();
ExtendedZoomControls zoomControls = (ExtendedZoomControls)
getZoomControls();
- zoomControls.show(canZoomScrollOut());
+ zoomControls.show(true, canZoomScrollOut());
zoomControls.requestFocus();
mPrivateHandler.removeCallbacks(mZoomControlRunnable);
mPrivateHandler.postDelayed(mZoomControlRunnable,
@@ -1206,11 +1289,15 @@ public class WebView extends AbsoluteLayout
/**
* Return a HitTestResult based on the current focus node. If a HTML::a tag
- * is found, the HitTestResult type is set to ANCHOR_TYPE and the url has to
- * be retrieved through {@link #requestFocusNodeHref} asynchronously. If a
- * HTML::img tag is found, the HitTestResult type is set to IMAGE_TYPE and
- * the url has to be retrieved through {@link #requestFocusNodeHref}
- * asynchronously. If a phone number is found, the HitTestResult type is set
+ * is found and the anchor has a non-javascript url, the HitTestResult type
+ * is set to SRC_ANCHOR_TYPE and the url is set in the "extra" field. If the
+ * anchor does not have a url or if it is a javascript url, the type will
+ * be UNKNOWN_TYPE and the url has to be retrieved through
+ * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is
+ * found, the HitTestResult type is set to IMAGE_TYPE and the url is set in
+ * the "extra" field. A type of
+ * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a url that has an image as
+ * a child node. If a phone number is found, the HitTestResult type is set
* to PHONE_TYPE and the phone number is set in the "extra" field of
* HitTestResult. If a map address is found, the HitTestResult type is set
* to GEO_TYPE and the address is set in the "extra" field of HitTestResult.
@@ -1227,42 +1314,38 @@ public class WebView extends AbsoluteLayout
if (nativeUpdateFocusNode()) {
FocusNode node = mFocusNode;
- if (node.mIsAnchor && node.mText == null) {
- result.setType(HitTestResult.ANCHOR_TYPE);
- } else if (node.mIsTextField || node.mIsTextArea) {
+ if (node.mIsTextField || node.mIsTextArea) {
result.setType(HitTestResult.EDIT_TEXT_TYPE);
- } else {
+ } else if (node.mText != null) {
String text = node.mText;
- if (text != null) {
- if (text.startsWith(SCHEME_TEL)) {
- result.setType(HitTestResult.PHONE_TYPE);
- result.setExtra(text.substring(SCHEME_TEL.length()));
- } else if (text.startsWith(SCHEME_MAILTO)) {
- result.setType(HitTestResult.EMAIL_TYPE);
- result.setExtra(text.substring(SCHEME_MAILTO.length()));
- } else if (text.startsWith(SCHEME_GEO)) {
- result.setType(HitTestResult.GEO_TYPE);
- result.setExtra(URLDecoder.decode(text
- .substring(SCHEME_GEO.length())));
- }
+ if (text.startsWith(SCHEME_TEL)) {
+ result.setType(HitTestResult.PHONE_TYPE);
+ result.setExtra(text.substring(SCHEME_TEL.length()));
+ } else if (text.startsWith(SCHEME_MAILTO)) {
+ result.setType(HitTestResult.EMAIL_TYPE);
+ result.setExtra(text.substring(SCHEME_MAILTO.length()));
+ } else if (text.startsWith(SCHEME_GEO)) {
+ result.setType(HitTestResult.GEO_TYPE);
+ result.setExtra(URLDecoder.decode(text
+ .substring(SCHEME_GEO.length())));
+ } else if (node.mIsAnchor) {
+ result.setType(HitTestResult.SRC_ANCHOR_TYPE);
+ result.setExtra(text);
}
}
}
int type = result.getType();
if (type == HitTestResult.UNKNOWN_TYPE
- || type == HitTestResult.ANCHOR_TYPE) {
+ || type == HitTestResult.SRC_ANCHOR_TYPE) {
// Now check to see if it is an image.
int contentX = viewToContent((int) mLastTouchX + mScrollX);
int contentY = viewToContent((int) mLastTouchY + mScrollY);
- if (nativeIsImage(contentX, contentY)) {
+ String text = nativeImageURI(contentX, contentY);
+ if (text != null) {
result.setType(type == HitTestResult.UNKNOWN_TYPE ?
HitTestResult.IMAGE_TYPE :
- HitTestResult.IMAGE_ANCHOR_TYPE);
- }
- if (nativeHasSrcUrl()) {
- result.setType(result.getType() == HitTestResult.ANCHOR_TYPE ?
- HitTestResult.SRC_ANCHOR_TYPE :
HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
+ result.setExtra(text);
}
}
return result;
@@ -1284,12 +1367,15 @@ public class WebView extends AbsoluteLayout
if (nativeUpdateFocusNode()) {
FocusNode node = mFocusNode;
if (node.mIsAnchor) {
+ // NOTE: We may already have the url of the anchor stored in
+ // node.mText but it may be out of date or the caller may want
+ // to know about javascript urls.
mWebViewCore.sendMessage(EventHub.REQUEST_FOCUS_HREF,
node.mFramePointer, node.mNodePointer, hrefMsg);
}
}
}
-
+
/**
* Request the url of the image last touched by the user. msg will be sent
* to its target with a String representing the url as its object.
@@ -1298,13 +1384,13 @@ public class WebView extends AbsoluteLayout
* as the data member with "url" as key. The result can be null.
*/
public void requestImageRef(Message msg) {
- if (msg == null || mNativeClass == 0) {
- return;
- }
int contentX = viewToContent((int) mLastTouchX + mScrollX);
int contentY = viewToContent((int) mLastTouchY + mScrollY);
- mWebViewCore.sendMessage(EventHub.REQUEST_IMAGE_HREF, contentX,
- contentY, msg);
+ String ref = nativeImageURI(contentX, contentY);
+ Bundle data = msg.getData();
+ data.putString("url", ref);
+ msg.setData(data);
+ msg.sendToTarget();
}
private static int pinLoc(int x, int viewMax, int docMax) {
@@ -1340,6 +1426,25 @@ public class WebView extends AbsoluteLayout
return Math.round(x * mActualScale);
}
+ // Called by JNI to invalidate the View, given rectangle coordinates in
+ // content space
+ private void viewInvalidate(int l, int t, int r, int b) {
+ invalidate(contentToView(l), contentToView(t), contentToView(r),
+ contentToView(b));
+ }
+
+ // Called by JNI to invalidate the View after a delay, given rectangle
+ // coordinates in content space
+ private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) {
+ postInvalidateDelayed(delay, contentToView(l), contentToView(t),
+ contentToView(r), contentToView(b));
+ }
+
+ private Rect contentToView(Rect x) {
+ return new Rect(contentToView(x.left), contentToView(x.top)
+ , contentToView(x.right), contentToView(x.bottom));
+ }
+
/* call from webcoreview.draw(), so we're still executing in the UI thread
*/
private void recordNewContentSize(int w, int h, boolean updateLayout) {
@@ -1420,15 +1525,29 @@ public class WebView extends AbsoluteLayout
// Used to avoid sending many visible rect messages.
private Rect mLastVisibleRectSent;
+ private Rect mLastGlobalRect;
private Rect sendOurVisibleRect() {
Rect rect = new Rect();
calcOurContentVisibleRect(rect);
+ if (mFindIsUp) {
+ rect.bottom -= viewToContent(FIND_HEIGHT);
+ }
// Rect.equals() checks for null input.
if (!rect.equals(mLastVisibleRectSent)) {
- mWebViewCore.sendMessage(EventHub.SET_VISIBLE_RECT, rect);
+ mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
+ rect.left, rect.top);
mLastVisibleRectSent = rect;
}
+ Rect globalRect = new Rect();
+ if (getGlobalVisibleRect(globalRect)
+ && !globalRect.equals(mLastGlobalRect)) {
+ // TODO: the global offset is only used by windowRect()
+ // in ChromeClientAndroid ; other clients such as touch
+ // and mouse events could return view + screen relative points.
+ mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect);
+ mLastGlobalRect = globalRect;
+ }
return rect;
}
@@ -1488,12 +1607,19 @@ public class WebView extends AbsoluteLayout
}
}
+ // Make sure this stays in sync with the actual height of the FindDialog.
+ private static final int FIND_HEIGHT = 79;
+
@Override
protected int computeVerticalScrollRange() {
if (mDrawHistory) {
return mHistoryHeight;
} else {
- return contentToView(mContentHeight);
+ int height = contentToView(mContentHeight);
+ if (mFindIsUp) {
+ height += FIND_HEIGHT;
+ }
+ return height;
}
}
@@ -1507,6 +1633,21 @@ public class WebView extends AbsoluteLayout
WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
return h != null ? h.getUrl() : null;
}
+
+ /**
+ * Get the original url for the current page. This is not always the same
+ * as the url passed to WebViewClient.onPageStarted because although the
+ * load for that url has begun, the current page may not have changed.
+ * Also, there may have been redirects resulting in a different url to that
+ * originally requested.
+ * @return The url that was originally requested for the current page.
+ *
+ * @hide pending API Council approval
+ */
+ public String getOriginalUrl() {
+ WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
+ return h != null ? h.getOriginalUrl() : null;
+ }
/**
* Get the title for the current page. This is the title of the current page
@@ -1612,82 +1753,35 @@ public class WebView extends AbsoluteLayout
}
/*
- * Making the find methods private since we are disabling for 1.0
- *
- * Find and highlight the next occurance of the String find, beginning with
- * the current selection. Wraps the page infinitely, and scrolls. When
- * WebCore determines whether it has found it, the response message is sent
- * to its target with true(1) or false(0) as arg1 depending on whether the
- * text was found.
- * @param find String to find.
- * @param response A Message object that will be dispatched with the result
- * as the arg1 member. A result of 1 means the search
- * succeeded.
- */
- private void findNext(String find, Message response) {
- if (response == null) {
- return;
- }
- Message m = Message.obtain(null, EventHub.FIND, 1, 0, response);
- m.getData().putString("find", find);
- mWebViewCore.sendMessage(m);
- }
-
- /*
- * Making the find methods private since we are disabling for 1.0
- *
- * Find and highlight the previous occurance of the String find, beginning
- * with the current selection.
- * @param find String to find.
- * @param response A Message object that will be dispatched with the result
- * as the arg1 member. A result of 1 means the search
- * succeeded.
- */
- private void findPrevious(String find, Message response) {
- if (response == null) {
- return;
- }
- Message m = Message.obtain(null, EventHub.FIND, -1, 0, response);
- m.getData().putString("find", find);
- mWebViewCore.sendMessage(m);
- }
-
- /*
- * Making the find methods private since we are disabling for 1.0
+ * Highlight and scroll to the next occurance of String in findAll.
+ * Wraps the page infinitely, and scrolls. Must be called after
+ * calling findAll.
*
- * Find and highlight the first occurance of find, beginning with the start
- * of the page.
- * @param find String to find.
- * @param response A Message object that will be dispatched with the result
- * as the arg1 member. A result of 1 means the search
- * succeeded.
+ * @param forward Direction to search.
*/
- private void findFirst(String find, Message response) {
- if (response == null) {
- return;
- }
- Message m = Message.obtain(null, EventHub.FIND, 0, 0, response);
- m.getData().putString("find", find);
- mWebViewCore.sendMessage(m);
+ public void findNext(boolean forward) {
+ nativeFindNext(forward);
}
/*
- * Making the find methods private since we are disabling for 1.0
- *
* Find all instances of find on the page and highlight them.
* @param find String to find.
- * @param response A Message object that will be dispatched with the result
- * as the arg1 member. The result will be the number of
- * matches to the String find.
+ * @return int The number of occurances of the String "find"
+ * that were found.
*/
- private void findAll(String find, Message response) {
- if (response == null) {
- return;
- }
- Message m = Message.obtain(null, EventHub.FIND_ALL, 0, 0, response);
- m.getData().putString("find", find);
- mWebViewCore.sendMessage(m);
+ public int findAll(String find) {
+ mFindIsUp = true;
+ int result = nativeFindAll(find.toLowerCase(), find.toUpperCase());
+ invalidate();
+ return result;
}
+
+ // Used to know whether the find dialog is open. Affects whether
+ // or not we draw the highlights for matches.
+ private boolean mFindIsUp;
+
+ private native int nativeFindAll(String findLower, String findUpper);
+ private native void nativeFindNext(boolean forward);
/**
* Return the first substring consisting of the address of a physical
@@ -1714,12 +1808,15 @@ public class WebView extends AbsoluteLayout
}
/*
- * Making the find methods private since we are disabling for 1.0
- *
* Clear the highlighting surrounding text matches created by findAll.
*/
- private void clearMatches() {
- mWebViewCore.sendMessage(EventHub.CLEAR_MATCHES);
+ public void clearMatches() {
+ mFindIsUp = false;
+ 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);
+ invalidate();
}
/**
@@ -2023,10 +2120,21 @@ public class WebView extends AbsoluteLayout
nativeRecomputeFocus();
// Update the buttons in the picture, so when we draw the picture
// to the screen, they are in the correct state.
- nativeRecordButtons();
+ // Tell the native side if user is a) touching the screen,
+ // b) pressing the trackball down, or c) pressing the enter key
+ // If the focus is a button, we need to draw it in the pressed
+ // 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
+ || mTrackballDown || mGotEnterDown, false);
drawCoreAndFocusRing(canvas, mBackgroundColor, mDrawFocusRing);
}
canvas.restoreToCount(sc);
+
+ if (AUTO_REDRAW_HACK && mAutoRedraw) {
+ invalidate();
+ }
}
@Override
@@ -2097,7 +2205,12 @@ public class WebView extends AbsoluteLayout
if (mNativeClass == 0) return;
if (mShiftIsPressed) {
- nativeDrawSelection(canvas, mSelectX, mSelectY, mExtendSelection);
+ if (mTouchSelection) {
+ nativeDrawSelectionRegion(canvas);
+ } else {
+ nativeDrawSelection(canvas, mSelectX, mSelectY,
+ mExtendSelection);
+ }
} else if (drawFocus) {
if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
mTouchMode = TOUCH_SHORTPRESS_MODE;
@@ -2111,7 +2224,14 @@ public class WebView extends AbsoluteLayout
}
nativeDrawFocusRing(canvas);
}
+ // When the FindDialog is up, only draw the matches if we are not in
+ // the process of scrolling them into view.
+ if (mFindIsUp && !animateScroll) {
+ nativeDrawMatches(canvas);
+ }
}
+
+ private native void nativeDrawMatches(Canvas canvas);
private float scrollZoomGridScale(float invScale) {
float griddedInvScale = (int) (invScale * SCROLL_ZOOM_GRID)
@@ -2163,19 +2283,40 @@ public class WebView extends AbsoluteLayout
Rect scrollFrame = new Rect();
scrollFrame.set(mZoomScrollX, mZoomScrollY,
mZoomScrollX + width, mZoomScrollY + height);
- if (mContentWidth == width) {
- float offsetX = (width * halfScale - width) / 2;
+ if (mContentWidth * mZoomScrollLimit < width) {
+ float scale = zoomFrameScaleX(width, halfScale, 1.0f);
+ float offsetX = (width * scale - width) * 0.5f;
scrollFrame.left -= offsetX;
scrollFrame.right += offsetX;
}
- if (mContentHeight == height) {
- float offsetY = (height * halfScale - height) / 2;
+ if (mContentHeight * mZoomScrollLimit < height) {
+ float scale = zoomFrameScaleY(height, halfScale, 1.0f);
+ float offsetY = (height * scale - height) * 0.5f;
scrollFrame.top -= offsetY;
scrollFrame.bottom += offsetY;
}
return scrollFrame;
}
+ private float zoomFrameScaleX(int width, float halfScale, float noScale) {
+ // mContentWidth > width > mContentWidth * mZoomScrollLimit
+ if (mContentWidth <= width) {
+ return halfScale;
+ }
+ float part = (width - mContentWidth * mZoomScrollLimit)
+ / (width * (1 - mZoomScrollLimit));
+ return halfScale * part + noScale * (1.0f - part);
+ }
+
+ private float zoomFrameScaleY(int height, float halfScale, float noScale) {
+ if (mContentHeight <= height) {
+ return halfScale;
+ }
+ float part = (height - mContentHeight * mZoomScrollLimit)
+ / (height * (1 - mZoomScrollLimit));
+ return halfScale * part + noScale * (1.0f - part);
+ }
+
private float scrollZoomMagScale(float invScale) {
return (invScale * 2 + mInvActualScale) / 3;
}
@@ -2252,8 +2393,14 @@ public class WebView extends AbsoluteLayout
}
int sc = canvas.save();
canvas.clipRect(scrollFrame);
- float halfX = maxX > 0 ? (float) mZoomScrollX / maxX : 0.5f;
- float halfY = maxY > 0 ? (float) mZoomScrollY / maxY : 0.5f;
+ float halfX = (float) mZoomScrollX / maxX;
+ if (mContentWidth * mZoomScrollLimit < width) {
+ halfX = zoomFrameScaleX(width, 0.5f, halfX);
+ }
+ float halfY = (float) mZoomScrollY / maxY;
+ if (mContentHeight * mZoomScrollLimit < height) {
+ halfY = zoomFrameScaleY(height, 0.5f, halfY);
+ }
canvas.scale(halfScale, halfScale, mZoomScrollX + width * halfX
, mZoomScrollY + height * halfY);
if (LOGV_ENABLED) {
@@ -2350,7 +2497,10 @@ public class WebView extends AbsoluteLayout
}
private void zoomScrollOut() {
- if (canZoomScrollOut() == false) return;
+ if (canZoomScrollOut() == false) {
+ mTouchMode = TOUCH_DONE_MODE;
+ return;
+ }
startZoomScrollOut();
mTouchMode = SCROLL_ZOOM_ANIMATION_OUT;
invalidate();
@@ -2377,8 +2527,8 @@ public class WebView extends AbsoluteLayout
+ " mZoomScrollLimit=" + mZoomScrollLimit + " x=" + x);
}
x += maxScreenX * mLastScrollX / maxZoomX - mLastTouchX;
- x = Math.max(0, Math.min(maxScreenX, x));
- mZoomScrollX = (int) (x * maxZoomX / maxScreenX);
+ x *= Math.max(maxZoomX / maxScreenX, mZoomScrollInvLimit);
+ mZoomScrollX = Math.max(0, Math.min(maxZoomX, (int) x));
}
int maxZoomY = mContentHeight - height;
if (maxZoomY > 0) {
@@ -2390,8 +2540,8 @@ public class WebView extends AbsoluteLayout
+ " mZoomScrollLimit=" + mZoomScrollLimit + " y=" + y);
}
y += maxScreenY * mLastScrollY / maxZoomY - mLastTouchY;
- y = Math.max(0, Math.min(maxScreenY, y));
- mZoomScrollY = (int) (y * maxZoomY / maxScreenY);
+ y *= Math.max(maxZoomY / maxScreenY, mZoomScrollInvLimit);
+ mZoomScrollY = Math.max(0, Math.min(maxZoomY, (int) y));
}
if (oldX != mZoomScrollX || oldY != mZoomScrollY) {
invalidate();
@@ -2540,6 +2690,13 @@ public class WebView extends AbsoluteLayout
new WebViewCore.FocusData(mFocusData));
}
+ // Called by JNI when a touch event puts a textfield into focus.
+ private void displaySoftKeyboard() {
+ InputMethodManager imm = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mTextEntry);
+ }
+
// Used to register the global focus change listener one time to avoid
// multiple references to WebView
private boolean mGlobalFocusChangeListenerAdded;
@@ -2663,7 +2820,8 @@ public class WebView extends AbsoluteLayout
ArrayList<String> pastEntries = mDatabase.getFormData(mUrl, mName);
if (pastEntries.size() > 0) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
- mContext, com.android.internal.R.layout.simple_list_item_1,
+ mContext, com.android.internal.R.layout
+ .search_dropdown_item_1line,
pastEntries);
((HashMap) mUpdateMessage.obj).put("adapter", adapter);
mUpdateMessage.sendToTarget();
@@ -2679,152 +2837,166 @@ public class WebView extends AbsoluteLayout
mTextEntry.setRect(x, y, width, height);
}
- // These variables are used to determine long press with the enter key, or
+ // This is used to determine long press with the enter key, or
// a center key. Does not affect long press with the trackball/touch.
- private long mDownTime = 0;
- private boolean mGotDown = false;
+ private boolean mGotEnterDown = false;
// Enable copy/paste with trackball here.
// This should be left disabled until the framework can guarantee
// delivering matching key-up and key-down events for the shift key
- private static final boolean ENABLE_COPY_PASTE = false;
+ private static final boolean ENABLE_COPY_PASTE = true;
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) {
+ if (LOGV_ENABLED) {
+ Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
+ + ", " + event);
+ }
+
+ if (mNativeClass == 0) {
return false;
}
- if (keyCode != KeyEvent.KEYCODE_DPAD_CENTER &&
- keyCode != KeyEvent.KEYCODE_ENTER) {
- mGotDown = false;
+
+ // do this hack up front, so it always works, regardless of touch-mode
+ if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) {
+ mAutoRedraw = !mAutoRedraw;
+ if (mAutoRedraw) {
+ invalidate();
+ }
+ return true;
}
- if (mAltIsPressed == false && (keyCode == KeyEvent.KEYCODE_ALT_LEFT
- || keyCode == KeyEvent.KEYCODE_ALT_RIGHT)) {
- mAltIsPressed = true;
+
+ // Bubble up the key event if
+ // 1. it is a system key; or
+ // 2. the host application wants to handle it; or
+ // 3. webview is in scroll-zoom state;
+ if (event.isSystem()
+ || mCallbackProxy.uiOverrideKeyEvent(event)
+ || (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM)) {
+ return false;
}
+
if (ENABLE_COPY_PASTE && mShiftIsPressed == false
&& (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) {
mExtendSelection = false;
mShiftIsPressed = true;
- if (mNativeClass != 0 && nativeUpdateFocusNode()) {
+ if (nativeUpdateFocusNode()) {
FocusNode node = mFocusNode;
- mSelectX = node.mBounds.left;
- mSelectY = node.mBounds.top;
+ mSelectX = contentToView(node.mBounds.left);
+ mSelectY = contentToView(node.mBounds.top);
} else {
- mSelectX = mScrollX + SELECT_CURSOR_OFFSET;
- mSelectY = mScrollY + SELECT_CURSOR_OFFSET;
+ mSelectX = mScrollX + (int) mLastTouchX;
+ mSelectY = mScrollY + (int) mLastTouchY;
}
}
- if (keyCode == KeyEvent.KEYCODE_CALL) {
- if (mNativeClass != 0 && nativeUpdateFocusNode()) {
- FocusNode node = mFocusNode;
- String text = node.mText;
- if (!node.mIsTextField && !node.mIsTextArea && text != null &&
- text.startsWith(SCHEME_TEL)) {
- Intent intent = new Intent(Intent.ACTION_DIAL,
- Uri.parse(text));
- getContext().startActivity(intent);
- return true;
- }
+
+ if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
+ && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
+ // always handle the navigation keys in the UI thread
+ switchOutDrawHistory();
+ if (navHandledKey(keyCode, 1, false, event.getEventTime())) {
+ playSoundEffect(keyCodeToSoundsEffect(keyCode));
+ return true;
}
+ // Bubble up the key event as WebView doesn't handle it
return false;
}
- if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) {
- return false;
- }
- if (LOGV_ENABLED) {
- Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
- + ", " + event);
- }
- boolean isArrowKey = keyCode == KeyEvent.KEYCODE_DPAD_UP ||
- keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
- keyCode == KeyEvent.KEYCODE_DPAD_LEFT ||
- keyCode == KeyEvent.KEYCODE_DPAD_RIGHT;
- if (isArrowKey && event.getEventTime() - mTrackballLastTime
- <= TRACKBALL_KEY_TIMEOUT) {
- if (LOGV_ENABLED) Log.v(LOGTAG, "ignore arrow");
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+ || keyCode == KeyEvent.KEYCODE_ENTER) {
+ switchOutDrawHistory();
+ if (event.getRepeatCount() == 0) {
+ mGotEnterDown = true;
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(LONG_PRESS_ENTER), LONG_PRESS_TIMEOUT);
+ nativeRecordButtons(true, true);
+ // FIXME, currently in webcore keydown it doesn't do anything.
+ // In keyup, it calls both keydown and keyup, we should fix it.
+ mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode,
+ EventHub.KEYEVENT_UNHANDLED_TYPE, event);
+ return true;
+ }
+ // Bubble up the key event as WebView doesn't handle it
return false;
}
- boolean weHandledTheKey = false;
- if (event.getMetaState() == 0) {
+ if (getSettings().getNavDump()) {
switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_DOWN:
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- case KeyEvent.KEYCODE_DPAD_LEFT:
- // TODO: alternatively we can do panning as touch does
- switchOutDrawHistory();
- weHandledTheKey = navHandledKey(keyCode, 1, false
- , event.getEventTime());
- if (weHandledTheKey) {
- playSoundEffect(keyCodeToSoundsEffect(keyCode));
- }
+ case KeyEvent.KEYCODE_4:
+ // "/data/data/com.android.browser/displayTree.txt"
+ nativeDumpDisplayTree(getUrl());
break;
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_ENTER:
- if (event.getRepeatCount() == 0) {
- switchOutDrawHistory();
- mDownTime = event.getEventTime();
- mGotDown = true;
- return true;
- }
- if (mGotDown && event.getEventTime() - mDownTime >
- ViewConfiguration.getLongPressTimeout()) {
- performLongClick();
- mGotDown = false;
- return true;
- }
+ case KeyEvent.KEYCODE_5:
+ case KeyEvent.KEYCODE_6:
+ // 5: dump the dom tree to the file
+ // "/data/data/com.android.browser/domTree.txt"
+ // 6: dump the dom tree to the adb log
+ mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE,
+ (keyCode == KeyEvent.KEYCODE_5) ? 1 : 0, 0);
break;
- case KeyEvent.KEYCODE_9:
- if (mNativeClass != 0 && getSettings().getNavDump()) {
- debugDump();
- }
+ case KeyEvent.KEYCODE_7:
+ case KeyEvent.KEYCODE_8:
+ // 7: dump the render tree to the file
+ // "/data/data/com.android.browser/renderTree.txt"
+ // 8: dump the render tree to the adb log
+ mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE,
+ (keyCode == KeyEvent.KEYCODE_7) ? 1 : 0, 0);
break;
- default:
+ case KeyEvent.KEYCODE_9:
+ debugDump();
break;
}
}
- // suppress sending arrow keys to webkit
- if (!weHandledTheKey && !isArrowKey) {
- mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode, 0, event);
+ if (nativeFocusNodeWantsKeyEvents()) {
+ mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode,
+ EventHub.KEYEVENT_FOCUS_NODE_TYPE, event);
+ // return true as DOM handles the key
+ return true;
+ } else if (false) { // reserved to check the meta tag
+ // pass the key to DOM
+ mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode,
+ EventHub.KEYEVENT_UNHANDLED_TYPE, event);
+ // return true as DOM handles the key
+ return true;
}
- return weHandledTheKey;
+ // Bubble up the key event as WebView doesn't handle it
+ return false;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
- || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
- if (mExtendSelection) {
- // copy region so core operates on copy without touching orig.
- Region selection = new Region(nativeGetSelection());
- if (selection.isEmpty() == false) {
- Toast.makeText(mContext
- , com.android.internal.R.string.text_copied
- , Toast.LENGTH_SHORT).show();
- mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection);
- }
- }
- mShiftIsPressed = false;
- return true;
- }
if (LOGV_ENABLED) {
- Log.v(LOGTAG, "MT keyUp at" + System.currentTimeMillis()
+ Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis()
+ ", " + event);
}
- if (keyCode == KeyEvent.KEYCODE_ALT_LEFT
- || keyCode == KeyEvent.KEYCODE_ALT_RIGHT) {
- mAltIsPressed = false;
+
+ if (mNativeClass == 0) {
+ return false;
}
+
+ // special CALL handling when focus node's href is "tel:XXX"
+ if (keyCode == KeyEvent.KEYCODE_CALL && nativeUpdateFocusNode()) {
+ FocusNode node = mFocusNode;
+ String text = node.mText;
+ if (!node.mIsTextField && !node.mIsTextArea && text != null
+ && text.startsWith(SCHEME_TEL)) {
+ Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text));
+ getContext().startActivity(intent);
+ return true;
+ }
+ }
+
+ // Bubble up the key event if
+ // 1. it is a system key; or
+ // 2. the host application wants to handle it;
if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) {
return false;
}
+ // special handling in scroll_zoom state
if (mTouchMode >= FIRST_SCROLL_ZOOM && mTouchMode <= LAST_SCROLL_ZOOM) {
if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode
&& mTouchMode != SCROLL_ZOOM_ANIMATION_IN) {
@@ -2835,64 +3007,112 @@ public class WebView extends AbsoluteLayout
}
return false;
}
- Rect visibleRect = sendOurVisibleRect();
- // Note that sendOurVisibleRect calls viewToContent, so the coordinates
- // should be in content coordinates.
- boolean nodeOnScreen = false;
- boolean isTextField = false;
- boolean isTextArea = false;
- FocusNode node = null;
- if (mNativeClass != 0 && nativeUpdateFocusNode()) {
- node = mFocusNode;
- isTextField = node.mIsTextField;
- isTextArea = node.mIsTextArea;
- nodeOnScreen = Rect.intersects(node.mBounds, visibleRect);
- }
-
- if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
- if (mShiftIsPressed) {
- return false;
- }
- if (getSettings().supportZoom()) {
- if (mTouchMode == TOUCH_DOUBLECLICK_MODE) {
- zoomScrollOut();
- } else {
- if (LOGV_ENABLED) Log.v(LOGTAG, "TOUCH_DOUBLECLICK_MODE");
- mPrivateHandler.sendMessageDelayed(mPrivateHandler
- .obtainMessage(SWITCH_TO_ENTER), TAP_TIMEOUT);
- mTouchMode = TOUCH_DOUBLECLICK_MODE;
- }
+
+ if (ENABLE_COPY_PASTE && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
+ || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) {
+ if (commitCopy()) {
return true;
- } else {
- keyCode = KeyEvent.KEYCODE_ENTER;
}
}
- if (KeyEvent.KEYCODE_ENTER == keyCode) {
- if (LOGV_ENABLED) Log.v(LOGTAG, "KEYCODE_ENTER == keyCode");
- if (!nodeOnScreen) {
- return false;
+
+ if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
+ && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
+ // always handle the navigation keys in the UI thread
+ // Bubble up the key event as WebView doesn't handle it
+ return false;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+ || keyCode == KeyEvent.KEYCODE_ENTER) {
+ // remove the long press message first
+ mPrivateHandler.removeMessages(LONG_PRESS_ENTER);
+ mGotEnterDown = false;
+
+ if (KeyEvent.KEYCODE_DPAD_CENTER == keyCode) {
+ if (mShiftIsPressed) {
+ return false;
+ }
+ if (getSettings().supportZoom()) {
+ if (mTouchMode == TOUCH_DOUBLECLICK_MODE) {
+ zoomScrollOut();
+ } else {
+ if (LOGV_ENABLED) {
+ Log.v(LOGTAG, "TOUCH_DOUBLECLICK_MODE");
+ }
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(SWITCH_TO_ENTER), TAP_TIMEOUT);
+ mTouchMode = TOUCH_DOUBLECLICK_MODE;
+ }
+ return true;
+ }
+ }
+
+ Rect visibleRect = sendOurVisibleRect();
+ // Note that sendOurVisibleRect calls viewToContent, so the
+ // coordinates should be in content coordinates.
+ boolean nodeOnScreen = false;
+ boolean isTextField = false;
+ boolean isTextArea = false;
+ FocusNode node = null;
+ if (nativeUpdateFocusNode()) {
+ node = mFocusNode;
+ isTextField = node.mIsTextField;
+ isTextArea = node.mIsTextArea;
+ nodeOnScreen = Rect.intersects(node.mBounds, visibleRect);
}
- if (node != null && !isTextField && !isTextArea) {
+ if (nodeOnScreen && !isTextField && !isTextArea) {
nativeSetFollowedLink(true);
- mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS,
+ mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS,
EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0,
new WebViewCore.FocusData(mFocusData));
- if (mCallbackProxy.uiOverrideUrlLoading(node.mText)) {
- return true;
- }
playSoundEffect(SoundEffectConstants.CLICK);
+ if (!mCallbackProxy.uiOverrideUrlLoading(node.mText)) {
+ mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode,
+ EventHub.KEYEVENT_UNHANDLED_TYPE, event);
+ }
+ return true;
}
- }
- if (!nodeOnScreen) {
- // FIXME: Want to give Callback a chance to handle it, and
- // possibly pass down to javascript.
+ // Bubble up the key event as WebView doesn't handle it
return false;
}
- if (LOGV_ENABLED) Log.v(LOGTAG, "onKeyUp send EventHub.KEY_UP");
- mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode, 0, event);
- return true;
+
+ if (nativeFocusNodeWantsKeyEvents()) {
+ mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode,
+ EventHub.KEYEVENT_FOCUS_NODE_TYPE, event);
+ // return true as DOM handles the key
+ return true;
+ } else if (false) { // reserved to check the meta tag
+ // pass the key to DOM
+ mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode,
+ EventHub.KEYEVENT_UNHANDLED_TYPE, event);
+ // return true as DOM handles the key
+ return true;
+ }
+
+ // Bubble up the key event as WebView doesn't handle it
+ return false;
}
-
+
+ private boolean commitCopy() {
+ boolean copiedSomething = false;
+ if (mExtendSelection) {
+ // copy region so core operates on copy without touching orig.
+ Region selection = new Region(nativeGetSelection());
+ if (selection.isEmpty() == false) {
+ Toast.makeText(mContext
+ , com.android.internal.R.string.text_copied
+ , Toast.LENGTH_SHORT).show();
+ mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection);
+ copiedSomething = true;
+ }
+ }
+ mShiftIsPressed = false;
+ if (mTouchMode == TOUCH_SELECT_MODE) {
+ mTouchMode = TOUCH_INIT_MODE;
+ }
+ return copiedSomething;
+ }
+
// Set this as a hierarchy change listener so we can know when this view
// is removed and still have access to our parent.
@Override
@@ -2962,6 +3182,7 @@ public class WebView extends AbsoluteLayout
// we regain focus.
mDrawFocusRing = false;
mGotKeyDown = false;
+ mShiftIsPressed = false;
if (inEditingMode()) {
clearTextEntry();
mNeedsUpdateTextEntry = true;
@@ -3055,8 +3276,7 @@ public class WebView extends AbsoluteLayout
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (mNativeClass == 0 || !isClickable() || !isLongClickable() ||
- !hasFocus()) {
+ if (mNativeClass == 0 || !isClickable() || !isLongClickable()) {
return false;
}
@@ -3069,6 +3289,32 @@ public class WebView extends AbsoluteLayout
float x = ev.getX();
float y = ev.getY();
long eventTime = ev.getEventTime();
+
+ // Due to the touch screen edge effect, a touch closer to the edge
+ // always snapped to the edge. As getViewWidth() can be different from
+ // getWidth() due to the scrollbar, adjusting the point to match
+ // getViewWidth(). Same applied to the height.
+ if (x > getViewWidth() - 1) {
+ x = getViewWidth() - 1;
+ }
+ if (y > getViewHeight() - 1) {
+ y = getViewHeight() - 1;
+ }
+
+ // pass the touch events from UI thread to WebCore thread
+ if (mForwardTouchEvents && mTouchMode != SCROLL_ZOOM_OUT
+ && mTouchMode != SCROLL_ZOOM_ANIMATION_IN
+ && mTouchMode != SCROLL_ZOOM_ANIMATION_OUT
+ && (action != MotionEvent.ACTION_MOVE ||
+ eventTime - mLastSentTouchTime > TOUCH_SENT_INTERVAL)) {
+ WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData();
+ ted.mAction = action;
+ ted.mX = viewToContent((int) x + mScrollX);
+ ted.mY = viewToContent((int) y + mScrollY);;
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+ mLastSentTouchTime = eventTime;
+ }
+
switch (action) {
case MotionEvent.ACTION_DOWN: {
if (mTouchMode == SCROLL_ZOOM_ANIMATION_IN
@@ -3083,6 +3329,16 @@ public class WebView extends AbsoluteLayout
mScroller.abortAnimation();
mTouchMode = TOUCH_DRAG_START_MODE;
mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
+ } else if (mShiftIsPressed) {
+ mSelectX = mScrollX + (int) x;
+ mSelectY = mScrollY + (int) y;
+ mTouchMode = TOUCH_SELECT_MODE;
+ if (LOGV_ENABLED) {
+ Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY);
+ }
+ nativeMoveSelection(viewToContent(mSelectX)
+ , viewToContent(mSelectY), false);
+ mTouchSelection = mExtendSelection = true;
} else {
mTouchMode = TOUCH_INIT_MODE;
}
@@ -3116,6 +3372,17 @@ public class WebView extends AbsoluteLayout
int deltaY = (int) (mLastTouchY - y);
if (mTouchMode != TOUCH_DRAG_MODE) {
+ if (mTouchMode == TOUCH_SELECT_MODE) {
+ mSelectX = mScrollX + (int) x;
+ mSelectY = mScrollY + (int) y;
+ if (LOGV_ENABLED) {
+ Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY);
+ }
+ nativeMoveSelection(viewToContent(mSelectX)
+ , viewToContent(mSelectY), true);
+ invalidate();
+ break;
+ }
if ((deltaX * deltaX + deltaY * deltaY)
< TOUCH_SLOP_SQUARE) {
break;
@@ -3214,11 +3481,13 @@ public class WebView extends AbsoluteLayout
mUserScroll = true;
}
- if (mZoomControls != null && mMinZoomScale < mMaxZoomScale) {
+ boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
+ boolean showMagnify = canZoomScrollOut();
+ if (mZoomControls != null && (showPlusMinus || showMagnify)) {
if (mZoomControls.getVisibility() == View.VISIBLE) {
mPrivateHandler.removeCallbacks(mZoomControlRunnable);
} else {
- mZoomControls.show(canZoomScrollOut());
+ mZoomControls.show(showPlusMinus, showMagnify);
}
mPrivateHandler.postDelayed(mZoomControlRunnable,
ZOOM_CONTROLS_TIMEOUT);
@@ -3244,6 +3513,10 @@ public class WebView extends AbsoluteLayout
doShortPress();
}
break;
+ case TOUCH_SELECT_MODE:
+ commitCopy();
+ mTouchSelection = mExtendSelection = false;
+ break;
case SCROLL_ZOOM_ANIMATION_IN:
case SCROLL_ZOOM_ANIMATION_OUT:
// no action during scroll animation
@@ -3349,6 +3622,7 @@ public class WebView extends AbsoluteLayout
private int mTrackballXMove = 0;
private int mTrackballYMove = 0;
private boolean mExtendSelection = false;
+ private boolean mTouchSelection = false;
private static final int TRACKBALL_KEY_TIMEOUT = 1000;
private static final int TRACKBALL_TIMEOUT = 200;
private static final int TRACKBALL_WAIT = 100;
@@ -3359,7 +3633,6 @@ public class WebView extends AbsoluteLayout
private int mSelectX = 0;
private int mSelectY = 0;
private boolean mShiftIsPressed = false;
- private boolean mAltIsPressed = false;
private boolean mTrackballDown = false;
private long mTrackballUpTime = 0;
private long mLastFocusTime = 0;
@@ -3386,17 +3659,18 @@ public class WebView extends AbsoluteLayout
@Override
public boolean onTrackballEvent(MotionEvent ev) {
long time = ev.getEventTime();
- if (mAltIsPressed) {
+ if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) {
if (ev.getY() > 0) pageDown(true);
if (ev.getY() < 0) pageUp(true);
return true;
}
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mPrivateHandler.removeMessages(SWITCH_TO_ENTER);
- mPrivateHandler.sendMessageDelayed(
- mPrivateHandler.obtainMessage(LONG_PRESS_TRACKBALL), 1000);
mTrackTrackball = true;
mTrackballDown = true;
+ if (mNativeClass != 0) {
+ nativeRecordButtons(true, true);
+ }
if (time - mLastFocusTime <= TRACKBALL_TIMEOUT
&& !mLastFocusBounds.equals(nativeGetFocusRingBounds())) {
nativeSelectBestAt(mLastFocusBounds);
@@ -3408,7 +3682,8 @@ public class WebView extends AbsoluteLayout
}
return false; // let common code in onKeyDown at it
} else if (mTrackTrackball) {
- mPrivateHandler.removeMessages(LONG_PRESS_TRACKBALL);
+ // LONG_PRESS_ENTER is set in common onKeyDown
+ mPrivateHandler.removeMessages(LONG_PRESS_ENTER);
mTrackTrackball = false;
}
if (ev.getAction() == MotionEvent.ACTION_UP) {
@@ -3823,13 +4098,7 @@ public class WebView extends AbsoluteLayout
return;
}
switchOutDrawHistory();
- // 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);
-
- // call uiOverride next to check whether it is a special node,
+ // call uiOverride to check whether it is a special node,
// phone/email/address, which are not handled by WebKit
if (nativeUpdateFocusNode()) {
FocusNode node = mFocusNode;
@@ -3840,6 +4109,11 @@ public class WebView extends AbsoluteLayout
}
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);
}
@Override
@@ -3907,6 +4181,8 @@ public class WebView extends AbsoluteLayout
mHeightCanMeasure = false;
}
}
+ } else {
+ mHeightCanMeasure = false;
}
if (mNativeClass != 0) {
nativeSetHeightCanMeasure(mHeightCanMeasure);
@@ -3915,6 +4191,8 @@ public class WebView extends AbsoluteLayout
if (widthMode == MeasureSpec.UNSPECIFIED) {
mWidthCanMeasure = true;
measuredWidth = contentWidth;
+ } else {
+ mWidthCanMeasure = false;
}
synchronized (this) {
@@ -4076,10 +4354,14 @@ public class WebView extends AbsoluteLayout
break;
case NEW_PICTURE_MSG_ID:
// called for new content
- final Point viewSize = (Point) msg.obj;
+ final WebViewCore.DrawData draw =
+ (WebViewCore.DrawData) msg.obj;
+ final Point viewSize = draw.mViewPoint;
if (mZoomScale > 0) {
- if (Math.abs(mZoomScale * viewSize.x -
- getViewWidth()) < 1) {
+ // use the same logic in sendViewSizeZoom() to make sure
+ // the mZoomScale has matched the viewSize so that we
+ // can clear mZoomScale
+ if (Math.round(getViewWidth() / mZoomScale) == viewSize.x) {
mZoomScale = 0;
mWebViewCore.sendMessage(EventHub.SET_SNAP_ANCHOR,
0, 0);
@@ -4091,8 +4373,14 @@ public class WebView extends AbsoluteLayout
// received in the fixed dimension.
final boolean updateLayout = viewSize.x == mLastWidthSent
&& viewSize.y == mLastHeightSent;
- recordNewContentSize(msg.arg1, msg.arg2, updateLayout);
- invalidate();
+ recordNewContentSize(draw.mWidthHeight.x,
+ draw.mWidthHeight.y, updateLayout);
+ if (LOGV_ENABLED) {
+ Rect b = draw.mInvalRegion.getBounds();
+ Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
+ b.left+","+b.top+","+b.right+","+b.bottom+"}");
+ }
+ invalidate(contentToView(draw.mInvalRegion.getBounds()));
if (mPictureListener != null) {
mPictureListener.onNewPicture(WebView.this, capturePicture());
}
@@ -4156,6 +4444,7 @@ public class WebView extends AbsoluteLayout
}
int initialScale = msg.arg1;
int viewportWidth = msg.arg2;
+ // by default starting a new page with 100% zoom scale.
float scale = 1.0f;
if (mInitialScale > 0) {
scale = mInitialScale / 100.0f;
@@ -4165,10 +4454,15 @@ public class WebView extends AbsoluteLayout
// to 0
mLastWidthSent = 0;
}
- // by default starting a new page with 100% zoom scale.
- scale = initialScale == 0 ? (viewportWidth > 0 ?
- ((float) width / viewportWidth) : 1.0f)
- : initialScale / 100.0f;
+ if (initialScale == 0) {
+ // if viewportWidth is defined and it is smaller
+ // than the view width, zoom in to fill the view
+ if (viewportWidth > 0 && viewportWidth < width) {
+ scale = (float) width / viewportWidth;
+ }
+ } else {
+ scale = initialScale / 100.0f;
+ }
}
setNewZoomScale(scale, false);
break;
@@ -4213,10 +4507,32 @@ public class WebView extends AbsoluteLayout
WebViewCore.resumeUpdate(mWebViewCore);
break;
- case LONG_PRESS_TRACKBALL:
+ case LONG_PRESS_ENTER:
+ // as this is shared by keydown and trackballdown, reset all
+ // the states
+ mGotEnterDown = false;
mTrackTrackball = false;
mTrackballDown = false;
- performLongClick();
+ // LONG_PRESS_ENTER is sent as a delayed message. If we
+ // switch to windows overview, the WebView will be
+ // temporarily removed from the view system. In that case,
+ // do nothing.
+ if (getParent() != null) {
+ performLongClick();
+ }
+ break;
+
+ case WEBCORE_NEED_TOUCH_EVENTS:
+ mForwardTouchEvents = (msg.arg1 != 0);
+ 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);
+ }
+ mTouchMode = TOUCH_DONE_MODE;
break;
default:
@@ -4514,10 +4830,7 @@ public class WebView extends AbsoluteLayout
}
Rect contentFocus = nativeGetFocusRingBounds();
if (contentFocus.isEmpty()) return keyHandled;
- Rect viewFocus = new Rect(contentToView(contentFocus.left)
- , contentToView(contentFocus.top)
- , contentToView(contentFocus.right)
- , contentToView(contentFocus.bottom));
+ Rect viewFocus = contentToView(contentFocus);
Rect visRect = new Rect();
calcOurVisibleRect(visRect);
Rect outset = new Rect(visRect);
@@ -4559,7 +4872,7 @@ public class WebView extends AbsoluteLayout
public void debugDump() {
nativeDebugDump();
- mWebViewCore.sendMessage(EventHub.DUMP_WEBKIT);
+ mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE);
}
/**
@@ -4583,6 +4896,7 @@ public class WebView extends AbsoluteLayout
private native void nativeDrawFocusRing(Canvas content);
private native void nativeDrawSelection(Canvas content
, int x, int y, boolean extendSelection);
+ private native void nativeDrawSelectionRegion(Canvas content);
private native boolean nativeUpdateFocusNode();
private native Rect nativeGetFocusRingBounds();
private native Rect nativeGetNavBounds();
@@ -4593,17 +4907,27 @@ public class WebView extends AbsoluteLayout
boolean noScroll);
private native void nativeNotifyFocusSet(boolean inEditingMode);
private native void nativeRecomputeFocus();
- private native void nativeRecordButtons();
+ // 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 nativeResetFocus();
private native void nativeResetNavClipBounds();
private native void nativeSelectBestAt(Rect rect);
+ private native void nativeSetFindIsDown();
private native void nativeSetFollowedLink(boolean followed);
private native void nativeSetHeightCanMeasure(boolean measure);
private native void nativeSetNavBounds(Rect rect);
private native void nativeSetNavClipBounds(Rect rect);
- private native boolean nativeHasSrcUrl();
- private native boolean nativeIsImage(int x, int y);
+ private native String nativeImageURI(int x, int y);
+ /**
+ * Returns true if the native focus nodes says it wants to handle key events
+ * (ala plugins). This can only be called if mNativeClass is non-zero!
+ */
+ private native boolean nativeFocusNodeWantsKeyEvents();
private native void nativeMoveSelection(int x, int y
, boolean extendSelection);
private native Region nativeGetSelection();
+
+ private native void nativeDumpDisplayTree(String urlOrNull);
}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 263346e..9e413f9 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -70,13 +70,6 @@ final class WebViewCore {
// The BrowserFrame is an interface to the native Frame component.
private BrowserFrame mBrowserFrame;
-
- /* This is a ring of pictures for content. After B is built, it is swapped
- with A.
- */
- private Picture mContentPictureA = new Picture(); // draw()
- private Picture mContentPictureB = new Picture(); // nativeDraw()
-
/*
* range is from 200 to 10,000. 0 is a special value means device-width. -1
* means undefined.
@@ -196,7 +189,8 @@ final class WebViewCore {
sWebCoreHandler.removeMessages(WebCoreThread.INITIALIZE, this);
}
- /* Get the BrowserFrame component. This is used for subwindow creation. */
+ /* Get the BrowserFrame component. This is used for subwindow creation and
+ * is called only from BrowserFrame in the WebCore thread. */
/* package */ BrowserFrame getBrowserFrame() {
return mBrowserFrame;
}
@@ -278,30 +272,40 @@ final class WebViewCore {
static native String nativeFindAddress(String addr);
/**
- * Find and highlight an occurance of text matching find
- * @param find The text to find.
- * @param forward If true, search forward. Else, search backwards.
- * @param fromSelection Whether to start from the current selection or from
- * the beginning of the viewable page.
- * @return boolean Whether the text was found.
+ * Empty the picture set.
*/
- private native boolean nativeFind(String find,
- boolean forward,
- boolean fromSelection);
-
+ private native void nativeClearContent();
+
+ /**
+ * Create a flat picture from the set of pictures.
+ */
+ private native void nativeCopyContentToPicture(Picture picture);
+
+ /**
+ * Draw the picture set with a background color. Returns true
+ * if some individual picture took too long to draw and can be
+ * split into parts. Called from the UI thread.
+ */
+ private native boolean nativeDrawContent(Canvas canvas, int color);
+
/**
- * Find all occurances of text matching find and highlight them.
- * @param find The text to find.
- * @return int The number of occurances of find found.
+ * Redraw a portion of the picture set. The Point wh returns the
+ * width and height of the overall picture.
*/
- private native int nativeFindAll(String find);
+ private native boolean nativeRecordContent(Region invalRegion, Point wh);
/**
- * Clear highlights on text created by nativeFindAll.
+ * Splits slow parts of the picture set. Called from the webkit
+ * thread after nativeDrawContent returns true.
*/
- private native void nativeClearMatches();
+ private native void nativeSplitContent();
+
+ // these must be kept lock-step with the KeyState enum in WebViewCore.h
+ static private final int KEY_ACTION_DOWN = 0;
+ static private final int KEY_ACTION_UP = 1;
- private native void nativeDraw(Picture content);
+ private native boolean nativeSendKeyToFocusNode(int keyCode, int unichar,
+ int repeatCount, boolean isShift, boolean isAlt, int keyAction);
private native boolean nativeKeyUp(int keycode, int keyvalue);
@@ -343,21 +347,12 @@ final class WebViewCore {
private native String nativeRetrieveHref(int framePtr, int nodePtr);
- /**
- * Return the url of the image located at (x,y) in content coordinates, or
- * null if there is no image at that point.
- *
- * @param x x content ordinate
- * @param y y content ordinate
- * @return String url of the image located at (x,y), or null if there is
- * no image there.
- */
- private native String nativeRetrieveImageRef(int x, int y);
-
private native void nativeTouchUp(int touchGeneration,
int buildGeneration, int framePtr, int nodePtr, int x, int y,
int size, boolean isClick, boolean retry);
+ private native boolean nativeHandleTouchEvent(int action, int x, int y);
+
private native void nativeUnblockFocus();
private native void nativeUpdateFrameCache();
@@ -368,7 +363,11 @@ final class WebViewCore {
private native void nativeSetBackgroundColor(int color);
- private native void nativeDump();
+ private native void nativeDumpDomTree(boolean useFile);
+
+ private native void nativeDumpRenderTree(boolean useFile);
+
+ private native void nativeDumpNavTree();
private native void nativeRefreshPlugins(boolean reloadOpenPages);
@@ -393,6 +392,10 @@ final class WebViewCore {
private native String nativeGetSelection(Region sel);
+ // Register a scheme to be treated as local scheme so that it can access
+ // local asset files for resources
+ private native void nativeRegisterURLSchemeAsLocal(String scheme);
+
// EventHub for processing messages
private final EventHub mEventHub;
// WebCore thread handler
@@ -421,10 +424,7 @@ final class WebViewCore {
switch (msg.what) {
case INITIALIZE:
WebViewCore core = (WebViewCore) msg.obj;
- synchronized (core) {
- core.initialize();
- core.notify();
- }
+ core.initialize();
break;
case REDUCE_PRIORITY:
@@ -501,6 +501,12 @@ final class WebViewCore {
boolean mRetry;
}
+ static class TouchEventData {
+ int mAction; // MotionEvent.getAction()
+ int mX;
+ int mY;
+ }
+
class EventHub {
// Message Ids
static final int LOAD_URL = 100;
@@ -510,7 +516,7 @@ final class WebViewCore {
static final int KEY_UP = 104;
static final int VIEW_SIZE_CHANGED = 105;
static final int GO_BACK_FORWARD = 106;
- static final int SET_VISIBLE_RECT = 107;
+ static final int SET_SCROLL_OFFSET = 107;
static final int RESTORE_STATE = 108;
static final int PAUSE_TIMERS = 109;
static final int RESUME_TIMERS = 110;
@@ -519,16 +525,13 @@ final class WebViewCore {
static final int SET_SELECTION = 113;
static final int REPLACE_TEXT = 114;
static final int PASS_TO_JS = 115;
- static final int FIND = 116;
+ static final int SET_GLOBAL_BOUNDS = 116;
static final int UPDATE_CACHE_AND_TEXT_ENTRY = 117;
- static final int FIND_ALL = 118;
- static final int CLEAR_MATCHES = 119;
static final int DOC_HAS_IMAGES = 120;
static final int SET_SNAP_ANCHOR = 121;
static final int DELETE_SELECTION = 122;
static final int LISTBOX_CHOICES = 123;
static final int SINGLE_LISTBOX_CHOICE = 124;
- static final int DUMP_WEBKIT = 125;
static final int SET_BACKGROUND_COLOR = 126;
static final int UNBLOCK_FOCUS = 127;
static final int SAVE_DOCUMENT_STATE = 128;
@@ -536,9 +539,10 @@ final class WebViewCore {
static final int WEBKIT_DRAW = 130;
static final int SYNC_SCROLL = 131;
static final int REFRESH_PLUGINS = 132;
-
+ static final int SPLIT_PICTURE_SET = 133;
+ static final int CLEAR_CONTENT = 134;
+
// UI nav messages
- static final int REQUEST_IMAGE_HREF = 134;
static final int SET_FINAL_FOCUS = 135;
static final int SET_KIT_FOCUS = 136;
static final int REQUEST_FOCUS_HREF = 137;
@@ -547,6 +551,8 @@ final class WebViewCore {
// motion
static final int TOUCH_UP = 140;
+ // message used to pass UI touch events to WebCore
+ static final int TOUCH_EVENT = 141;
// Network-based messaging
static final int CLEAR_SSL_PREF_TABLE = 150;
@@ -554,6 +560,12 @@ final class WebViewCore {
// Test harness messages
static final int REQUEST_EXT_REPRESENTATION = 160;
static final int REQUEST_DOC_AS_TEXT = 161;
+
+ // debugging
+ static final int DUMP_DOMTREE = 170;
+ static final int DUMP_RENDERTREE = 171;
+ static final int DUMP_NAVTREE = 172;
+
// private message ids
private static final int DESTROY = 200;
@@ -561,6 +573,19 @@ final class WebViewCore {
static final int NO_FOCUS_CHANGE_BLOCK = 0;
static final int BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP = 1;
+ /* The KEY_DOWN and KEY_UP messages pass the keyCode in arg1, and a
+ "type" in arg2. These are the types, and they describe what the
+ circumstances were that prompted the UI thread to send the keyevent
+ to webkit.
+
+ FOCUS_NODE - the currently focused node says it wants key events
+ (e.g. plugins)
+ UNHANDLED - the UI side did not handle the key, so we give webkit
+ a shot at it.
+ */
+ static final int KEYEVENT_FOCUS_NODE_TYPE = 0;
+ static final int KEYEVENT_UNHANDLED_TYPE = 1;
+
// Private handler for WebCore messages.
private Handler mHandler;
// Message queue for containing messages before the WebCore thread is
@@ -607,8 +632,30 @@ final class WebViewCore {
case LOAD_DATA:
HashMap loadParams = (HashMap) msg.obj;
- mBrowserFrame.loadData(
- (String) loadParams.get("baseUrl"),
+ String baseUrl = (String) loadParams.get("baseUrl");
+ if (baseUrl != null) {
+ int i = baseUrl.indexOf(':');
+ if (i > 0) {
+ /*
+ * In 1.0, {@link
+ * WebView#loadDataWithBaseURL} can access
+ * local asset files as long as the data is
+ * valid. In the new WebKit, the restriction
+ * is tightened. To be compatible with 1.0,
+ * we automatically add the scheme of the
+ * baseUrl for local access as long as it is
+ * not http(s)/ftp(s)/about/javascript
+ */
+ String scheme = baseUrl.substring(0, i);
+ if (!scheme.startsWith("http") &&
+ !scheme.startsWith("ftp") &&
+ !scheme.startsWith("about") &&
+ !scheme.startsWith("javascript")) {
+ nativeRegisterURLSchemeAsLocal(scheme);
+ }
+ }
+ }
+ mBrowserFrame.loadData(baseUrl,
(String) loadParams.get("data"),
(String) loadParams.get("mimeType"),
(String) loadParams.get("encoding"),
@@ -622,8 +669,7 @@ final class WebViewCore {
// up with native side
if (mBrowserFrame.committed()
&& !mBrowserFrame.firstLayoutDone()) {
- mBrowserFrame.didFirstLayout(mBrowserFrame
- .currentUrl());
+ mBrowserFrame.didFirstLayout();
}
// Do this after syncing up the layout state.
stopLoading();
@@ -634,11 +680,11 @@ final class WebViewCore {
break;
case KEY_DOWN:
- keyDown(msg.arg1, (KeyEvent) msg.obj);
+ keyDown(msg.arg1, msg.arg2, (KeyEvent) msg.obj);
break;
case KEY_UP:
- keyUp(msg.arg1, (KeyEvent) msg.obj);
+ keyUp(msg.arg1, msg.arg2, (KeyEvent) msg.obj);
break;
case VIEW_SIZE_CHANGED:
@@ -646,12 +692,16 @@ final class WebViewCore {
((Float) msg.obj).floatValue());
break;
- case SET_VISIBLE_RECT:
- Rect r = (Rect) msg.obj;
+ case SET_SCROLL_OFFSET:
// note: these are in document coordinates
// (inv-zoom)
- nativeSetVisibleRect(r.left, r.top, r.width(),
- r.height());
+ nativeSetScrollOffset(msg.arg1, msg.arg2);
+ break;
+
+ case SET_GLOBAL_BOUNDS:
+ Rect r = (Rect) msg.obj;
+ nativeSetGlobalBounds(r.left, r.top, r.width(),
+ r.height());
break;
case GO_BACK_FORWARD:
@@ -745,30 +795,6 @@ final class WebViewCore {
break;
}
- case FIND:
- /* arg1:
- * 1 - Find next
- * -1 - Find previous
- * 0 - Find first
- */
- Message response = (Message) msg.obj;
- boolean find = nativeFind(msg.getData().getString("find"),
- msg.arg1 != -1, msg.arg1 != 0);
- response.arg1 = find ? 1 : 0;
- response.sendToTarget();
- break;
-
- case FIND_ALL:
- int found = nativeFindAll(msg.getData().getString("find"));
- Message resAll = (Message) msg.obj;
- resAll.arg1 = found;
- resAll.sendToTarget();
- break;
-
- case CLEAR_MATCHES:
- nativeClearMatches();
- break;
-
case CLEAR_SSL_PREF_TABLE:
Network.getInstance(mContext)
.clearUserSslPrefTable();
@@ -784,6 +810,17 @@ final class WebViewCore {
touchUpData.mRetry);
break;
+ 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();
+ }
+ break;
+ }
+
case ADD_JS_INTERFACE:
HashMap map = (HashMap) msg.obj;
Object obj = map.get("object");
@@ -826,27 +863,17 @@ final class WebViewCore {
case REQUEST_FOCUS_HREF: {
Message hrefMsg = (Message) msg.obj;
String res = nativeRetrieveHref(msg.arg1, msg.arg2);
- Bundle data = hrefMsg.getData();
- data.putString("url", res);
- hrefMsg.setData(data);
+ hrefMsg.getData().putString("url", res);
hrefMsg.sendToTarget();
break;
}
- case REQUEST_IMAGE_HREF: {
- Message refMsg = (Message) msg.obj;
- String ref =
- nativeRetrieveImageRef(msg.arg1, msg.arg2);
- Bundle data = refMsg.getData();
- data.putString("url", ref);
- refMsg.setData(data);
- refMsg.sendToTarget();
- break;
- }
-
case UPDATE_CACHE_AND_TEXT_ENTRY:
nativeUpdateFrameCache();
- sendViewInvalidate();
+ // FIXME: this should provide a minimal rectangle
+ if (mWebView != null) {
+ mWebView.postInvalidate();
+ }
sendUpdateTextEntry();
break;
@@ -901,9 +928,17 @@ final class WebViewCore {
, WebView.UPDATE_CLIPBOARD, str)
.sendToTarget();
break;
-
- case DUMP_WEBKIT:
- nativeDump();
+
+ case DUMP_DOMTREE:
+ nativeDumpDomTree(msg.arg1 == 1);
+ break;
+
+ case DUMP_RENDERTREE:
+ nativeDumpRenderTree(msg.arg1 == 1);
+ break;
+
+ case DUMP_NAVTREE:
+ nativeDumpNavTree();
break;
case SYNC_SCROLL:
@@ -914,6 +949,18 @@ final class WebViewCore {
case REFRESH_PLUGINS:
nativeRefreshPlugins(msg.arg1 != 0);
break;
+
+ case SPLIT_PICTURE_SET:
+ nativeSplitContent();
+ mSplitPictureIsScheduled = false;
+ break;
+
+ case CLEAR_CONTENT:
+ // Clear the view so that onDraw() will draw nothing
+ // but white background
+ // (See public method WebView.clearView)
+ nativeClearContent();
+ break;
}
}
};
@@ -982,6 +1029,7 @@ final class WebViewCore {
private synchronized void removeMessages() {
// reset mDrawIsScheduled flag as WEBKIT_DRAW may be removed
mDrawIsScheduled = false;
+ mSplitPictureIsScheduled = false;
if (mMessages != null) {
mMessages.clear();
} else {
@@ -1001,7 +1049,7 @@ final class WebViewCore {
// Methods called by host activity (in the same thread)
//-------------------------------------------------------------------------
- public void stopLoading() {
+ void stopLoading() {
if (LOGV_ENABLED) Log.v(LOGTAG, "CORE stopLoading");
if (mBrowserFrame != null) {
mBrowserFrame.stopLoading();
@@ -1082,23 +1130,48 @@ final class WebViewCore {
mBrowserFrame.loadUrl(url);
}
- private void keyDown(int code, KeyEvent event) {
+ private void keyDown(int code, int target, KeyEvent event) {
if (LOGV_ENABLED) {
Log.v(LOGTAG, "CORE keyDown at " + System.currentTimeMillis()
+ ", " + event);
}
+ switch (target) {
+ case EventHub.KEYEVENT_UNHANDLED_TYPE:
+ break;
+ case EventHub.KEYEVENT_FOCUS_NODE_TYPE:
+ if (nativeSendKeyToFocusNode(code, event.getUnicodeChar(),
+ event.getRepeatCount(),
+ event.isShiftPressed(),
+ event.isAltPressed(),
+ KEY_ACTION_DOWN)) {
+ return;
+ }
+ break;
+ }
+ // If we get here, no one handled it, so call our proxy
mCallbackProxy.onUnhandledKeyEvent(event);
}
- private void keyUp(int code, KeyEvent event) {
+ private void keyUp(int code, int target, KeyEvent event) {
if (LOGV_ENABLED) {
Log.v(LOGTAG, "CORE keyUp at " + System.currentTimeMillis()
+ ", " + event);
}
- if (!nativeKeyUp(code, event.getUnicodeChar())) {
- mCallbackProxy.onUnhandledKeyEvent(event);
+ switch (target) {
+ case EventHub.KEYEVENT_UNHANDLED_TYPE:
+ if (!nativeKeyUp(code, event.getUnicodeChar())) {
+ mCallbackProxy.onUnhandledKeyEvent(event);
+ }
+ break;
+ case EventHub.KEYEVENT_FOCUS_NODE_TYPE:
+ nativeSendKeyToFocusNode(code, event.getUnicodeChar(),
+ event.getRepeatCount(),
+ event.isShiftPressed(),
+ event.isAltPressed(),
+ KEY_ACTION_UP);
+ break;
+ }
}
- }
// These values are used to avoid requesting a layout based on old values
private int mCurrentViewWidth = 0;
@@ -1140,8 +1213,9 @@ final class WebViewCore {
mCurrentViewHeight = h;
if (needInvalidate) {
// ensure {@link #webkitDraw} is called as we were blocking in
- // {@link #contentInvalidate} when mCurrentViewWidth is 0
- contentInvalidate();
+ // {@link #contentDraw} when mCurrentViewWidth is 0
+ if (LOGV_ENABLED) Log.v(LOGTAG, "viewSizeChanged");
+ contentDraw();
}
mEventHub.sendMessage(Message.obtain(null,
EventHub.UPDATE_CACHE_AND_TEXT_ENTRY));
@@ -1156,30 +1230,42 @@ final class WebViewCore {
// Used to avoid posting more than one draw message.
private boolean mDrawIsScheduled;
+
+ // Used to avoid posting more than one split picture message.
+ private boolean mSplitPictureIsScheduled;
+
+ // Used to suspend drawing.
+ private boolean mDrawIsPaused;
// Used to end scale+scroll mode, accessed by both threads
boolean mEndScaleZoom = false;
+ public class DrawData {
+ public DrawData() {
+ mInvalRegion = new Region();
+ mWidthHeight = new Point();
+ }
+ public Region mInvalRegion;
+ public Point mViewPoint;
+ public Point mWidthHeight;
+ }
+
private void webkitDraw() {
mDrawIsScheduled = false;
- nativeDraw(mContentPictureB);
- int w;
- int h;
- synchronized (this) {
- Picture temp = mContentPictureB;
- mContentPictureB = mContentPictureA;
- mContentPictureA = temp;
- w = mContentPictureA.getWidth();
- h = mContentPictureA.getHeight();
+ DrawData draw = new DrawData();
+ if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw start");
+ if (nativeRecordContent(draw.mInvalRegion, draw.mWidthHeight)
+ == false) {
+ if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw abort");
+ return;
}
-
if (mWebView != null) {
// Send the native view size that was used during the most recent
// layout.
+ draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight);
+ if (LOGV_ENABLED) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID");
Message.obtain(mWebView.mPrivateHandler,
- WebView.NEW_PICTURE_MSG_ID, w, h,
- new Point(mCurrentViewWidth, mCurrentViewHeight))
- .sendToTarget();
+ WebView.NEW_PICTURE_MSG_ID, draw).sendToTarget();
if (mWebkitScrollX != 0 || mWebkitScrollY != 0) {
// as we have the new picture, try to sync the scroll position
Message.obtain(mWebView.mPrivateHandler,
@@ -1217,37 +1303,18 @@ final class WebViewCore {
df = mScrollFilter;
}
canvas.setDrawFilter(df);
- synchronized (this) {
- Picture picture = mContentPictureA;
- int sc = canvas.save(Canvas.CLIP_SAVE_FLAG);
- Rect clip = new Rect(0, 0, picture.getWidth(), picture.getHeight());
- canvas.clipRect(clip, Region.Op.DIFFERENCE);
- canvas.drawColor(color);
- canvas.restoreToCount(sc);
- // experiment commented out
- // if (TEST_BUCKET) {
- // nativeDrawContentPicture(canvas);
- // } else {
- canvas.drawPicture(picture);
- // }
- }
+ boolean tookTooLong = nativeDrawContent(canvas, color);
canvas.setDrawFilter(null);
- }
-
- /* package */ void clearContentPicture() {
- // experiment commented out
- // if (TEST_BUCKET) {
- // nativeClearContentPicture();
- // }
- synchronized (this) {
- mContentPictureA = new Picture();
+ if (tookTooLong && mSplitPictureIsScheduled == false) {
+ mSplitPictureIsScheduled = true;
+ sendMessage(EventHub.SPLIT_PICTURE_SET);
}
}
/*package*/ Picture copyContentPicture() {
- synchronized (this) {
- return new Picture(mContentPictureA);
- }
+ Picture result = new Picture();
+ nativeCopyContentToPicture(result);
+ return result;
}
static void pauseUpdate(WebViewCore core) {
@@ -1263,7 +1330,7 @@ final class WebViewCore {
// webcore thread priority is still lowered.
if (core != null) {
synchronized (core) {
- core.mDrawIsScheduled = true;
+ core.mDrawIsPaused = true;
core.mEventHub.removeMessages(EventHub.WEBKIT_DRAW);
}
}
@@ -1278,7 +1345,9 @@ final class WebViewCore {
if (core != null) {
synchronized (core) {
core.mDrawIsScheduled = false;
- core.contentInvalidate();
+ core.mDrawIsPaused = false;
+ if (LOGV_ENABLED) Log.v(LOGTAG, "resumeUpdate");
+ core.contentDraw();
}
}
}
@@ -1309,7 +1378,7 @@ final class WebViewCore {
//-------------------------------------------------------------------------
// called from JNI or WebView thread
- /* package */ void contentInvalidate() {
+ /* package */ void contentDraw() {
// don't update the Picture until we have an initial width and finish
// the first layout
if (mCurrentViewWidth == 0 || !mBrowserFrame.firstLayoutDone()) {
@@ -1317,14 +1386,14 @@ final class WebViewCore {
}
// only fire an event if this is our first request
synchronized (this) {
- if (mDrawIsScheduled) {
+ if (mDrawIsPaused || mDrawIsScheduled) {
return;
}
mDrawIsScheduled = true;
mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
}
}
-
+
// called by JNI
private void contentScrollBy(int dx, int dy) {
if (!mBrowserFrame.firstLayoutDone()) {
@@ -1397,6 +1466,7 @@ final class WebViewCore {
sWebCoreHandler.removeMessages(WebCoreThread.CACHE_TICKER);
sWebCoreHandler.sendMessage(sWebCoreHandler
.obtainMessage(WebCoreThread.CACHE_TICKER));
+ contentDraw();
}
// called by JNI
@@ -1408,9 +1478,9 @@ final class WebViewCore {
}
// called by JNI
- private void sendViewInvalidate() {
+ private void sendViewInvalidate(int left, int top, int right, int bottom) {
if (mWebView != null) {
- mWebView.postInvalidate();
+ mWebView.postInvalidate(left, top, right, bottom);
}
}
@@ -1421,7 +1491,7 @@ final class WebViewCore {
private native void setViewportSettingsFromNative();
// called by JNI
- private void didFirstLayout(String url) {
+ private void didFirstLayout() {
// Trick to ensure that the Picture has the exact height for the content
// by forcing to layout with 0 height after the page is ready, which is
// indicated by didFirstLayout. This is essential to get rid of the
@@ -1435,7 +1505,7 @@ final class WebViewCore {
mWebView.mLastHeightSent, -1.0f));
}
- mBrowserFrame.didFirstLayout(url);
+ mBrowserFrame.didFirstLayout();
// reset the scroll position as it is a new page now
mWebkitScrollX = mWebkitScrollY = 0;
@@ -1517,7 +1587,16 @@ final class WebViewCore {
mRestoredScale = scale;
}
}
-
+
+ // called by JNI
+ private void needTouchEvents(boolean need) {
+ if (mWebView != null) {
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.WEBCORE_NEED_TOUCH_EVENTS, need ? 1 : 0, 0)
+ .sendToTarget();
+ }
+ }
+
// called by JNI
private void updateTextfield(int ptr, boolean changeToPassword,
String text, int textGeneration) {
@@ -1530,9 +1609,10 @@ final class WebViewCore {
}
}
- // these must be in document space (i.e. not scaled/zoomed.
- private native void nativeSetVisibleRect(int x, int y, int width,
- int height);
+ // these must be in document space (i.e. not scaled/zoomed).
+ private native void nativeSetScrollOffset(int dx, int dy);
+
+ private native void nativeSetGlobalBounds(int x, int y, int w, int h);
// called by JNI
private void requestListBox(String[] array, boolean[] enabledArray,
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index b367e27..96f3698 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -39,7 +39,7 @@ public class WebViewDatabase {
// log tag
protected static final String LOGTAG = "webviewdatabase";
- private static final int DATABASE_VERSION = 8;
+ private static final int DATABASE_VERSION = 9;
// 2 -> 3 Modified Cache table to allow cache of redirects
// 3 -> 4 Added Oma-Downloads table
// 4 -> 5 Modified Cache table to support persistent contentLength
@@ -47,6 +47,7 @@ public class WebViewDatabase {
// 5 -> 6 Add INDEX for cache table
// 6 -> 7 Change cache localPath from int to String
// 7 -> 8 Move cache to its own db
+ // 8 -> 9 Store both scheme and host when storing passwords
private static final int CACHE_DATABASE_VERSION = 1;
private static WebViewDatabase mInstance = null;
@@ -172,7 +173,6 @@ public class WebViewDatabase {
mDatabase.beginTransaction();
try {
upgradeDatabase();
- bootstrapDatabase();
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
@@ -200,6 +200,10 @@ public class WebViewDatabase {
} finally {
mCacheDatabase.endTransaction();
}
+ // Erase the files from the file system in the
+ // case that the database was updated and the
+ // there were existing cache content
+ CacheManager.removeAllCacheFiles();
}
if (mCacheDatabase != null) {
@@ -237,24 +241,26 @@ public class WebViewDatabase {
if (oldVersion != 0) {
Log.i(LOGTAG, "Upgrading database from version "
+ oldVersion + " to "
- + DATABASE_VERSION + ", which will destroy all old data");
+ + DATABASE_VERSION + ", which will destroy old data");
+ }
+ boolean justPasswords = 8 == oldVersion && 9 == DATABASE_VERSION;
+ if (!justPasswords) {
+ mDatabase.execSQL("DROP TABLE IF EXISTS "
+ + mTableNames[TABLE_COOKIES_ID]);
+ mDatabase.execSQL("DROP TABLE IF EXISTS cache");
+ mDatabase.execSQL("DROP TABLE IF EXISTS "
+ + mTableNames[TABLE_FORMURL_ID]);
+ mDatabase.execSQL("DROP TABLE IF EXISTS "
+ + mTableNames[TABLE_FORMDATA_ID]);
+ mDatabase.execSQL("DROP TABLE IF EXISTS "
+ + mTableNames[TABLE_HTTPAUTH_ID]);
}
mDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_COOKIES_ID]);
- mDatabase.execSQL("DROP TABLE IF EXISTS cache");
- mDatabase.execSQL("DROP TABLE IF EXISTS "
+ mTableNames[TABLE_PASSWORD_ID]);
- mDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_FORMURL_ID]);
- mDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_FORMDATA_ID]);
- mDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_HTTPAUTH_ID]);
+
mDatabase.setVersion(DATABASE_VERSION);
- }
- private static void bootstrapDatabase() {
- if (mDatabase != null) {
+ if (!justPasswords) {
// cookies
mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID]
+ " (" + ID_COL + " INTEGER PRIMARY KEY, "
@@ -265,14 +271,6 @@ public class WebViewDatabase {
mDatabase.execSQL("CREATE INDEX cookiesIndex ON "
+ mTableNames[TABLE_COOKIES_ID] + " (path)");
- // password
- mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID]
- + " (" + ID_COL + " INTEGER PRIMARY KEY, "
- + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL
- + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE ("
- + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL
- + ") ON CONFLICT REPLACE);");
-
// formurl
mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID]
+ " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL
@@ -295,6 +293,13 @@ public class WebViewDatabase {
+ HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL + ", "
+ HTTPAUTH_USERNAME_COL + ") ON CONFLICT REPLACE);");
}
+ // passwords
+ mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID]
+ + " (" + ID_COL + " INTEGER PRIMARY KEY, "
+ + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL
+ + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE ("
+ + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL
+ + ") ON CONFLICT REPLACE);");
}
private static void upgradeCacheDatabase() {
@@ -639,10 +644,11 @@ public class WebViewDatabase {
if (cursor.moveToFirst()) {
int batchSize = 100;
StringBuilder pathStr = new StringBuilder(20 + 16 * batchSize);
- pathStr.append("DELETE FROM cache WHERE filepath = ?");
+ pathStr.append("DELETE FROM cache WHERE filepath IN (?");
for (int i = 1; i < batchSize; i++) {
- pathStr.append(" OR filepath = ?");
+ pathStr.append(", ?");
}
+ pathStr.append(")");
SQLiteStatement statement = mCacheDatabase.compileStatement(pathStr
.toString());
// as bindString() uses 1-based index, initialize index to 1
@@ -658,6 +664,7 @@ public class WebViewDatabase {
pathList.add(filePath);
if (index++ == batchSize) {
statement.execute();
+ statement.clearBindings();
index = 1;
}
} while (cursor.moveToNext() && amount > 0);
@@ -679,19 +686,20 @@ public class WebViewDatabase {
/**
* Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique.
*
- * @param host The host for the password
+ * @param schemePlusHost The scheme and host for the password
* @param username The username for the password. If it is null, it means
* password can't be saved.
* @param password The password
*/
- void setUsernamePassword(String host, String username, String password) {
- if (host == null || mDatabase == null) {
+ void setUsernamePassword(String schemePlusHost, String username,
+ String password) {
+ if (schemePlusHost == null || mDatabase == null) {
return;
}
synchronized (mPasswordLock) {
final ContentValues c = new ContentValues();
- c.put(PASSWORD_HOST_COL, host);
+ c.put(PASSWORD_HOST_COL, schemePlusHost);
c.put(PASSWORD_USERNAME_COL, username);
c.put(PASSWORD_PASSWORD_COL, password);
mDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL,
@@ -702,12 +710,12 @@ public class WebViewDatabase {
/**
* Retrieve the username and password for a given host
*
- * @param host The host which passwords applies to
+ * @param schemePlusHost The scheme and host which passwords applies to
* @return String[] if found, String[0] is username, which can be null and
* String[1] is password. Return null if it can't find anything.
*/
- String[] getUsernamePassword(String host) {
- if (host == null || mDatabase == null) {
+ String[] getUsernamePassword(String schemePlusHost) {
+ if (schemePlusHost == null || mDatabase == null) {
return null;
}
@@ -718,8 +726,8 @@ public class WebViewDatabase {
synchronized (mPasswordLock) {
String[] ret = null;
Cursor cursor = mDatabase.query(mTableNames[TABLE_PASSWORD_ID],
- columns, selection, new String[] { host }, null, null,
- null);
+ columns, selection, new String[] { schemePlusHost }, null,
+ null, null);
if (cursor.moveToFirst()) {
ret = new String[2];
ret[0] = cursor.getString(
diff --git a/core/java/android/webkit/gears/AndroidWifiDataProvider.java b/core/java/android/webkit/gears/AndroidWifiDataProvider.java
new file mode 100644
index 0000000..7379f59
--- /dev/null
+++ b/core/java/android/webkit/gears/AndroidWifiDataProvider.java
@@ -0,0 +1,136 @@
+// Copyright 2008, Google Inc.
+//
+// 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Config;
+import android.util.Log;
+import android.webkit.WebView;
+import java.util.List;
+
+/**
+ * WiFi data provider implementation for Android.
+ * {@hide}
+ */
+public final class AndroidWifiDataProvider extends BroadcastReceiver {
+ /**
+ * Logging tag
+ */
+ private static final String TAG = "Gears-J-WifiProvider";
+ /**
+ * Our Wifi manager instance.
+ */
+ private WifiManager mWifiManager;
+ /**
+ * The native object ID.
+ */
+ private long mNativeObject;
+ /**
+ * The Context instance.
+ */
+ private Context mContext;
+
+ /**
+ * Constructs a instance of this class and registers for wifi scan
+ * updates. Note that this constructor must be called on a Looper
+ * thread. Suitable threads can be created on the native side using
+ * the AndroidLooperThread C++ class.
+ */
+ public AndroidWifiDataProvider(WebView webview, long object) {
+ mNativeObject = object;
+ mContext = webview.getContext();
+ mWifiManager =
+ (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ if (mWifiManager == null) {
+ Log.e(TAG,
+ "AndroidWifiDataProvider: could not get location manager.");
+ throw new NullPointerException(
+ "AndroidWifiDataProvider: locationManager is null.");
+ }
+
+ // Create a Handler that identifies the message loop associated
+ // with the current thread. Note that it is not necessary to
+ // override handleMessage() at all since the Intent
+ // ReceiverDispatcher (see the ActivityThread class) only uses
+ // this handler to post a Runnable to this thread's loop.
+ Handler handler = new Handler(Looper.myLooper());
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(mWifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ mContext.registerReceiver(this, filter, null, handler);
+
+ // Get the last scan results and pass them to the native side.
+ // We can't just invoke the callback here, so we queue a message
+ // to this thread's loop.
+ handler.post(new Runnable() {
+ public void run() {
+ onUpdateAvailable(mWifiManager.getScanResults(), mNativeObject);
+ }
+ });
+ }
+
+ /**
+ * Called when the provider is no longer needed.
+ */
+ public void shutdown() {
+ mContext.unregisterReceiver(this);
+ if (Config.LOGV) {
+ Log.v(TAG, "Wifi provider closed.");
+ }
+ }
+
+ /**
+ * This method is called when the AndroidWifiDataProvider is receiving an
+ * Intent broadcast.
+ * @param context The Context in which the receiver is running.
+ * @param intent The Intent being received.
+ */
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(
+ mWifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+ if (Config.LOGV) {
+ Log.v(TAG, "Wifi scan resulst available");
+ }
+ onUpdateAvailable(mWifiManager.getScanResults(), mNativeObject);
+ }
+ }
+
+ /**
+ * The native method called when new wifi data is available.
+ * @param scanResults is a list of ScanResults to pass to the native side.
+ * @param nativeObject is a pointer to the corresponding
+ * AndroidWifiDataProvider C++ instance.
+ */
+ private static native void onUpdateAvailable(
+ List<ScanResult> scanResults, long nativeObject);
+}
diff --git a/core/java/android/webkit/gears/DesktopAndroid.java b/core/java/android/webkit/gears/DesktopAndroid.java
index 00a9a47..ee8ca49 100644
--- a/core/java/android/webkit/gears/DesktopAndroid.java
+++ b/core/java/android/webkit/gears/DesktopAndroid.java
@@ -40,8 +40,6 @@ import android.webkit.WebView;
public class DesktopAndroid {
private static final String TAG = "Gears-J-Desktop";
- private static final String BROWSER = "com.android.browser";
- private static final String BROWSER_ACTIVITY = BROWSER + ".BrowserActivity";
private static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate";
private static final String ACTION_INSTALL_SHORTCUT =
"com.android.launcher.action.INSTALL_SHORTCUT";
@@ -78,11 +76,9 @@ public class DesktopAndroid {
String url, String imagePath) {
Context context = webview.getContext();
- ComponentName browser = new ComponentName(BROWSER, BROWSER_ACTIVITY);
-
Intent viewWebPage = new Intent(Intent.ACTION_VIEW);
- viewWebPage.setComponent(browser);
viewWebPage.setData(Uri.parse(url));
+ viewWebPage.addCategory(Intent.CATEGORY_BROWSABLE);
Intent intent = new Intent(ACTION_INSTALL_SHORTCUT);
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, viewWebPage);
diff --git a/core/java/android/webkit/gears/HttpRequestAndroid.java b/core/java/android/webkit/gears/HttpRequestAndroid.java
index 8668c54..30f855f 100644
--- a/core/java/android/webkit/gears/HttpRequestAndroid.java
+++ b/core/java/android/webkit/gears/HttpRequestAndroid.java
@@ -163,7 +163,20 @@ public final class HttpRequestAndroid {
// Setup the connection. This doesn't go to the wire yet - it
// doesn't block.
try {
- connection = (HttpURLConnection) new URL(url).openConnection();
+ 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);
@@ -197,11 +210,13 @@ public final class HttpRequestAndroid {
log("interrupt() called but no child thread");
return;
}
- if (inBlockingOperation) {
- log("Interrupting blocking operation");
- childThread.interrupt();
- } else {
- log("Nothing to interrupt");
+ synchronized (this) {
+ if (inBlockingOperation) {
+ log("Interrupting blocking operation");
+ childThread.interrupt();
+ } else {
+ log("Nothing to interrupt");
+ }
}
}
@@ -472,7 +487,7 @@ public final class HttpRequestAndroid {
String encoding = cacheResult.getEncoding();
// Encoding may not be specified. No default.
String contentType = mimeType;
- if (encoding != null) {
+ if (encoding != null && encoding.length() > 0) {
contentType += "; charset=" + encoding;
}
setResponseHeader(KEY_CONTENT_TYPE, contentType);
diff --git a/core/java/android/webkit/gears/NativeDialog.java b/core/java/android/webkit/gears/NativeDialog.java
new file mode 100644
index 0000000..9e2b375
--- /dev/null
+++ b/core/java/android/webkit/gears/NativeDialog.java
@@ -0,0 +1,142 @@
+// 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.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import java.io.File;
+import java.lang.InterruptedException;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Utility class to call a modal native dialog on Android
+ * The dialog itself is an Activity defined in the Browser.
+ * @hide
+ */
+public class NativeDialog {
+
+ private static final String TAG = "Gears-J-NativeDialog";
+
+ private final String DIALOG_PACKAGE = "com.android.browser";
+ private final String DIALOG_CLASS = DIALOG_PACKAGE + ".GearsNativeDialog";
+
+ private static Lock mLock = new ReentrantLock();
+ private static Condition mDialogFinished = mLock.newCondition();
+ private static String mResults = null;
+
+ private static boolean mAsynchronousDialog;
+
+ /**
+ * Utility function to build the intent calling the
+ * dialog activity
+ */
+ private Intent createIntent(String type, String arguments) {
+ Intent intent = new Intent();
+ intent.setClassName(DIALOG_PACKAGE, DIALOG_CLASS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra("dialogArguments", arguments);
+ intent.putExtra("dialogType", type);
+ return intent;
+ }
+
+ /**
+ * Opens a native dialog synchronously and waits for its completion.
+ *
+ * The dialog is an activity (GearsNativeDialog) provided by the Browser
+ * that we call via startActivity(). Contrary to a normal activity though,
+ * we need to block until it returns. To do so, we define a static lock
+ * object in this class, which GearsNativeDialog can unlock once done
+ */
+ public String showDialog(Context context, String file,
+ String arguments) {
+
+ try {
+ mAsynchronousDialog = false;
+ mLock.lock();
+ File path = new File(file);
+ String fileName = path.getName();
+ String type = fileName.substring(0, fileName.indexOf(".html"));
+ Intent intent = createIntent(type, arguments);
+
+ mResults = null;
+ context.startActivity(intent);
+ mDialogFinished.await();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "exception e: " + e);
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "exception e: " + e);
+ } finally {
+ mLock.unlock();
+ }
+
+ return mResults;
+ }
+
+ /**
+ * Opens a native dialog asynchronously
+ *
+ * The dialog is an activity (GearsNativeDialog) provided by the
+ * Browser.
+ */
+ public void showAsyncDialog(Context context, String type,
+ String arguments) {
+ mAsynchronousDialog = true;
+ Intent intent = createIntent(type, arguments);
+ context.startActivity(intent);
+ }
+
+ /**
+ * Static method that GearsNativeDialog calls to unlock us
+ */
+ public static void signalFinishedDialog() {
+ if (!mAsynchronousDialog) {
+ mLock.lock();
+ mDialogFinished.signal();
+ mLock.unlock();
+ } else {
+ // we call the native callback
+ closeAsynchronousDialog(mResults);
+ }
+ }
+
+ /**
+ * Static method that GearsNativeDialog calls to set the
+ * dialog's result
+ */
+ public static void closeDialog(String res) {
+ mResults = res;
+ }
+
+ /**
+ * Native callback method
+ */
+ private native static void closeAsynchronousDialog(String res);
+}
diff --git a/core/java/android/webkit/gears/PluginSettings.java b/core/java/android/webkit/gears/PluginSettings.java
new file mode 100644
index 0000000..2d0cc13
--- /dev/null
+++ b/core/java/android/webkit/gears/PluginSettings.java
@@ -0,0 +1,79 @@
+// 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.Context;
+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 PluginSettings {
+
+ private static final String TAG = "Gears-J-PluginSettings";
+ private Context mContext;
+
+ public PluginSettings(Plugin plugin) {
+ plugin.setClickHandler(new ClickHandler());
+ }
+
+ /**
+ * We do not call the dialog synchronously here as the main
+ * message loop would be blocked, so we call it via a secondary thread.
+ */
+ private class ClickHandler implements PreferencesClickHandler {
+ public void handleClickEvent(Context context) {
+ mContext = context.getApplicationContext();
+ Thread startDialog = new Thread(new StartDialog(context));
+ startDialog.start();
+ }
+ }
+
+ /**
+ * Simple wrapper class to call the gears native method in
+ * a separate thread (the native code will then instanciate a NativeDialog
+ * object which will start the GearsNativeDialog activity defined in
+ * the Browser).
+ */
+ private class StartDialog implements Runnable {
+ Context mContext;
+
+ public StartDialog(Context context) {
+ mContext = context;
+ }
+
+ public void run() {
+ runSettingsDialog(mContext);
+ }
+ }
+
+ private static native void runSettingsDialog(Context c);
+
+}
diff --git a/core/java/android/webkit/gears/UrlInterceptHandlerGears.java b/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
index 95fc30f..288240e 100644
--- a/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
+++ b/core/java/android/webkit/gears/UrlInterceptHandlerGears.java
@@ -407,6 +407,8 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
true); // forceCache
if (cacheResult == null) {
+ // With the no-cache policy we could end up
+ // with a null result
return null;
}
@@ -444,8 +446,7 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
// be used for input.
cacheResult = CacheManager.getCacheFile(gearsUrl, null);
if (cacheResult != null) {
- if (logEnabled)
- log("Returning surrogate result");
+ log("Returning surrogate result");
return cacheResult;
} else {
// Not an expected condition, but handle gracefully. Perhaps out
@@ -476,7 +477,10 @@ public class UrlInterceptHandlerGears implements UrlInterceptHandler {
}
/**
- * Convenience debug function. Calls Android logging mechanism.
+ * Convenience debug function. Calls the Android logging
+ * mechanism. logEnabled is not a constant, so if the string
+ * evaluation is potentially expensive, the caller also needs to
+ * check it.
* @param str String to log to the Android console.
*/
private void log(String str) {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 19b1ce0..1440522 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -58,6 +58,8 @@ import java.util.List;
* @attr ref android.R.styleable#AbsListView_textFilterEnabled
* @attr ref android.R.styleable#AbsListView_transcriptMode
* @attr ref android.R.styleable#AbsListView_cacheColorHint
+ * @attr ref android.R.styleable#AbsListView_fastScrollEnabled
+ * @attr ref android.R.styleable#AbsListView_smoothScrollbar
*/
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
@@ -116,6 +118,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* Indicates the view is in the process of being flung
*/
static final int TOUCH_MODE_FLING = 4;
+
+ /**
+ * Indicates that the user is currently dragging the fast scroll thumb
+ */
+ static final int TOUCH_MODE_FAST_SCROLL = 5;
/**
* Regular layout - usually an unsolicited layout from the view system
@@ -304,6 +311,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* bitmap cache after scrolling.
*/
boolean mScrollingCacheEnabled;
+
+ /**
+ * Whether or not to enable the fast scroll feature on this list
+ */
+ boolean mFastScrollEnabled;
/**
* Optional callback to notify client when scroll position has changed
@@ -321,6 +333,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
EditText mTextFilter;
/**
+ * Indicates whether to use pixels-based or position-based scrollbar
+ * properties.
+ */
+ private boolean mSmoothScrollbarEnabled = true;
+
+ /**
* Indicates that this view supports filtering
*/
private boolean mTextFilterEnabled;
@@ -401,6 +419,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
/**
+ * Helper object that renders and controls the fast scroll thumb.
+ */
+ private FastScroller mFastScroller;
+
+ /**
* Interface definition for a callback to be invoked when the list or grid
* has been scrolled.
*/
@@ -493,11 +516,84 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
int color = a.getColor(R.styleable.AbsListView_cacheColorHint, 0);
setCacheColorHint(color);
+
+ boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false);
+ setFastScrollEnabled(enableFastScroll);
+ boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
+ setSmoothScrollbarEnabled(smoothScrollbar);
+
a.recycle();
}
/**
+ * Enables fast scrolling by letting the user quickly scroll through lists by
+ * dragging the fast scroll thumb. The adapter attached to the list may want
+ * to implement {@link SectionIndexer} if it wishes to display alphabet preview and
+ * jump between sections of the list.
+ * @see SectionIndexer
+ * @see #isFastScrollEnabled()
+ * @param enabled whether or not to enable fast scrolling
+ */
+ public void setFastScrollEnabled(boolean enabled) {
+ mFastScrollEnabled = enabled;
+ if (enabled) {
+ if (mFastScroller == null) {
+ mFastScroller = new FastScroller(getContext(), this);
+ }
+ } else {
+ if (mFastScroller != null) {
+ mFastScroller.stop();
+ mFastScroller = null;
+ }
+ }
+ }
+
+ /**
+ * Returns the current state of the fast scroll feature.
+ * @see #setFastScrollEnabled(boolean)
+ * @return true if fast scroll is enabled, false otherwise
+ */
+ @ViewDebug.ExportedProperty
+ public boolean isFastScrollEnabled() {
+ return mFastScrollEnabled;
+ }
+
+ /**
+ * When smooth scrollbar is enabled, the position and size of the scrollbar thumb
+ * is computed based on the number of visible pixels in the visible items. This
+ * however assumes that all list items have the same height. If you use a list in
+ * which items have different heights, the scrollbar will change appearance as the
+ * user scrolls through the list. To avoid this issue, you need to disable this
+ * property.
+ *
+ * When smooth scrollbar is disabled, the position and size of the scrollbar thumb
+ * is based solely on the number of items in the adapter and the position of the
+ * visible items inside the adapter. This provides a stable scrollbar as the user
+ * navigates through a list of items with varying heights.
+ *
+ * @param enabled Whether or not to enable smooth scrollbar.
+ *
+ * @see #setSmoothScrollbarEnabled(boolean)
+ * @attr ref android.R.styleable#AbsListView_smoothScrollbar
+ */
+ public void setSmoothScrollbarEnabled(boolean enabled) {
+ mSmoothScrollbarEnabled = enabled;
+ }
+
+ /**
+ * Returns the current state of the fast scroll feature.
+ *
+ * @return True if smooth scrollbar is enabled is enabled, false otherwise.
+ *
+ * @see #setSmoothScrollbarEnabled(boolean)
+ */
+ @ViewDebug.ExportedProperty
+ public boolean isSmoothScrollbarEnabled() {
+ return mSmoothScrollbarEnabled;
+ }
+
+ /**
* Set the listener that will receive notifications every time the list scrolls.
*
* @param l the scroll listener
@@ -511,6 +607,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* Notify our scroll listener (if there is one) of a change in scroll state
*/
void invokeOnItemScrollListener() {
+ if (mFastScroller != null) {
+ mFastScroller.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
+ }
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
}
@@ -525,6 +624,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* @see #setScrollingCacheEnabled(boolean)
* @see View#setDrawingCacheEnabled(boolean)
*/
+ @ViewDebug.ExportedProperty
public boolean isScrollingCacheEnabled() {
return mScrollingCacheEnabled;
}
@@ -571,6 +671,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* @see #setTextFilterEnabled(boolean)
* @see Filterable
*/
+ @ViewDebug.ExportedProperty
public boolean isTextFilterEnabled() {
return mTextFilterEnabled;
}
@@ -595,10 +696,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
setWillNotDraw(false);
setAlwaysDrawnWithCacheEnabled(false);
setScrollingCacheEnabled(true);
+ setScrollContainer(true);
}
private void useDefaultSelector() {
- setSelector(getResources().getDrawable(com.android.internal.R.drawable.list_selector_background));
+ setSelector(getResources().getDrawable(
+ com.android.internal.R.drawable.list_selector_background));
}
/**
@@ -607,6 +710,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*
* @return true if the content is stacked from the bottom edge, false otherwise
*/
+ @ViewDebug.ExportedProperty
public boolean isStackFromBottom() {
return mStackFromBottom;
}
@@ -843,35 +947,54 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
protected int computeVerticalScrollExtent() {
final int count = getChildCount();
if (count > 0) {
- int extent = count * 100;
+ if (mSmoothScrollbarEnabled) {
+ int extent = count * 100;
+
+ View view = getChildAt(0);
+ final int top = view.getTop();
+ int height = view.getHeight();
+ if (height > 0) {
+ extent += (top * 100) / height;
+ }
- View view = getChildAt(0);
- final int top = view.getTop();
- int height = view.getHeight();
- if (height > 0) {
- extent += (top * 100) / height;
- }
+ view = getChildAt(count - 1);
+ final int bottom = view.getBottom();
+ height = view.getHeight();
+ if (height > 0) {
+ extent -= ((bottom - getHeight()) * 100) / height;
+ }
- view = getChildAt(count - 1);
- final int bottom = view.getBottom();
- height = view.getHeight();
- if (height > 0) {
- extent -= ((bottom - getHeight()) * 100) / height;
+ return extent;
+ } else {
+ return 1;
}
-
- return extent;
}
return 0;
}
@Override
protected int computeVerticalScrollOffset() {
- if (mFirstPosition >= 0 && getChildCount() > 0) {
- final View view = getChildAt(0);
- final int top = view.getTop();
- int height = view.getHeight();
- if (height > 0) {
- return Math.max(mFirstPosition * 100 - (top * 100) / height, 0);
+ final int firstPosition = mFirstPosition;
+ final int childCount = getChildCount();
+ if (firstPosition >= 0 && childCount > 0) {
+ if (mSmoothScrollbarEnabled) {
+ final View view = getChildAt(0);
+ final int top = view.getTop();
+ int height = view.getHeight();
+ if (height > 0) {
+ return Math.max(firstPosition * 100 - (top * 100) / height, 0);
+ }
+ } else {
+ int index;
+ final int count = mItemCount;
+ if (firstPosition == 0) {
+ index = 0;
+ } else if (firstPosition + childCount == count) {
+ index = count;
+ } else {
+ index = firstPosition + childCount / 2;
+ }
+ return (int) (firstPosition + childCount * (index / (float) count));
}
}
return 0;
@@ -879,7 +1002,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
@Override
protected int computeVerticalScrollRange() {
- return Math.max(mItemCount * 100, 0);
+ return mSmoothScrollbarEnabled ? Math.max(mItemCount * 100, 0) : mItemCount;
}
@Override
@@ -1140,6 +1263,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mDataChanged = true;
rememberSyncState();
}
+ if (mFastScroller != null) {
+ mFastScroller.onSizeChanged(w, h, oldw, oldh);
+ }
}
/**
@@ -1669,6 +1795,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
@Override
public boolean onTouchEvent(MotionEvent ev) {
+
+ if (mFastScroller != null) {
+ boolean intercepted = mFastScroller.onTouchEvent(ev);
+ if (intercepted) {
+ return true;
+ }
+ }
final int action = ev.getAction();
final int x = (int) ev.getX();
final int y = (int) ev.getY();
@@ -1684,28 +1817,30 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
switch (action) {
case MotionEvent.ACTION_DOWN: {
int motionPosition = pointToPosition(x, y);
- if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
- && (getAdapter().isEnabled(motionPosition))) {
- // User clicked on an actual view (and was not stopping a fling). It might be a
- // click or a scroll. Assume it is a click until proven otherwise
- mTouchMode = TOUCH_MODE_DOWN;
- // FIXME Debounce
- if (mPendingCheckForTap == null) {
- mPendingCheckForTap = new CheckForTap();
- }
- postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
- } else {
- if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
- // If we couldn't find a view to click on, but the down event was touching
- // the edge, we will bail out and try again. This allows the edge correcting
- // code in ViewRoot to try to find a nearby view to select
- return false;
+ if (!mDataChanged) {
+ if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
+ && (getAdapter().isEnabled(motionPosition))) {
+ // User clicked on an actual view (and was not stopping a fling). It might be a
+ // click or a scroll. Assume it is a click until proven otherwise
+ mTouchMode = TOUCH_MODE_DOWN;
+ // FIXME Debounce
+ if (mPendingCheckForTap == null) {
+ mPendingCheckForTap = new CheckForTap();
+ }
+ postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
+ } else {
+ if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
+ // If we couldn't find a view to click on, but the down event was touching
+ // the edge, we will bail out and try again. This allows the edge correcting
+ // code in ViewRoot to try to find a nearby view to select
+ return false;
+ }
+ // User clicked on whitespace, or stopped a fling. It is a scroll.
+ createScrollingCache();
+ mTouchMode = TOUCH_MODE_SCROLL;
+ motionPosition = findMotionRow(y);
+ reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
}
- // User clicked on whitespace, or stopped a fling. It is a scroll.
- createScrollingCache();
- mTouchMode = TOUCH_MODE_SCROLL;
- motionPosition = findMotionRow(y);
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
}
if (motionPosition >= 0) {
@@ -1897,6 +2032,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return true;
}
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ if (mFastScroller != null) {
+ mFastScroller.draw(canvas);
+ }
+ }
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
@@ -1904,6 +2047,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
int x = (int) ev.getX();
int y = (int) ev.getY();
View v;
+
+ if (mFastScroller != null) {
+ boolean intercepted = mFastScroller.onInterceptTouchEvent(ev);
+ if (intercepted) {
+ return true;
+ }
+ }
+
switch (action) {
case MotionEvent.ACTION_DOWN: {
int motionPosition = findMotionRow(y);
@@ -1965,7 +2116,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
- private void reportScrollStateChange(int newState) {
+ /**
+ * Fires an "on scroll state changed" event to the registered
+ * {@link android.widget.AbsListView.OnScrollListener}, if any. The state change
+ * is fired only if the specified state is different from the previously known state.
+ *
+ * @param newState The new scroll state.
+ */
+ void reportScrollStateChange(int newState) {
if (newState != mLastScrollState) {
if (mOnScrollListener != null) {
mOnScrollListener.onScrollStateChanged(this, newState);
@@ -2013,10 +2171,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private void endFling() {
mTouchMode = TOUCH_MODE_REST;
- if (mOnScrollListener != null) {
- mOnScrollListener.onScrollStateChanged(AbsListView.this,
- OnScrollListener.SCROLL_STATE_IDLE);
- }
+ reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
clearScrollingCache();
}
@@ -2411,7 +2566,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (selectedPos >= 0) {
mLayoutMode = LAYOUT_SPECIFIC;
setSelectionInt(selectedPos);
+ invokeOnItemScrollListener();
}
+ reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
return selectedPos >= 0;
}
@@ -2547,7 +2704,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// Make sure we have a window before showing the popup
if (getWindowVisibility() == View.VISIBLE) {
int screenHeight = WindowManagerImpl.getDefault().getDefaultDisplay().getHeight();
- final int[] xy = mLocation;
+ final int[] xy = new int[2];
getLocationOnScreen(xy);
int bottomGap = screenHeight - xy[1] - getHeight() + 20;
mPopup.showAtLocation(this, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
@@ -2689,6 +2846,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
com.android.internal.R.layout.typing_filter, null);
mTextFilter.addTextChangedListener(this);
p.setFocusable(false);
+ p.setTouchable(false);
p.setContentView(mTextFilter);
p.setWidth(LayoutParams.WRAP_CONTENT);
p.setHeight(LayoutParams.WRAP_CONTENT);
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 1fa7318..65ca885 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -242,6 +242,7 @@ public abstract class AbsSeekBar extends ProgressBar {
case MotionEvent.ACTION_MOVE:
trackTouchEvent(event);
+ attemptClaimDrag();
break;
case MotionEvent.ACTION_UP:
@@ -281,6 +282,16 @@ public abstract class AbsSeekBar extends ProgressBar {
setProgress((int) progress, true);
}
+
+ /**
+ * Tries to claim the user's drag motion, and requests disallowing any
+ * ancestors from stealing events in the drag.
+ */
+ private void attemptClaimDrag() {
+ if (mParent != null) {
+ mParent.requestDisallowInterceptTouchEvent(true);
+ }
+ }
/**
* This is called when the user has started touching this widget.
diff --git a/core/java/android/widget/AbsoluteLayout.java b/core/java/android/widget/AbsoluteLayout.java
index 36a3b10..c77f7ae 100644
--- a/core/java/android/widget/AbsoluteLayout.java
+++ b/core/java/android/widget/AbsoluteLayout.java
@@ -32,7 +32,11 @@ import android.widget.RemoteViews.RemoteView;
* <p><strong>XML attributes</strong></p> <p> See {@link
* android.R.styleable#ViewGroup ViewGroup Attributes}, {@link
* android.R.styleable#View View Attributes}</p>
+ *
+ * @deprecated Use {@link android.widget.FrameLayout}, {@link android.widget.RelativeLayout}
+ * or a custom layout instead.
*/
+@Deprecated
@RemoteView
public class AbsoluteLayout extends ViewGroup {
public AbsoluteLayout(Context context) {
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index e096612..173e80f 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -140,7 +140,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
* The position within the adapter's data set of the item to select
* during the next layout.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty
int mNextSelectedPosition = INVALID_POSITION;
/**
@@ -151,7 +151,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
/**
* The position within the adapter's data set of the currently selected item.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty
int mSelectedPosition = INVALID_POSITION;
/**
@@ -520,6 +520,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
*
* @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
*/
+ @ViewDebug.CapturedViewProperty
public int getSelectedItemPosition() {
return mNextSelectedPosition;
}
@@ -528,6 +529,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
* @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
* if nothing is selected.
*/
+ @ViewDebug.CapturedViewProperty
public long getSelectedItemId() {
return mNextSelectedRowId;
}
@@ -557,6 +559,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
* AdapterView. (This is the number of data items, which may be
* larger than the number of visible view.)
*/
+ @ViewDebug.CapturedViewProperty
public int getCount() {
return mItemCount;
}
diff --git a/core/java/android/widget/AlphabetIndexer.java b/core/java/android/widget/AlphabetIndexer.java
new file mode 100644
index 0000000..bbabaaa
--- /dev/null
+++ b/core/java/android/widget/AlphabetIndexer.java
@@ -0,0 +1,283 @@
+/*
+ * 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.database.Cursor;
+import android.database.DataSetObserver;
+import android.util.SparseIntArray;
+
+/**
+ * A helper class for adapters that implement the SectionIndexer interface.
+ * If the items in the adapter are sorted by simple alphabet-based sorting, then
+ * this class provides a way to do fast indexing of large lists using binary search.
+ * It caches the indices that have been determined through the binary search and also
+ * invalidates the cache if changes occur in the cursor.
+ * <p/>
+ * Your adapter is responsible for updating the cursor by calling {@link #setCursor} if the
+ * cursor changes. {@link #getPositionForSection} method does the binary search for the starting
+ * index of a given section (alphabet).
+ * @hide pending API council approval
+ */
+public class AlphabetIndexer extends DataSetObserver implements SectionIndexer {
+
+ /**
+ * Cursor that is used by the adapter of the list view.
+ */
+ protected Cursor mDataCursor;
+
+ /**
+ * The index of the cursor column that this list is sorted on.
+ */
+ protected int mColumnIndex;
+
+ /**
+ * The string of characters that make up the indexing sections.
+ */
+ protected CharSequence mAlphabet;
+
+ /**
+ * Cached length of the alphabet array.
+ */
+ private int mAlphabetLength;
+
+ /**
+ * This contains a cache of the computed indices so far. It will get reset whenever
+ * the dataset changes or the cursor changes.
+ */
+ private SparseIntArray mAlphaMap;
+
+ /**
+ * Use a collator to compare strings in a localized manner.
+ */
+ private java.text.Collator mCollator;
+
+ /**
+ * The section array converted from the alphabet string.
+ */
+ private String[] mAlphabetArray;
+
+ /**
+ * Constructs the indexer.
+ * @param cursor the cursor containing the data set
+ * @param sortedColumnIndex the column number in the cursor that is sorted
+ * alphabetically
+ * @param alphabet string containing the alphabet, with space as the first character.
+ * For example, use the string " ABCDEFGHIJKLMNOPQRSTUVWXYZ" for English indexing.
+ * The characters must be uppercase and be sorted in ascii/unicode order. Basically
+ * characters in the alphabet will show up as preview letters.
+ */
+ public AlphabetIndexer(Cursor cursor, int sortedColumnIndex, CharSequence alphabet) {
+ mDataCursor = cursor;
+ mColumnIndex = sortedColumnIndex;
+ mAlphabet = alphabet;
+ mAlphabetLength = alphabet.length();
+ mAlphabetArray = new String[mAlphabetLength];
+ for (int i = 0; i < mAlphabetLength; i++) {
+ mAlphabetArray[i] = Character.toString(mAlphabet.charAt(i));
+ }
+ mAlphaMap = new SparseIntArray(mAlphabetLength);
+ if (cursor != null) {
+ cursor.registerDataSetObserver(this);
+ }
+ // Get a Collator for the current locale for string comparisons.
+ mCollator = java.text.Collator.getInstance();
+ mCollator.setStrength(java.text.Collator.PRIMARY);
+ }
+
+ /**
+ * Returns the section array constructed from the alphabet provided in the constructor.
+ * @return the section array
+ */
+ public Object[] getSections() {
+ return mAlphabetArray;
+ }
+
+ /**
+ * Sets a new cursor as the data set and resets the cache of indices.
+ * @param cursor the new cursor to use as the data set
+ */
+ public void setCursor(Cursor cursor) {
+ if (mDataCursor != null) {
+ mDataCursor.unregisterDataSetObserver(this);
+ }
+ mDataCursor = cursor;
+ if (cursor != null) {
+ mDataCursor.registerDataSetObserver(this);
+ }
+ mAlphaMap.clear();
+ }
+
+ /**
+ * Default implementation compares the first character of word with letter.
+ */
+ protected int compare(String word, String letter) {
+ return mCollator.compare(word.substring(0, 1), letter);
+ }
+
+ /**
+ * Performs a binary search or cache lookup to find the first row that
+ * matches a given section's starting letter.
+ * @param sectionIndex the section to search for
+ * @return the row index of the first occurrence, or the nearest next letter.
+ * For instance, if searching for "T" and no "T" is found, then the first
+ * row starting with "U" or any higher letter is returned. If there is no
+ * data following "T" at all, then the list size is returned.
+ */
+ public int getPositionForSection(int sectionIndex) {
+ final SparseIntArray alphaMap = mAlphaMap;
+ final Cursor cursor = mDataCursor;
+
+ if (cursor == null || mAlphabet == null) {
+ return 0;
+ }
+
+ // Check bounds
+ if (sectionIndex <= 0) {
+ return 0;
+ }
+ if (sectionIndex >= mAlphabetLength) {
+ sectionIndex = mAlphabetLength - 1;
+ }
+
+ int savedCursorPos = cursor.getPosition();
+
+ int count = cursor.getCount();
+ int start = 0;
+ int end = count;
+ int pos;
+
+ char letter = mAlphabet.charAt(sectionIndex);
+ String targetLetter = Character.toString(letter);
+ int key = letter;
+ // Check map
+ if (Integer.MIN_VALUE != (pos = alphaMap.get(key, Integer.MIN_VALUE))) {
+ // Is it approximate? Using negative value to indicate that it's
+ // an approximation and positive value when it is the accurate
+ // position.
+ if (pos < 0) {
+ pos = -pos;
+ end = pos;
+ } else {
+ // Not approximate, this is the confirmed start of section, return it
+ return pos;
+ }
+ }
+
+ // Do we have the position of the previous section?
+ if (sectionIndex > 0) {
+ int prevLetter =
+ mAlphabet.charAt(sectionIndex - 1);
+ int prevLetterPos = alphaMap.get(prevLetter, Integer.MIN_VALUE);
+ if (prevLetterPos != Integer.MIN_VALUE) {
+ start = Math.abs(prevLetterPos);
+ }
+ }
+
+ // Now that we have a possibly optimized start and end, let's binary search
+
+ pos = (end + start) / 2;
+
+ while (pos < end) {
+ // Get letter at pos
+ cursor.moveToPosition(pos);
+ String curName = cursor.getString(mColumnIndex);
+ if (curName == null) {
+ if (pos == 0) {
+ break;
+ } else {
+ pos--;
+ continue;
+ }
+ }
+ int diff = compare(curName, targetLetter);
+ if (diff != 0) {
+ // Commenting out approximation code because it doesn't work for certain
+ // lists with custom comparators
+ // Enter approximation in hash if a better solution doesn't exist
+ // String startingLetter = Character.toString(getFirstLetter(curName));
+ // int startingLetterKey = startingLetter.charAt(0);
+ // int curPos = alphaMap.get(startingLetterKey, Integer.MIN_VALUE);
+ // if (curPos == Integer.MIN_VALUE || Math.abs(curPos) > pos) {
+ // Negative pos indicates that it is an approximation
+ // alphaMap.put(startingLetterKey, -pos);
+ // }
+ // if (mCollator.compare(startingLetter, targetLetter) < 0) {
+ if (diff < 0) {
+ start = pos + 1;
+ if (start >= count) {
+ pos = count;
+ break;
+ }
+ } else {
+ end = pos;
+ }
+ } else {
+ // They're the same, but that doesn't mean it's the start
+ if (start == pos) {
+ // This is it
+ break;
+ } else {
+ // Need to go further lower to find the starting row
+ end = pos;
+ }
+ }
+ pos = (start + end) / 2;
+ }
+ alphaMap.put(key, pos);
+ cursor.moveToPosition(savedCursorPos);
+ return pos;
+ }
+
+ /**
+ * Returns the section index for a given position in the list by querying the item
+ * and comparing it with all items in the section array.
+ */
+ public int getSectionForPosition(int position) {
+ int savedCursorPos = mDataCursor.getPosition();
+ mDataCursor.moveToPosition(position);
+ mDataCursor.moveToPosition(savedCursorPos);
+ String curName = mDataCursor.getString(mColumnIndex);
+ // Linear search, as there are only a few items in the section index
+ // Could speed this up later if it actually gets used.
+ for (int i = 0; i < mAlphabetLength; i++) {
+ char letter = mAlphabet.charAt(i);
+ String targetLetter = Character.toString(letter);
+ if (compare(curName, targetLetter) == 0) {
+ return i;
+ }
+ }
+ return 0; // Don't recognize the letter - falls under zero'th section
+ }
+
+ /*
+ * @hide
+ */
+ @Override
+ public void onChanged() {
+ super.onChanged();
+ mAlphaMap.clear();
+ }
+
+ /*
+ * @hide
+ */
+ @Override
+ public void onInvalidated() {
+ super.onInvalidated();
+ mAlphaMap.clear();
+ }
+}
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 808104e..fbb0105 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -25,9 +25,9 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.text.format.Time;
import android.util.AttributeSet;
import android.view.View;
-import android.pim.Time;
import java.util.TimeZone;
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index 582117f..5fa00e7 100755
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -18,7 +18,6 @@ package android.widget;
import com.android.internal.R;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
@@ -31,9 +30,17 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+
import java.io.File;
+import java.text.Collator;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -56,15 +63,15 @@ public class AppSecurityPermissions implements View.OnClickListener {
BOTH
}
- private final String TAG = "AppSecurityPermissions";
+ private final static String TAG = "AppSecurityPermissions";
private boolean localLOGV = false;
private Context mContext;
private LayoutInflater mInflater;
private PackageManager mPm;
private LinearLayout mPermsView;
- private HashMap<String, String> mDangerousMap;
- private HashMap<String, String> mNormalMap;
- private ArrayList<PermissionInfo> mPermsList;
+ private Map<String, String> mDangerousMap;
+ private Map<String, String> mNormalMap;
+ private List<PermissionInfo> mPermsList;
private String mDefaultGrpLabel;
private String mDefaultGrpName="DefaultGrp";
private String mPermFormat;
@@ -79,18 +86,129 @@ public class AppSecurityPermissions implements View.OnClickListener {
private State mCurrentState;
private LinearLayout mNonDangerousList;
private LinearLayout mDangerousList;
- private HashMap<String, String> mGroupLabelCache;
+ private HashMap<String, CharSequence> mGroupLabelCache;
private View mNoPermsView;
-
- public AppSecurityPermissions(Context context) {
- this(context, null);
- }
-
- public AppSecurityPermissions(Context context, ArrayList<PermissionInfo> permList) {
+
+ public AppSecurityPermissions(Context context, List<PermissionInfo> permList) {
mContext = context;
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mPm = context.getPackageManager();
+ mPm = mContext.getPackageManager();
mPermsList = permList;
+ }
+
+ public AppSecurityPermissions(Context context, String packageName) {
+ mContext = context;
+ mPm = mContext.getPackageManager();
+ mPermsList = new ArrayList<PermissionInfo>();
+ Set<PermissionInfo> permSet = new HashSet<PermissionInfo>();
+ PackageInfo pkgInfo;
+ try {
+ pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName);
+ return;
+ }
+ // Extract all user permissions
+ if((pkgInfo.applicationInfo != null) && (pkgInfo.applicationInfo.uid != -1)) {
+ getAllUsedPermissions(pkgInfo.applicationInfo.uid, permSet);
+ }
+ for(PermissionInfo tmpInfo : permSet) {
+ mPermsList.add(tmpInfo);
+ }
+ }
+
+ public AppSecurityPermissions(Context context, PackageParser.Package pkg) {
+ mContext = context;
+ mPm = mContext.getPackageManager();
+ mPermsList = new ArrayList<PermissionInfo>();
+ Set<PermissionInfo> permSet = new HashSet<PermissionInfo>();
+ if(pkg == null) {
+ return;
+ }
+ // Extract shared user permissions if any
+ if(pkg.mSharedUserId != null) {
+ int sharedUid;
+ try {
+ sharedUid = mPm.getUidForSharedUser(pkg.mSharedUserId);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Could'nt retrieve shared user id for:"+pkg.packageName);
+ return;
+ }
+ getAllUsedPermissions(sharedUid, permSet);
+ } else {
+ ArrayList<String> strList = pkg.requestedPermissions;
+ int size;
+ if((strList == null) || ((size = strList.size()) == 0)) {
+ return;
+ }
+ // Extract permissions defined in current package
+ extractPerms(strList.toArray(new String[size]), permSet);
+ }
+ for(PermissionInfo tmpInfo : permSet) {
+ mPermsList.add(tmpInfo);
+ }
+ }
+
+ public PackageParser.Package getPackageInfo(Uri packageURI) {
+ final String archiveFilePath = packageURI.getPath();
+ PackageParser packageParser = new PackageParser(archiveFilePath);
+ File sourceFile = new File(archiveFilePath);
+ DisplayMetrics metrics = new DisplayMetrics();
+ metrics.setToDefaults();
+ return packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0);
+ }
+
+ private void getAllUsedPermissions(int sharedUid, Set<PermissionInfo> permSet) {
+ String sharedPkgList[] = mPm.getPackagesForUid(sharedUid);
+ if(sharedPkgList == null || (sharedPkgList.length == 0)) {
+ return;
+ }
+ for(String sharedPkg : sharedPkgList) {
+ getPermissionsForPackage(sharedPkg, permSet);
+ }
+ }
+
+ private void getPermissionsForPackage(String packageName,
+ Set<PermissionInfo> permSet) {
+ PackageInfo pkgInfo;
+ try {
+ pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName);
+ return;
+ }
+ if(pkgInfo == null) {
+ return;
+ }
+ String strList[] = pkgInfo.requestedPermissions;
+ if(strList == null) {
+ return;
+ }
+ extractPerms(strList, permSet);
+ }
+
+ private void extractPerms(String strList[], Set<PermissionInfo> permSet) {
+ if((strList == null) || (strList.length == 0)) {
+ return;
+ }
+ for(String permName:strList) {
+ try {
+ PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0);
+ if(tmpPermInfo != null) {
+ permSet.add(tmpPermInfo);
+ }
+ } catch (NameNotFoundException e) {
+ Log.i(TAG, "Ignoring unknown permission:"+permName);
+ }
+ }
+ }
+
+ public int getPermissionCount() {
+ return mPermsList.size();
+ }
+
+ public View getPermissionsView() {
+
+ mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mPermsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null);
mShowMore = mPermsView.findViewById(R.id.show_more);
mShowMoreIcon = (ImageView) mShowMore.findViewById(R.id.show_more_icon);
@@ -112,32 +230,9 @@ public class AppSecurityPermissions implements View.OnClickListener {
mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission);
mShowMaxIcon = mContext.getResources().getDrawable(R.drawable.expander_ic_maximized);
mShowMinIcon = mContext.getResources().getDrawable(R.drawable.expander_ic_minimized);
- }
-
- public void setSecurityPermissionsView() {
- setPermissions(mPermsList);
- }
-
- public void setSecurityPermissionsView(Uri pkgURI) {
- final String archiveFilePath = pkgURI.getPath();
- PackageParser packageParser = new PackageParser(archiveFilePath);
- File sourceFile = new File(archiveFilePath);
- DisplayMetrics metrics = new DisplayMetrics();
- metrics.setToDefaults();
- PackageParser.Package pkgInfo = packageParser.parsePackage(sourceFile,
- archiveFilePath, metrics, 0);
- mPermsList = generatePermissionsInfo(pkgInfo.requestedPermissions);
- //For packages that havent been installed we need the application info object
- //to load the labels and other resources.
- setPermissions(mPermsList, pkgInfo.applicationInfo);
- }
-
- public void setSecurityPermissionsView(PackageInfo pInfo) {
- mPermsList = generatePermissionsInfo(pInfo.requestedPermissions);
+
+ // Set permissions view
setPermissions(mPermsList);
- }
-
- public View getPermissionsView() {
return mPermsView;
}
@@ -164,74 +259,30 @@ public class AppSecurityPermissions implements View.OnClickListener {
* is null the other non null value is returned without formatting
* this is to placate initial error checks
*/
- private String formatPermissions(String groupDesc, String permDesc) {
+ private String formatPermissions(String groupDesc, CharSequence permDesc) {
if(groupDesc == null) {
- return permDesc;
- }
- groupDesc = canonicalizeGroupDesc(groupDesc);
- if(permDesc == null) {
- return groupDesc;
+ if(permDesc == null) {
+ return null;
+ }
+ return permDesc.toString();
}
- return String.format(mPermFormat, groupDesc, permDesc);
- }
-
- /**
- * Utility method that concatenates two strings defined by mPermFormat.
- */
- private String formatPermissions(String groupDesc, CharSequence permDesc) {
groupDesc = canonicalizeGroupDesc(groupDesc);
if(permDesc == null) {
return groupDesc;
}
- // Format only if str1 and str2 are not null.
- return formatPermissions(groupDesc, permDesc.toString());
+ // groupDesc and permDesc are non null
+ return String.format(mPermFormat, groupDesc, permDesc.toString());
}
- private ArrayList<PermissionInfo> generatePermissionsInfo(String[] strList) {
- ArrayList<PermissionInfo> permInfoList = new ArrayList<PermissionInfo>();
- if(strList == null) {
- return permInfoList;
- }
- PermissionInfo tmpPermInfo = null;
- for(int i = 0; i < strList.length; i++) {
- try {
- tmpPermInfo = mPm.getPermissionInfo(strList[i], 0);
- permInfoList.add(tmpPermInfo);
- } catch (NameNotFoundException e) {
- Log.i(TAG, "Ignoring unknown permisison:"+strList[i]);
- continue;
- }
- }
- return permInfoList;
- }
-
- private ArrayList<PermissionInfo> generatePermissionsInfo(ArrayList<String> strList) {
- ArrayList<PermissionInfo> permInfoList = new ArrayList<PermissionInfo>();
- if(strList != null) {
- PermissionInfo tmpPermInfo = null;
- for(String permName:strList) {
- try {
- tmpPermInfo = mPm.getPermissionInfo(permName, 0);
- permInfoList.add(tmpPermInfo);
- } catch (NameNotFoundException e) {
- Log.i(TAG, "Ignoring unknown permisison:"+permName);
- continue;
- }
- }
- }
- return permInfoList;
- }
-
- private String getGroupLabel(String grpName) {
+ private CharSequence getGroupLabel(String grpName) {
if (grpName == null) {
//return default label
return mDefaultGrpLabel;
}
- String cachedLabel = mGroupLabelCache.get(grpName);
+ CharSequence cachedLabel = mGroupLabelCache.get(grpName);
if (cachedLabel != null) {
return cachedLabel;
}
-
PermissionGroupInfo pgi;
try {
pgi = mPm.getPermissionGroupInfo(grpName, 0);
@@ -239,7 +290,7 @@ public class AppSecurityPermissions implements View.OnClickListener {
Log.i(TAG, "Invalid group name:" + grpName);
return null;
}
- String label = pgi.loadLabel(mPm).toString();
+ CharSequence label = pgi.loadLabel(mPm).toString();
mGroupLabelCache.put(grpName, label);
return label;
}
@@ -249,13 +300,13 @@ public class AppSecurityPermissions implements View.OnClickListener {
* list of permission descriptions.
*/
private void displayPermissions(boolean dangerous) {
- HashMap<String, String> permInfoMap = dangerous ? mDangerousMap : mNormalMap;
+ Map<String, String> permInfoMap = dangerous ? mDangerousMap : mNormalMap;
LinearLayout permListView = dangerous ? mDangerousList : mNonDangerousList;
permListView.removeAllViews();
Set<String> permInfoStrSet = permInfoMap.keySet();
for (String loopPermGrpInfoStr : permInfoStrSet) {
- String grpLabel = getGroupLabel(loopPermGrpInfoStr);
+ CharSequence grpLabel = getGroupLabel(loopPermGrpInfoStr);
//guaranteed that grpLabel wont be null since permissions without groups
//will belong to the default group
if(localLOGV) Log.i(TAG, "Adding view group:" + grpLabel + ", desc:"
@@ -269,7 +320,7 @@ public class AppSecurityPermissions implements View.OnClickListener {
mNoPermsView.setVisibility(View.VISIBLE);
}
- private View getPermissionItemView(String grpName, String permList,
+ private View getPermissionItemView(CharSequence grpName, String permList,
boolean dangerous) {
View permView = mInflater.inflate(R.layout.app_permission_item, null);
Drawable icon = dangerous ? mDangerousIcon : mNormalIcon;
@@ -334,35 +385,105 @@ public class AppSecurityPermissions implements View.OnClickListener {
}
return false;
}
-
- private void setPermissions(ArrayList<PermissionInfo> permList) {
- setPermissions(permList, null);
+
+ /*
+ * Utility method that aggregates all permission descriptions categorized by group
+ * Say group1 has perm11, perm12, perm13, the group description will be
+ * perm11_Desc, perm12_Desc, perm13_Desc
+ */
+ private void aggregateGroupDescs(
+ Map<String, List<PermissionInfo> > map, Map<String, String> retMap) {
+ if(map == null) {
+ return;
+ }
+ if(retMap == null) {
+ return;
+ }
+ Set<String> grpNames = map.keySet();
+ Iterator<String> grpNamesIter = grpNames.iterator();
+ while(grpNamesIter.hasNext()) {
+ String grpDesc = null;
+ String grpNameKey = grpNamesIter.next();
+ List<PermissionInfo> grpPermsList = map.get(grpNameKey);
+ if(grpPermsList == null) {
+ continue;
+ }
+ for(PermissionInfo permInfo: grpPermsList) {
+ CharSequence permDesc = permInfo.loadLabel(mPm);
+ grpDesc = formatPermissions(grpDesc, permDesc);
+ }
+ // Insert grpDesc into map
+ if(grpDesc != null) {
+ if(localLOGV) Log.i(TAG, "Group:"+grpNameKey+" description:"+grpDesc.toString());
+ retMap.put(grpNameKey, grpDesc.toString());
+ }
+ }
}
- private void setPermissions(ArrayList<PermissionInfo> permList, ApplicationInfo appInfo) {
- mDangerousMap = new HashMap<String, String>();
- mNormalMap = new HashMap<String, String>();
- mGroupLabelCache = new HashMap<String, String>();
+ private static class PermissionInfoComparator implements Comparator<PermissionInfo> {
+ private PackageManager mPm;
+ private final Collator sCollator = Collator.getInstance();
+ PermissionInfoComparator(PackageManager pm) {
+ mPm = pm;
+ }
+ public final int compare(PermissionInfo a, PermissionInfo b) {
+ CharSequence sa = a.loadLabel(mPm);
+ CharSequence sb = b.loadLabel(mPm);
+ return sCollator.compare(sa, sb);
+ }
+ }
+
+ private void setPermissions(List<PermissionInfo> permList) {
+ mGroupLabelCache = new HashMap<String, CharSequence>();
//add the default label so that uncategorized permissions can go here
mGroupLabelCache.put(mDefaultGrpName, mDefaultGrpLabel);
+
+ // Map containing group names and a list of permissions under that group
+ // categorized as dangerous
+ mDangerousMap = new HashMap<String, String>();
+ // Map containing group names and a list of permissions under that group
+ // categorized as normal
+ mNormalMap = new HashMap<String, String>();
+
+ // Additional structures needed to ensure that permissions are unique under
+ // each group
+ Map<String, List<PermissionInfo>> dangerousMap =
+ new HashMap<String, List<PermissionInfo>>();
+ Map<String, List<PermissionInfo> > normalMap =
+ new HashMap<String, List<PermissionInfo>>();
+ PermissionInfoComparator permComparator = new PermissionInfoComparator(mPm);
+
if (permList != null) {
+ // First pass to group permissions
for (PermissionInfo pInfo : permList) {
+ if(localLOGV) Log.i(TAG, "Processing permission:"+pInfo.name);
if(!isDisplayablePermission(pInfo)) {
+ if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" is not displayable");
continue;
}
- String grpName = (pInfo.group == null) ? mDefaultGrpName : pInfo.group;
- HashMap<String, String> permInfoMap =
+ Map<String, List<PermissionInfo> > permInfoMap =
(pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) ?
- mDangerousMap : mNormalMap;
- // Check to make sure we have a label for the group
- if (getGroupLabel(grpName) == null) {
- continue;
+ dangerousMap : normalMap;
+ String grpName = (pInfo.group == null) ? mDefaultGrpName : pInfo.group;
+ if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" belongs to group:"+grpName);
+ List<PermissionInfo> grpPermsList = permInfoMap.get(grpName);
+ if(grpPermsList == null) {
+ grpPermsList = new ArrayList<PermissionInfo>();
+ permInfoMap.put(grpName, grpPermsList);
+ grpPermsList.add(pInfo);
+ } else {
+ int idx = Collections.binarySearch(grpPermsList, pInfo, permComparator);
+ if(localLOGV) Log.i(TAG, "idx="+idx+", list.size="+grpPermsList.size());
+ if (idx < 0) {
+ idx = -idx-1;
+ grpPermsList.add(idx, pInfo);
+ }
}
- CharSequence permDesc = pInfo.loadLabel(mPm);
- String grpDesc = permInfoMap.get(grpName);
- permInfoMap.put(grpName, formatPermissions(grpDesc, permDesc));
- if(localLOGV) Log.i(TAG, pInfo.name + " : " + permDesc+" : " + grpName);
}
+ // Second pass to actually form the descriptions
+ // Look at dangerous permissions first
+ aggregateGroupDescs(dangerousMap, mDangerousMap);
+ aggregateGroupDescs(normalMap, mNormalMap);
}
mCurrentState = State.NO_PERMS;
diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
index fe50a01..c65a3ce 100644
--- a/core/java/android/widget/ArrayAdapter.java
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -28,7 +28,7 @@ import java.util.List;
/**
* A ListAdapter that manages a ListView backed by an array of arbitrary
- * objects. By default this class expects that the provided resource id referecnes
+ * objects. By default this class expects that the provided resource id references
* a single TextView. If you want to use a more complex layout, use the constructors that
* also takes a field id. That field id should reference a TextView in the larger layout
* resource.
@@ -179,7 +179,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
}
/**
- * Inserts the spcified object at the specified index in the array.
+ * Inserts the specified object at the specified index in the array.
*
* @param object The object to insert into the array.
* @param index The index at which the object must be inserted.
@@ -385,7 +385,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
}
/**
- * <p>An array filters constrains the content of the array adapter with
+ * <p>An array filter 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>
*/
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index e1f6fa8..d8fa603 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -23,11 +23,17 @@ import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.Selection;
import android.text.TextUtils;
+import android.text.TextWatcher;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.EditorInfo;
import com.android.internal.R;
@@ -55,7 +61,7 @@ import com.android.internal.R;
* super.onCreate(icicle);
* setContentView(R.layout.countries);
*
- * ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+ * ArrayAdapter&lt;String&gt; adapter = new ArrayAdapter&lt;String&gt;(this,
* android.R.layout.simple_dropdown_item_1line, COUNTRIES);
* AutoCompleteTextView textView = (AutoCompleteTextView)
* findViewById(R.id.countries_list);
@@ -74,6 +80,9 @@ import com.android.internal.R;
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownSelector
*/
public class AutoCompleteTextView extends EditText implements Filter.FilterListener {
+ static final boolean DEBUG = false;
+ static final String TAG = "AutoCompleteTextView";
+
private static final int HINT_VIEW_ID = 0x17;
private CharSequence mHintText;
@@ -85,6 +94,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
private PopupWindow mPopup;
private DropDownListView mDropDownList;
+ private int mDropDownVerticalOffset;
+ private int mDropDownHorizontalOffset;
private Drawable mDropDownListHighlight;
@@ -94,7 +105,12 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
private final DropDownItemClickListener mDropDownItemClickListener =
new DropDownItemClickListener();
- private boolean mTextChanged;
+ private int mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+ private boolean mOpenBefore;
+
+ private Validator mValidator = null;
+
+ private AutoCompleteTextView.ListSelectorHider mHideSelector;
public AutoCompleteTextView(Context context) {
this(context, null);
@@ -107,7 +123,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- mPopup = new PopupWindow(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle);
+ mPopup = new PopupWindow(context, attrs,
+ com.android.internal.R.attr.autoCompleteTextViewStyle);
TypedArray a =
context.obtainStyledAttributes(
@@ -120,13 +137,34 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
mDropDownListHighlight = a.getDrawable(
R.styleable.AutoCompleteTextView_dropDownSelector);
+ mDropDownVerticalOffset = (int)
+ a.getDimension(R.styleable.AutoCompleteTextView_dropDownVerticalOffset, 0.0f);
+ mDropDownHorizontalOffset = (int)
+ a.getDimension(R.styleable.AutoCompleteTextView_dropDownHorizontalOffset, 0.0f);
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);
+ }
+ }
+
a.recycle();
setFocusable(true);
+
+ addTextChangedListener(new MyWatcher());
}
/**
@@ -209,7 +247,10 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* in the drop down list.</p>
*
* @return the item click listener
+ *
+ * @deprecated Use {@link #getOnItemClickListener()} intead
*/
+ @Deprecated
public AdapterView.OnItemClickListener getItemClickListener() {
return mItemClickListener;
}
@@ -219,12 +260,35 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* item in the drop down list.</p>
*
* @return the item selected listener
+ *
+ * @deprecated Use {@link #getOnItemSelectedListener()} intead
*/
+ @Deprecated
public AdapterView.OnItemSelectedListener getItemSelectedListener() {
return mItemSelectedListener;
}
/**
+ * <p>Returns the listener that is notified whenever the user clicks an item
+ * in the drop down list.</p>
+ *
+ * @return the item click listener
+ */
+ public AdapterView.OnItemClickListener getOnItemClickListener() {
+ return mItemClickListener;
+ }
+
+ /**
+ * <p>Returns the listener that is notified whenever the user selects an
+ * item in the drop down list.</p>
+ *
+ * @return the item selected listener
+ */
+ public AdapterView.OnItemSelectedListener getOnItemSelectedListener() {
+ return mItemSelectedListener;
+ }
+
+ /**
* <p>Returns a filterable list adapter used for auto completion.</p>
*
* @return a data adapter used for auto completion
@@ -258,6 +322,19 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
@Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ if (isPopupShowing()) {
+ // special case for the back key, we do not even try to send it
+ // to the drop down list but instead, consume it immediately
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ dismissDropDown();
+ return true;
+ }
+ }
+ return super.onKeyPreIme(keyCode, event);
+ }
+
+ @Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (isPopupShowing()) {
boolean consumed = mDropDownList.onKeyUp(keyCode, event);
@@ -280,18 +357,41 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public boolean onKeyDown(int keyCode, KeyEvent event) {
// when the drop down is shown, we drive it directly
if (isPopupShowing()) {
- // special case for the back key, we do not even try to send it
- // to the drop down list but instead, consume it immediately
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- dismissDropDown();
- return true;
-
// the key events are forwarded to the list in the drop down view
// note that ListView handles space but we don't want that to happen
- } else if (keyCode != KeyEvent.KEYCODE_SPACE) {
- boolean consumed = mDropDownList.onKeyDown(keyCode, event);
-
+ if (keyCode != KeyEvent.KEYCODE_SPACE) {
+ int curIndex = mDropDownList.getSelectedItemPosition();
+ boolean consumed;
+ if (keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= 0) {
+ // When the selection is at the top, we block the key
+ // event to prevent focus from moving.
+ mDropDownList.hideSelector();
+ mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
+ mPopup.update();
+ return true;
+ }
+ consumed = mDropDownList.onKeyDown(keyCode, event);
+ if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed="
+ + consumed);
if (consumed) {
+ // If it handled the key event, then the user is
+ // navigating in the list, so we should put it in front.
+ mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+ // Here's a little trick we need to do to make sure that
+ // the list view is actually showing its focus indicator,
+ // 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) {
// avoid passing the focus from the text view to the
// next component
@@ -301,22 +401,14 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
case KeyEvent.KEYCODE_DPAD_UP:
return true;
}
- } else{
- int index = mDropDownList.getSelectedItemPosition();
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_UP:
- if (index == 0) {
- return true;
- }
- break;
+ } else {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
// when the selection is at the bottom, we block the
// event to avoid going to the next focusable widget
- case KeyEvent.KEYCODE_DPAD_DOWN:
- Adapter adapter = mDropDownList.getAdapter();
- if (index == adapter.getCount() - 1) {
- return true;
- }
- break;
+ Adapter adapter = mDropDownList.getAdapter();
+ if (curIndex == adapter.getCount() - 1) {
+ return true;
+ }
}
}
}
@@ -327,37 +419,9 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
}
- // when text is changed, inserted or deleted, we attempt to show
- // the drop down
- boolean openBefore = isPopupShowing();
- mTextChanged = false;
-
+ mLastKeyCode = keyCode;
boolean handled = super.onKeyDown(keyCode, event);
-
- // 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.
- if (openBefore && !isPopupShowing()) {
- return handled;
- }
-
- if (mTextChanged) { // would have been set in onTextChanged()
- // the drop down is shown only when a minimum number of characters
- // was typed in the text view
- if (enoughToFilter()) {
- if (mFilter != null) {
- performFiltering(getText(), keyCode);
- }
- } else {
- // drop down is automatically dismissed when enough characters
- // are deleted from the text view
- dismissDropDown();
- if (mFilter != null) {
- mFilter.filter(null);
- }
- }
- return true;
- }
+ mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
return handled;
}
@@ -369,14 +433,58 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* triggered.
*/
public boolean enoughToFilter() {
+ if (DEBUG) Log.v(TAG, "Enough to filter: len=" + getText().length()
+ + " threshold=" + mThreshold);
return getText().length() >= mThreshold;
}
- @Override
- protected void onTextChanged(CharSequence text, int start, int before,
- int after) {
- super.onTextChanged(text, start, before, after);
- mTextChanged = true;
+ /**
+ * This is used to watch for edits to the text view. Note that we call
+ * to methods on the auto complete text view class so that we can access
+ * private vars without going through thunks.
+ */
+ private class MyWatcher implements TextWatcher {
+ public void afterTextChanged(Editable s) {
+ doAfterTextChanged();
+ }
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ doBeforeTextChanged();
+ }
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+ }
+
+ void doBeforeTextChanged() {
+ // when text is changed, inserted or deleted, we attempt to show
+ // the drop down
+ mOpenBefore = isPopupShowing();
+ if (DEBUG) Log.v(TAG, "before text changed: open=" + mOpenBefore);
+ }
+
+ void doAfterTextChanged() {
+ // 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.
+ if (DEBUG) Log.v(TAG, "after text changed: openBefore=" + mOpenBefore
+ + " open=" + isPopupShowing());
+ if (mOpenBefore && !isPopupShowing()) {
+ return;
+ }
+
+ // the drop down is shown only when a minimum number of characters
+ // was typed in the text view
+ if (enoughToFilter()) {
+ if (mFilter != null) {
+ performFiltering(getText(), mLastKeyCode);
+ }
+ } else {
+ // drop down is automatically dismissed when enough characters
+ // are deleted from the text view
+ dismissDropDown();
+ if (mFilter != null) {
+ mFilter.filter(null);
+ }
+ }
}
/**
@@ -407,7 +515,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* <code>text</code>.</p>
*
* @param text the filtering pattern
- * @param keyCode the last character inserted in the edit box
+ * @param keyCode the last character inserted in the edit box; beware that
+ * this will be null when text is being added through a soft input method.
*/
@SuppressWarnings({ "UnusedDeclaration" })
protected void performFiltering(CharSequence text, int keyCode) {
@@ -423,6 +532,19 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
performCompletion(null, -1, -1);
}
+ @Override public void onCommitCompletion(CompletionInfo completion) {
+ if (isPopupShowing()) {
+ replaceText(completion.getText());
+ if (mItemClickListener != null) {
+ final DropDownListView list = mDropDownList;
+ // Note that we don't have a View here, so we will need to
+ // supply null. Hopefully no existing apps crash...
+ mItemClickListener.onItemClick(list, null, completion.getPosition(),
+ completion.getId());
+ }
+ }
+ }
+
private void performCompletion(View selectedView, int position, long id) {
if (isPopupShowing()) {
Object selectedItem;
@@ -464,7 +586,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public void onFilterComplete(int count) {
/*
- * This checks enoughToFilter() again because filtering requests
+ * This checks enoughToFilter() again because filtering requests
* are asynchronous, so the result may come back after enough text
* has since been deleted to make it no longer appropriate
* to filter.
@@ -497,22 +619,32 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
}
+ @Override
+ protected void onDetachedFromWindow() {
+ dismissDropDown();
+ super.onDetachedFromWindow();
+ }
+
/**
* <p>Closes the drop down if present on screen.</p>
*/
public void dismissDropDown() {
- mPopup.dismiss();
- if (mDropDownList != null) {
- // start next time with no selection
- mDropDownList.hideSelector();
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.displayCompletions(this, null);
}
+ mPopup.dismiss();
+ mPopup.setContentView(null);
+ mDropDownList = null;
}
@Override
protected boolean setFrame(int l, int t, int r, int b) {
boolean result = super.setFrame(l, t, r, b);
- mPopup.update(this, getMeasuredWidth(), -1);
+ if (mPopup.isShowing()) {
+ mPopup.update(this, getMeasuredWidth() - mPaddingLeft - mPaddingRight, -1);
+ }
return result;
}
@@ -523,11 +655,20 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public void showDropDown() {
int height = buildDropDown();
if (mPopup.isShowing()) {
- mPopup.update(this, getMeasuredWidth() - mPaddingLeft - mPaddingRight, height);
+ mPopup.update(this, mDropDownHorizontalOffset, mDropDownVerticalOffset,
+ getMeasuredWidth() - mPaddingLeft - mPaddingRight, height);
} else {
- mPopup.setHeight(height);
+ mPopup.setWindowLayoutMode(0, ViewGroup.LayoutParams.WRAP_CONTENT);
mPopup.setWidth(getMeasuredWidth() - mPaddingLeft - mPaddingRight);
- mPopup.showAsDropDown(this);
+ mPopup.setHeight(height);
+ mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
+ mPopup.setOutsideTouchable(true);
+ mPopup.setTouchInterceptor(new PopupTouchIntercepter());
+ mPopup.showAsDropDown(this, mDropDownHorizontalOffset, mDropDownVerticalOffset);
+ mDropDownList.hideSelector();
+ mDropDownList.setSelection(0);
+ mDropDownList.requestFocus();
+ post(mHideSelector);
}
}
@@ -541,14 +682,34 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
ViewGroup dropDownView;
int otherHeights = 0;
+ if (mAdapter != null) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ int N = mAdapter.getCount();
+ if (N > 20) N = 20;
+ CompletionInfo[] completions = new CompletionInfo[N];
+ for (int i=0; i<N; i++) {
+ Object item = mAdapter.getItem(i);
+ long id = mAdapter.getItemId(i);
+ completions[i] = new CompletionInfo(id, i,
+ convertSelectionToString(item));
+ }
+ imm.displayCompletions(this, completions);
+ }
+ }
+
if (mDropDownList == null) {
Context context = getContext();
+ mHideSelector = new ListSelectorHider();
+
mDropDownList = new DropDownListView(context);
mDropDownList.setSelector(mDropDownListHighlight);
mDropDownList.setAdapter(mAdapter);
mDropDownList.setVerticalFadingEdgeEnabled(true);
mDropDownList.setOnItemClickListener(mDropDownItemClickListener);
+ mDropDownList.setFocusable(true);
+ mDropDownList.setFocusableInTouchMode(true);
if (mItemSelectedListener != null) {
mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
@@ -614,6 +775,73 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
}
+ /**
+ * Sets the validator used to perform text validation.
+ *
+ * @param validator The validator used to validate the text entered in this widget.
+ *
+ * @see #getValidator()
+ * @see #performValidation()
+ */
+ public void setValidator(Validator validator) {
+ mValidator = validator;
+ }
+
+ /**
+ * Returns the Validator set with {@link #setValidator},
+ * or <code>null</code> if it was not set.
+ *
+ * @see #setValidator(android.widget.AutoCompleteTextView.Validator)
+ * @see #performValidation()
+ */
+ public Validator getValidator() {
+ return mValidator;
+ }
+
+ /**
+ * If a validator was set on this view and the current string is not valid,
+ * ask the validator to fix it.
+ *
+ * @see #getValidator()
+ * @see #setValidator(android.widget.AutoCompleteTextView.Validator)
+ */
+ public void performValidation() {
+ if (mValidator == null) return;
+
+ CharSequence text = getText();
+
+ if (!TextUtils.isEmpty(text) && !mValidator.isValid(text)) {
+ setText(mValidator.fixText(text));
+ }
+ }
+
+ /**
+ * Returns the Filter obtained from {@link Filterable#getFilter},
+ * or <code>null</code> if {@link #setAdapter} was not called with
+ * a Filterable.
+ */
+ protected Filter getFilter() {
+ return mFilter;
+ }
+
+ private class ListSelectorHider implements Runnable {
+ public void run() {
+ if (mDropDownList != null) {
+ mDropDownList.hideSelector();
+ }
+ }
+ }
+
+ private class PopupTouchIntercepter implements OnTouchListener {
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+ mPopup.update();
+ }
+ return false;
+ }
+ }
+
private class DropDownItemClickListener implements AdapterView.OnItemClickListener {
public void onItemClick(AdapterView parent, View v, int position, long id) {
performCompletion(v, position, id);
@@ -701,6 +929,21 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public boolean hasFocus() {
return true;
}
+
+ protected int[] onCreateDrawableState(int extraSpace) {
+ int[] res = super.onCreateDrawableState(extraSpace);
+ if (false) {
+ StringBuilder sb = new StringBuilder("Created drawable state: [");
+ for (int i=0; i<res.length; i++) {
+ if (i > 0) sb.append(", ");
+ sb.append("0x");
+ sb.append(Integer.toHexString(res[i]));
+ }
+ sb.append("]");
+ Log.i(TAG, sb.toString());
+ }
+ return res;
+ }
}
/**
@@ -709,54 +952,26 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* this View with an incorrect value in it, all we can do is try to fix it ourselves
* when this happens.
*/
- static public interface Validator {
+ public interface Validator {
/**
- * @return true if the text currently in the text editor is valid.
+ * Validates the specified text.
+ *
+ * @return true If the text currently in the text editor is valid.
+ *
+ * @see #fixText(CharSequence)
*/
boolean isValid(CharSequence text);
/**
- * @param invalidText a string that doesn't pass validation:
- * isValid(invalidText) returns false
- * @return a string based on invalidText such as invoking isValid() on it returns true.
+ * Corrects the specified text to make it valid.
+ *
+ * @param invalidText A string that doesn't pass validation: isValid(invalidText)
+ * returns false
+ *
+ * @return A string based on invalidText such as invoking isValid() on it returns true.
+ *
+ * @see #isValid(CharSequence)
*/
CharSequence fixText(CharSequence invalidText);
}
-
- private Validator mValidator = null;
-
- public void setValidator(Validator validator) {
- mValidator = validator;
- }
-
- /**
- * Returns the Validator set with {@link #setValidator},
- * or <code>null</code> if it was not set.
- */
- public Validator getValidator() {
- return mValidator;
- }
-
- /**
- * If a validator was set on this view and the current string is not valid,
- * ask the validator to fix it.
- */
- public void performValidation() {
- if (mValidator == null) return;
-
- CharSequence text = getText();
-
- if (!TextUtils.isEmpty(text) && !mValidator.isValid(text)) {
- setText(mValidator.fixText(text));
- }
- }
-
- /**
- * Returns the Filter obtained from {@link Filterable#getFilter},
- * or <code>null</code> if {@link #setAdapter} was not called with
- * a Filterable.
- */
- protected Filter getFilter() {
- return mFilter;
- }
}
diff --git a/core/java/android/widget/BaseExpandableListAdapter.java b/core/java/android/widget/BaseExpandableListAdapter.java
index 3a8bb2a..1bba7f0 100644
--- a/core/java/android/widget/BaseExpandableListAdapter.java
+++ b/core/java/android/widget/BaseExpandableListAdapter.java
@@ -71,7 +71,7 @@ public abstract class BaseExpandableListAdapter implements ExpandableListAdapter
* <p>
* Base implementation returns a long:
* <li> bit 0: Whether this ID points to a child (unset) or group (set), so for this method
- * this bit will be 0.
+ * this bit will be 1.
* <li> bit 1-31: Lower 31 bits of the groupId
* <li> bit 32-63: Lower 32 bits of the childId.
* <p>
@@ -86,7 +86,7 @@ public abstract class BaseExpandableListAdapter implements ExpandableListAdapter
* <p>
* Base implementation returns a long:
* <li> bit 0: Whether this ID points to a child (unset) or group (set), so for this method
- * this bit will be 1.
+ * this bit will be 0.
* <li> bit 1-31: Lower 31 bits of the groupId
* <li> bit 32-63: Lower 32 bits of the childId.
* <p>
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index 31d2063..7086ae2 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -22,7 +22,7 @@ import android.graphics.Canvas;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
-import android.pim.DateUtils;
+import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.RemoteViews.RemoteView;
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
index cacaeab..555f216 100644
--- a/core/java/android/widget/CursorAdapter.java
+++ b/core/java/android/widget/CursorAdapter.java
@@ -357,9 +357,8 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
@Override
public void onChange(boolean selfChange) {
- if (mAutoRequery && mCursor != null) {
- if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor +
- " due to update");
+ if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
+ if (Config.LOGV) Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
mDataValid = mCursor.requery();
}
}
diff --git a/core/java/android/widget/CursorTreeAdapter.java b/core/java/android/widget/CursorTreeAdapter.java
index fa8fd4b..7b9b7bd 100644
--- a/core/java/android/widget/CursorTreeAdapter.java
+++ b/core/java/android/widget/CursorTreeAdapter.java
@@ -450,10 +450,9 @@ public abstract class CursorTreeAdapter extends BaseExpandableListAdapter implem
}
void changeCursor(Cursor cursor, boolean releaseCursors) {
- if (mCursor != null) {
- mCursor.unregisterContentObserver(mContentObserver);
- mCursor.unregisterDataSetObserver(mDataSetObserver);
- }
+ if (cursor == mCursor) return;
+
+ deactivate();
mCursor = cursor;
if (cursor != null) {
cursor.registerContentObserver(mContentObserver);
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index c03bd32..67010b2 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -21,7 +21,7 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
-import android.pim.DateFormat;
+import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.LayoutInflater;
diff --git a/core/java/android/widget/DigitalClock.java b/core/java/android/widget/DigitalClock.java
index 3ca2c81..379883a 100644
--- a/core/java/android/widget/DigitalClock.java
+++ b/core/java/android/widget/DigitalClock.java
@@ -21,8 +21,8 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.SystemClock;
-import android.pim.DateFormat;
import android.provider.Settings;
+import android.text.format.DateFormat;
import android.util.AttributeSet;
import java.util.Calendar;
@@ -105,13 +105,7 @@ public class DigitalClock extends TextView {
* Pulls 12/24 mode from system settings
*/
private boolean get24HourMode() {
- String value = Settings.System.getString(
- getContext().getContentResolver(),
- Settings.System.TIME_12_24);
-
- if (value == null || value.equals("12"))
- return false;
- return true;
+ return android.text.format.DateFormat.is24HourFormat(getContext());
}
private void setFormat() {
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index e89a2bd..57aca24 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -19,7 +19,6 @@ package android.widget;
import android.text.*;
import android.text.method.*;
import android.content.Context;
-import android.content.res.Resources;
import android.util.AttributeSet;
@@ -99,4 +98,13 @@ public class EditText extends TextView {
public void extendSelection(int index) {
Selection.extendSelection(getText(), index);
}
+
+ @Override
+ public void setEllipsize(TextUtils.TruncateAt ellipsis) {
+ if (ellipsis == TextUtils.TruncateAt.MARQUEE) {
+ throw new IllegalArgumentException("EditText cannot use the ellipsize mode "
+ + "TextUtils.TruncateAt.MARQUEE");
+ }
+ super.setEllipsize(ellipsis);
+ }
}
diff --git a/core/java/android/widget/ExpandableListConnector.java b/core/java/android/widget/ExpandableListConnector.java
index ddedea3..ccce7c1 100644
--- a/core/java/android/widget/ExpandableListConnector.java
+++ b/core/java/android/widget/ExpandableListConnector.java
@@ -19,11 +19,13 @@ package android.widget;
import android.database.DataSetObserver;
import android.os.Parcel;
import android.os.Parcelable;
-import android.view.KeyEvent;
+import android.os.SystemClock;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/*
* Implementation notes:
@@ -120,7 +122,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
* either), so flPos must be a group and its group pos will be the
* same as its flPos
*/
- return new PositionMetadata(flPos, ExpandableListPosition.GROUP, flPos,
+ return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP, flPos,
-1, null, 0);
}
@@ -159,7 +161,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
* The flat list position is this middle group's flat list
* position, so we've found an exact hit
*/
- return new PositionMetadata(flPos, ExpandableListPosition.GROUP,
+ return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP,
midExpGm.gPos, -1, midExpGm, midExpGroupIndex);
} else if (flPos <= midExpGm.lastChildFlPos
/* && flPos > midGm.flPos as deduced from previous
@@ -172,7 +174,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
* the group
*/
final int childPos = flPos - (midExpGm.flPos + 1);
- return new PositionMetadata(flPos, ExpandableListPosition.CHILD,
+ return PositionMetadata.obtain(flPos, ExpandableListPosition.CHILD,
midExpGm.gPos, childPos, midExpGm, midExpGroupIndex);
}
}
@@ -184,11 +186,13 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
*/
- /* If we are to expand this group later, where would it go in the
- * mExpGroupMetadataList ? */
+ /**
+ * If we are to expand this group later, where would it go in the
+ * mExpGroupMetadataList ?
+ */
int insertPosition = 0;
- /* What is its group position from the list of all groups? */
+ /** What is its group position in the list of all groups? */
int groupPos = 0;
/*
@@ -237,7 +241,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
throw new RuntimeException("Unknown state");
}
- return new PositionMetadata(flPos, ExpandableListPosition.GROUP, groupPos, -1,
+ return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP, groupPos, -1,
null, insertPosition);
}
@@ -250,7 +254,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
* @param pos a {@link ExpandableListPosition} representing either a group position
* or child position
* @return the flat list position encompassed in a {@link PositionMetadata}
- * object that contains additional useful info for insertion, etc.
+ * object that contains additional useful info for insertion, etc., or null.
*/
PositionMetadata getFlattenedPos(final ExpandableListPosition pos) {
final ArrayList<GroupMetadata> egml = mExpGroupMetadataList;
@@ -268,7 +272,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
* its flPos will be the same as its group pos. The
* insert position is 0 (since the list is empty).
*/
- return new PositionMetadata(pos.groupPos, pos.type,
+ return PositionMetadata.obtain(pos.groupPos, pos.type,
pos.groupPos, pos.childPos, null, 0);
}
@@ -298,11 +302,11 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
if (pos.type == ExpandableListPosition.GROUP) {
/* If it's a group, give them this matched group's flPos */
- return new PositionMetadata(midExpGm.flPos, pos.type,
+ return PositionMetadata.obtain(midExpGm.flPos, pos.type,
pos.groupPos, pos.childPos, midExpGm, midExpGroupIndex);
} else if (pos.type == ExpandableListPosition.CHILD) {
/* If it's a child, calculate the flat list pos */
- return new PositionMetadata(midExpGm.flPos + pos.childPos
+ return PositionMetadata.obtain(midExpGm.flPos + pos.childPos
+ 1, pos.type, pos.groupPos, pos.childPos,
midExpGm, midExpGroupIndex);
} else {
@@ -341,7 +345,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
leftExpGm.lastChildFlPos
+ (pos.groupPos - leftExpGm.gPos);
- return new PositionMetadata(flPos, pos.type, pos.groupPos,
+ return PositionMetadata.obtain(flPos, pos.type, pos.groupPos,
pos.childPos, null, leftExpGroupIndex);
} else if (rightExpGroupIndex < midExpGroupIndex) {
@@ -355,7 +359,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
final int flPos =
rightExpGm.flPos
- (rightExpGm.gPos - pos.groupPos);
- return new PositionMetadata(flPos, pos.type, pos.groupPos,
+ return PositionMetadata.obtain(flPos, pos.type, pos.groupPos,
pos.childPos, null, rightExpGroupIndex);
} else {
return null;
@@ -370,13 +374,18 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
@Override
public boolean isEnabled(int flatListPos) {
final ExpandableListPosition pos = getUnflattenedPos(flatListPos).position;
-
+
+ boolean retValue;
if (pos.type == ExpandableListPosition.CHILD) {
- return mExpandableListAdapter.isChildSelectable(pos.groupPos, pos.childPos);
+ retValue = mExpandableListAdapter.isChildSelectable(pos.groupPos, pos.childPos);
} else {
// Groups are always selectable
- return true;
+ retValue = true;
}
+
+ pos.recycle();
+
+ return retValue;
}
public int getCount() {
@@ -391,62 +400,80 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
public Object getItem(int flatListPos) {
final PositionMetadata posMetadata = getUnflattenedPos(flatListPos);
+ Object retValue;
if (posMetadata.position.type == ExpandableListPosition.GROUP) {
- return mExpandableListAdapter
+ retValue = mExpandableListAdapter
.getGroup(posMetadata.position.groupPos);
} else if (posMetadata.position.type == ExpandableListPosition.CHILD) {
- return mExpandableListAdapter.getChild(posMetadata.position.groupPos,
+ retValue = mExpandableListAdapter.getChild(posMetadata.position.groupPos,
posMetadata.position.childPos);
} else {
// TODO: clean exit
throw new RuntimeException("Flat list position is of unknown type");
}
+
+ posMetadata.recycle();
+
+ return retValue;
}
public long getItemId(int flatListPos) {
final PositionMetadata posMetadata = getUnflattenedPos(flatListPos);
final long groupId = mExpandableListAdapter.getGroupId(posMetadata.position.groupPos);
+ long retValue;
if (posMetadata.position.type == ExpandableListPosition.GROUP) {
- return mExpandableListAdapter.getCombinedGroupId(groupId);
+ retValue = mExpandableListAdapter.getCombinedGroupId(groupId);
} else if (posMetadata.position.type == ExpandableListPosition.CHILD) {
final long childId = mExpandableListAdapter.getChildId(posMetadata.position.groupPos,
posMetadata.position.childPos);
- return mExpandableListAdapter.getCombinedChildId(groupId, childId);
+ retValue = mExpandableListAdapter.getCombinedChildId(groupId, childId);
} else {
// TODO: clean exit
throw new RuntimeException("Flat list position is of unknown type");
}
+
+ posMetadata.recycle();
+
+ return retValue;
}
public View getView(int flatListPos, View convertView, ViewGroup parent) {
final PositionMetadata posMetadata = getUnflattenedPos(flatListPos);
+ View retValue;
if (posMetadata.position.type == ExpandableListPosition.GROUP) {
- return mExpandableListAdapter.getGroupView(posMetadata.position.groupPos, posMetadata
+ retValue = mExpandableListAdapter.getGroupView(posMetadata.position.groupPos, posMetadata
.isExpanded(), convertView, parent);
} else if (posMetadata.position.type == ExpandableListPosition.CHILD) {
final boolean isLastChild = posMetadata.groupMetadata.lastChildFlPos == flatListPos;
- final View view = mExpandableListAdapter.getChildView(posMetadata.position.groupPos,
+ retValue = mExpandableListAdapter.getChildView(posMetadata.position.groupPos,
posMetadata.position.childPos, isLastChild, convertView, parent);
-
- return view;
} else {
// TODO: clean exit
throw new RuntimeException("Flat list position is of unknown type");
}
+
+ posMetadata.recycle();
+
+ return retValue;
}
@Override
public int getItemViewType(int flatListPos) {
final ExpandableListPosition pos = getUnflattenedPos(flatListPos).position;
+ int retValue;
if (pos.type == ExpandableListPosition.GROUP) {
- return 0;
+ retValue = 0;
} else {
- return 1;
+ retValue = 1;
}
+
+ pos.recycle();
+
+ return retValue;
}
@Override
@@ -464,22 +491,51 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
* positions.
*
* @param forceChildrenCountRefresh Forces refreshing of the children count
- * for all expanded groups.
+ * for all expanded groups.
+ * @param syncGroupPositions Whether to search for the group positions
+ * based on the group IDs. This should only be needed when calling
+ * this from an onChanged callback.
*/
- private void refreshExpGroupMetadataList(boolean forceChildrenCountRefresh) {
+ @SuppressWarnings("unchecked")
+ private void refreshExpGroupMetadataList(boolean forceChildrenCountRefresh,
+ boolean syncGroupPositions) {
final ArrayList<GroupMetadata> egml = mExpGroupMetadataList;
- final int egmlSize = egml.size();
+ int egmlSize = egml.size();
int curFlPos = 0;
/* Update child count as we go through */
mTotalExpChildrenCount = 0;
- GroupMetadata curGm;
+ if (syncGroupPositions) {
+ // We need to check whether any groups have moved positions
+ boolean positionsChanged = false;
+
+ for (int i = egmlSize - 1; i >= 0; i--) {
+ GroupMetadata curGm = egml.get(i);
+ int newGPos = findGroupPosition(curGm.gId, curGm.gPos);
+ if (newGPos != curGm.gPos) {
+ if (newGPos == AdapterView.INVALID_POSITION) {
+ // Doh, just remove it from the list of expanded groups
+ egml.remove(i);
+ egmlSize--;
+ }
+
+ curGm.gPos = newGPos;
+ if (!positionsChanged) positionsChanged = true;
+ }
+ }
+
+ if (positionsChanged) {
+ // At least one group changed positions, so re-sort
+ Collections.sort(egml);
+ }
+ }
+
int gChildrenCount;
int lastGPos = 0;
for (int i = 0; i < egmlSize; i++) {
/* Store in local variable since we'll access freq */
- curGm = egml.get(i);
+ GroupMetadata curGm = egml.get(i);
/*
* Get the number of children, try to refrain from calling
@@ -518,8 +574,13 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
* @param groupPos position of the group to collapse
*/
boolean collapseGroup(int groupPos) {
- return collapseGroup(getFlattenedPos(new ExpandableListPosition(ExpandableListPosition.GROUP,
- groupPos, -1, -1)));
+ PositionMetadata pm = getFlattenedPos(ExpandableListPosition.obtain(
+ ExpandableListPosition.GROUP, groupPos, -1, -1));
+ if (pm == null) return false;
+
+ boolean retValue = collapseGroup(pm);
+ pm.recycle();
+ return retValue;
}
boolean collapseGroup(PositionMetadata posMetadata) {
@@ -538,7 +599,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
mExpGroupMetadataList.remove(posMetadata.groupMetadata);
// Refresh the metadata
- refreshExpGroupMetadataList(false);
+ refreshExpGroupMetadataList(false, false);
// Notify of change
notifyDataSetChanged();
@@ -554,8 +615,11 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
* @param groupPos the group to be expanded
*/
boolean expandGroup(int groupPos) {
- return expandGroup(getFlattenedPos(new ExpandableListPosition(ExpandableListPosition.GROUP,
- groupPos, -1, -1)));
+ PositionMetadata pm = getFlattenedPos(ExpandableListPosition.obtain(
+ ExpandableListPosition.GROUP, groupPos, -1, -1));
+ boolean retValue = expandGroup(pm);
+ pm.recycle();
+ return retValue;
}
boolean expandGroup(PositionMetadata posMetadata) {
@@ -590,16 +654,16 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
}
}
- GroupMetadata expandedGm = new GroupMetadata();
-
- expandedGm.gPos = posMetadata.position.groupPos;
- expandedGm.flPos = GroupMetadata.REFRESH;
- expandedGm.lastChildFlPos = GroupMetadata.REFRESH;
+ GroupMetadata expandedGm = GroupMetadata.obtain(
+ GroupMetadata.REFRESH,
+ GroupMetadata.REFRESH,
+ posMetadata.position.groupPos,
+ mExpandableListAdapter.getGroupId(posMetadata.position.groupPos));
mExpGroupMetadataList.add(posMetadata.groupInsertIndex, expandedGm);
// Refresh the metadata
- refreshExpGroupMetadataList(false);
+ refreshExpGroupMetadataList(false, false);
// Notify of change
notifyDataSetChanged();
@@ -669,7 +733,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
}
mExpGroupMetadataList = expandedGroupMetadataList;
- refreshExpGroupMetadataList(true);
+ refreshExpGroupMetadataList(true, false);
}
@Override
@@ -678,17 +742,104 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
return adapter != null ? adapter.isEmpty() : true;
}
+ /**
+ * Searches the expandable list adapter for a group position matching the
+ * given group ID. The search starts at the given seed position and then
+ * alternates between moving up and moving down until 1) we find the right
+ * position, or 2) we run out of time, or 3) we have looked at every
+ * position
+ *
+ * @return Position of the row that matches the given row ID, or
+ * {@link AdapterView#INVALID_POSITION} if it can't be found
+ * @see AdapterView#findSyncPosition()
+ */
+ int findGroupPosition(long groupIdToMatch, int seedGroupPosition) {
+ int count = mExpandableListAdapter.getGroupCount();
+
+ if (count == 0) {
+ return AdapterView.INVALID_POSITION;
+ }
+
+ // If there isn't a selection don't hunt for it
+ if (groupIdToMatch == AdapterView.INVALID_ROW_ID) {
+ return AdapterView.INVALID_POSITION;
+ }
+
+ // Pin seed to reasonable values
+ seedGroupPosition = Math.max(0, seedGroupPosition);
+ seedGroupPosition = Math.min(count - 1, seedGroupPosition);
+
+ long endTime = SystemClock.uptimeMillis() + AdapterView.SYNC_MAX_DURATION_MILLIS;
+
+ long rowId;
+
+ // first position scanned so far
+ int first = seedGroupPosition;
+
+ // last position scanned so far
+ int last = seedGroupPosition;
+
+ // True if we should move down on the next iteration
+ boolean next = false;
+
+ // True when we have looked at the first item in the data
+ boolean hitFirst;
+
+ // True when we have looked at the last item in the data
+ boolean hitLast;
+
+ // Get the item ID locally (instead of getItemIdAtPosition), so
+ // we need the adapter
+ ExpandableListAdapter adapter = getAdapter();
+ if (adapter == null) {
+ return AdapterView.INVALID_POSITION;
+ }
+
+ while (SystemClock.uptimeMillis() <= endTime) {
+ rowId = adapter.getGroupId(seedGroupPosition);
+ if (rowId == groupIdToMatch) {
+ // Found it!
+ return seedGroupPosition;
+ }
+
+ hitLast = last == count - 1;
+ hitFirst = first == 0;
+
+ if (hitLast && hitFirst) {
+ // Looked at everything
+ break;
+ }
+
+ if (hitFirst || (next && !hitLast)) {
+ // Either we hit the top, or we are trying to move down
+ last++;
+ seedGroupPosition = last;
+ // Try going up next time
+ next = false;
+ } else if (hitLast || (!next && !hitFirst)) {
+ // Either we hit the bottom, or we are trying to move up
+ first--;
+ seedGroupPosition = first;
+ // Try going down next time
+ next = true;
+ }
+
+ }
+
+ return AdapterView.INVALID_POSITION;
+ }
+
protected class MyDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
- refreshExpGroupMetadataList(true);
+ refreshExpGroupMetadataList(true, true);
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
- refreshExpGroupMetadataList(true);
+ refreshExpGroupMetadataList(true, true);
notifyDataSetInvalidated();
}
@@ -699,7 +850,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
* position to either a) group position for groups, or b) child position for
* children
*/
- static class GroupMetadata implements Parcelable {
+ static class GroupMetadata implements Parcelable, Comparable {
final static int REFRESH = -1;
/** This group's flat list position */
@@ -717,6 +868,31 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
* This group's group position
*/
int gPos;
+
+ /**
+ * This group's id
+ */
+ long gId;
+
+ private GroupMetadata() {
+ }
+
+ static GroupMetadata obtain(int flPos, int lastChildFlPos, int gPos, long gId) {
+ GroupMetadata gm = new GroupMetadata();
+ gm.flPos = flPos;
+ gm.lastChildFlPos = lastChildFlPos;
+ gm.gPos = gPos;
+ gm.gId = gId;
+ return gm;
+ }
+
+ public int compareTo(Object another) {
+ if (another == null || !(another instanceof GroupMetadata)) {
+ throw new ClassCastException();
+ }
+
+ return gPos - ((GroupMetadata) another).gPos;
+ }
public int describeContents() {
return 0;
@@ -726,16 +902,18 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
dest.writeInt(flPos);
dest.writeInt(lastChildFlPos);
dest.writeInt(gPos);
+ dest.writeLong(gId);
}
public static final Parcelable.Creator<GroupMetadata> CREATOR =
new Parcelable.Creator<GroupMetadata>() {
public GroupMetadata createFromParcel(Parcel in) {
- GroupMetadata gm = new GroupMetadata();
- gm.flPos = in.readInt();
- gm.lastChildFlPos = in.readInt();
- gm.gPos = in.readInt();
+ GroupMetadata gm = GroupMetadata.obtain(
+ in.readInt(),
+ in.readInt(),
+ in.readInt(),
+ in.readLong());
return gm;
}
@@ -752,6 +930,11 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
* where to insert into the flat list, etc.)
*/
static public class PositionMetadata {
+
+ private static final int MAX_POOL_SIZE = 5;
+ private static ArrayList<PositionMetadata> sPool =
+ new ArrayList<PositionMetadata>(MAX_POOL_SIZE);
+
/** Data type to hold the position and its type (child/group) */
public ExpandableListPosition position;
@@ -771,17 +954,46 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
*/
public int groupInsertIndex;
- public PositionMetadata(int flatListPos, int type, int groupPos,
- int childPos) {
- position = new ExpandableListPosition(type, groupPos, childPos, flatListPos);
+ private void resetState() {
+ position = null;
+ groupMetadata = null;
+ groupInsertIndex = 0;
+ }
+
+ /**
+ * Use {@link #obtain(int, int, int, int, GroupMetadata, int)}
+ */
+ private PositionMetadata() {
}
- protected PositionMetadata(int flatListPos, int type, int groupPos,
+ static PositionMetadata obtain(int flatListPos, int type, int groupPos,
int childPos, GroupMetadata groupMetadata, int groupInsertIndex) {
- position = new ExpandableListPosition(type, groupPos, childPos, flatListPos);
-
- this.groupMetadata = groupMetadata;
- this.groupInsertIndex = groupInsertIndex;
+ PositionMetadata pm = getRecycledOrCreate();
+ pm.position = ExpandableListPosition.obtain(type, groupPos, childPos, flatListPos);
+ pm.groupMetadata = groupMetadata;
+ pm.groupInsertIndex = groupInsertIndex;
+ return pm;
+ }
+
+ private static PositionMetadata getRecycledOrCreate() {
+ PositionMetadata pm;
+ synchronized (sPool) {
+ if (sPool.size() > 0) {
+ pm = sPool.remove(0);
+ } else {
+ return new PositionMetadata();
+ }
+ }
+ pm.resetState();
+ return pm;
+ }
+
+ public void recycle() {
+ synchronized (sPool) {
+ if (sPool.size() < MAX_POOL_SIZE) {
+ sPool.add(this);
+ }
+ }
}
/**
diff --git a/core/java/android/widget/ExpandableListPosition.java b/core/java/android/widget/ExpandableListPosition.java
index 71e970c..e8d6113 100644
--- a/core/java/android/widget/ExpandableListPosition.java
+++ b/core/java/android/widget/ExpandableListPosition.java
@@ -16,6 +16,8 @@
package android.widget;
+import java.util.ArrayList;
+
/**
* ExpandableListPosition can refer to either a group's position or a child's
* position. Referring to a child's position requires both a group position (the
@@ -24,6 +26,11 @@ package android.widget;
* {@link #obtainGroupPosition(int)}.
*/
class ExpandableListPosition {
+
+ private static final int MAX_POOL_SIZE = 5;
+ private static ArrayList<ExpandableListPosition> sPool =
+ new ArrayList<ExpandableListPosition>(MAX_POOL_SIZE);
+
/**
* This data type represents a child position
*/
@@ -56,21 +63,14 @@ class ExpandableListPosition {
*/
public int type;
- ExpandableListPosition(int type, int groupPos, int childPos, int flatListPos) {
- this.type = type;
- this.flatListPos = flatListPos;
- this.groupPos = groupPos;
- this.childPos = childPos;
+ private void resetState() {
+ groupPos = 0;
+ childPos = 0;
+ flatListPos = 0;
+ type = 0;
}
-
- /**
- * Used internally by the {@link #obtainChildPosition} and
- * {@link #obtainGroupPosition} methods to construct a new object.
- */
- private ExpandableListPosition(int type, int groupPos, int childPos) {
- this.type = type;
- this.groupPos = groupPos;
- this.childPos = childPos;
+
+ private ExpandableListPosition() {
}
long getPackedPosition() {
@@ -79,11 +79,11 @@ class ExpandableListPosition {
}
static ExpandableListPosition obtainGroupPosition(int groupPosition) {
- return new ExpandableListPosition(GROUP, groupPosition, 0);
+ return obtain(GROUP, groupPosition, 0, 0);
}
static ExpandableListPosition obtainChildPosition(int groupPosition, int childPosition) {
- return new ExpandableListPosition(CHILD, groupPosition, childPosition);
+ return obtain(CHILD, groupPosition, childPosition, 0);
}
static ExpandableListPosition obtainPosition(long packedPosition) {
@@ -91,12 +91,45 @@ class ExpandableListPosition {
return null;
}
- final int type = ExpandableListView.getPackedPositionType(packedPosition) ==
- ExpandableListView.PACKED_POSITION_TYPE_CHILD ? CHILD : GROUP;
-
- return new ExpandableListPosition(type, ExpandableListView
- .getPackedPositionGroup(packedPosition), ExpandableListView
- .getPackedPositionChild(packedPosition));
+ ExpandableListPosition elp = getRecycledOrCreate();
+ elp.groupPos = ExpandableListView.getPackedPositionGroup(packedPosition);
+ if (ExpandableListView.getPackedPositionType(packedPosition) ==
+ ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
+ elp.type = CHILD;
+ elp.childPos = ExpandableListView.getPackedPositionChild(packedPosition);
+ } else {
+ elp.type = GROUP;
+ }
+ return elp;
+ }
+
+ static ExpandableListPosition obtain(int type, int groupPos, int childPos, int flatListPos) {
+ ExpandableListPosition elp = getRecycledOrCreate();
+ elp.type = type;
+ elp.groupPos = groupPos;
+ elp.childPos = childPos;
+ elp.flatListPos = flatListPos;
+ return elp;
+ }
+
+ private static ExpandableListPosition getRecycledOrCreate() {
+ ExpandableListPosition elp;
+ synchronized (sPool) {
+ if (sPool.size() > 0) {
+ elp = sPool.remove(0);
+ } else {
+ return new ExpandableListPosition();
+ }
+ }
+ elp.resetState();
+ return elp;
}
+ public void recycle() {
+ synchronized (sPool) {
+ if (sPool.size() < MAX_POOL_SIZE) {
+ sPool.add(this);
+ }
+ }
+ }
}
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 138cace..3de561a 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -25,6 +25,7 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ColorDrawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
@@ -184,7 +185,8 @@ public class ExpandableListView extends ListView {
/** Drawable to be used as a divider when it is adjacent to any children */
private Drawable mChildDivider;
-
+ private boolean mClipChildDivider;
+
public ExpandableListView(Context context) {
this(context, null);
}
@@ -245,7 +247,7 @@ public class ExpandableListView extends ListView {
final int myB = mBottom;
- PositionMetadata pos;
+ PositionMetadata pos = null;
View item;
Drawable indicator;
int t, b;
@@ -297,28 +299,27 @@ public class ExpandableListView extends ListView {
lastItemType = pos.position.type;
}
- if (indicatorRect.left == indicatorRect.right) {
- // The left and right bounds are the same, so nothing will be drawn
- continue;
- }
-
- // Use item's full height + the divider height
- if (mStackFromBottom) {
- // See ListView#dispatchDraw
- indicatorRect.top = t - mDividerHeight;
- indicatorRect.bottom = b;
- } else {
- indicatorRect.top = t;
- indicatorRect.bottom = b + mDividerHeight;
+ if (indicatorRect.left != indicatorRect.right) {
+ // Use item's full height + the divider height
+ if (mStackFromBottom) {
+ // See ListView#dispatchDraw
+ indicatorRect.top = t - mDividerHeight;
+ indicatorRect.bottom = b;
+ } else {
+ indicatorRect.top = t;
+ indicatorRect.bottom = b + mDividerHeight;
+ }
+
+ // Get the indicator (with its state set to the item's state)
+ indicator = getIndicator(pos);
+ if (indicator != null) {
+ // Draw the indicator
+ indicator.setBounds(indicatorRect);
+ indicator.draw(canvas);
+ }
}
- // Get the indicator (with its state set to the item's state)
- indicator = getIndicator(pos);
- if (indicator == null) continue;
-
- // Draw the indicator
- indicator.setBounds(indicatorRect);
- indicator.draw(canvas);
+ pos.recycle();
}
if (clipToPadding) {
@@ -376,6 +377,7 @@ public class ExpandableListView extends ListView {
*/
public void setChildDivider(Drawable childDivider) {
mChildDivider = childDivider;
+ mClipChildDivider = childDivider != null && childDivider instanceof ColorDrawable;
}
@Override
@@ -387,14 +389,25 @@ public class ExpandableListView extends ListView {
if (flatListPosition >= 0) {
PositionMetadata pos = mConnector.getUnflattenedPos(flatListPosition);
// If this item is a child, or it is a non-empty group that is expanded
- if ((pos.position.type == ExpandableListPosition.CHILD)
- || (pos.isExpanded() &&
- pos.groupMetadata.lastChildFlPos != pos.groupMetadata.flPos)) {
+ if ((pos.position.type == ExpandableListPosition.CHILD) || (pos.isExpanded() &&
+ pos.groupMetadata.lastChildFlPos != pos.groupMetadata.flPos)) {
// These are the cases where we draw the child divider
- mChildDivider.setBounds(bounds);
- mChildDivider.draw(canvas);
+ final Drawable divider = mChildDivider;
+ final boolean clip = mClipChildDivider;
+ if (!clip) {
+ divider.setBounds(bounds);
+ } else {
+ canvas.save();
+ canvas.clipRect(bounds);
+ }
+ divider.draw(canvas);
+ if (clip) {
+ canvas.restore();
+ }
+ pos.recycle();
return;
}
+ pos.recycle();
}
// Otherwise draw the default divider
@@ -495,6 +508,7 @@ public class ExpandableListView extends ListView {
id = getChildOrGroupId(posMetadata.position);
+ boolean returnValue;
if (posMetadata.position.type == ExpandableListPosition.GROUP) {
/* It's a group, so handle collapsing/expanding */
@@ -513,6 +527,7 @@ public class ExpandableListView extends ListView {
if (mOnGroupClickListener != null) {
if (mOnGroupClickListener.onGroupClick(this, v,
posMetadata.position.groupPos, id)) {
+ posMetadata.recycle();
return true;
}
}
@@ -527,7 +542,7 @@ public class ExpandableListView extends ListView {
}
}
- return true;
+ returnValue = true;
} else {
/* It's a child, so pass on event */
if (mOnChildClickListener != null) {
@@ -536,8 +551,12 @@ public class ExpandableListView extends ListView {
posMetadata.position.childPos, id);
}
- return false;
+ returnValue = false;
}
+
+ posMetadata.recycle();
+
+ return returnValue;
}
/**
@@ -676,7 +695,10 @@ public class ExpandableListView extends ListView {
* in packed position representation.
*/
public long getExpandableListPosition(int flatListPosition) {
- return mConnector.getUnflattenedPos(flatListPosition).position.getPackedPosition();
+ PositionMetadata pm = mConnector.getUnflattenedPos(flatListPosition);
+ long packedPos = pm.position.getPackedPosition();
+ pm.recycle();
+ return packedPos;
}
/**
@@ -691,8 +713,11 @@ public class ExpandableListView extends ListView {
* @return The flat list position for the given child or group.
*/
public int getFlatListPosition(long packedPosition) {
- return mConnector.getFlattenedPos(ExpandableListPosition.obtainPosition(packedPosition)).
- position.flatListPos;
+ PositionMetadata pm = mConnector.getFlattenedPos(ExpandableListPosition
+ .obtainPosition(packedPosition));
+ int retValue = pm.position.flatListPos;
+ pm.recycle();
+ return retValue;
}
/**
@@ -737,8 +762,11 @@ public class ExpandableListView extends ListView {
*/
public void setSelectedGroup(int groupPosition) {
ExpandableListPosition elGroupPos = ExpandableListPosition
- .obtainGroupPosition(groupPosition);
- super.setSelection(mConnector.getFlattenedPos(elGroupPos).position.flatListPos);
+ .obtainGroupPosition(groupPosition);
+ PositionMetadata pm = mConnector.getFlattenedPos(elGroupPos);
+ elGroupPos.recycle();
+ super.setSelection(pm.position.flatListPos);
+ pm.recycle();
}
/**
@@ -775,6 +803,9 @@ public class ExpandableListView extends ListView {
super.setSelection(flatChildPos.position.flatListPos);
+ elChildPos.recycle();
+ flatChildPos.recycle();
+
return true;
}
@@ -883,11 +914,15 @@ public class ExpandableListView extends ListView {
@Override
ContextMenuInfo createContextMenuInfo(View view, int flatListPosition, long id) {
- ExpandableListPosition pos = mConnector.getUnflattenedPos(flatListPosition).position;
+ PositionMetadata pm = mConnector.getUnflattenedPos(flatListPosition);
+ ExpandableListPosition pos = pm.position;
+ pm.recycle();
id = getChildOrGroupId(pos);
+ long packedPosition = pos.getPackedPosition();
+ pos.recycle();
- return new ExpandableListContextMenuInfo(view, pos.getPackedPosition(), id);
+ return new ExpandableListContextMenuInfo(view, packedPosition, id);
}
/**
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
new file mode 100644
index 0000000..bdcfeef
--- /dev/null
+++ b/core/java/android/widget/FastScroller.java
@@ -0,0 +1,471 @@
+/*
+ * 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.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+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;
+
+/**
+ * Helper class for AbsListView to draw and control the Fast Scroll thumb
+ */
+class FastScroller {
+
+
+ // Scroll thumb not showing
+ private static final int STATE_NONE = 0;
+ // Not implemented yet - fade-in transition
+ private static final int STATE_ENTER = 1;
+ // Scroll thumb visible and moving along with the scrollbar
+ private static final int STATE_VISIBLE = 2;
+ // Scroll thumb being dragged by user
+ private static final int STATE_DRAGGING = 3;
+ // Scroll thumb fading out due to inactivity timeout
+ private static final int STATE_EXIT = 4;
+
+ private Drawable mThumbDrawable;
+ private Drawable mOverlayDrawable;
+
+ private int mThumbH;
+ private int mThumbW;
+ private int mThumbY;
+
+ private RectF mOverlayPos;
+ private int mOverlaySize = 104;
+
+ private AbsListView mList;
+ private boolean mScrollCompleted;
+ private int mVisibleItem;
+ private Paint mPaint;
+ private int mListOffset;
+
+ private Object [] mSections;
+ private String mSectionText;
+ private boolean mDrawOverlay;
+ private ScrollFade mScrollFade;
+
+ private int mState;
+
+ private Handler mHandler = new Handler();
+
+ private BaseAdapter mListAdapter;
+ private SectionIndexer mSectionIndexer;
+
+ private boolean mChangedBounds;
+
+ public FastScroller(Context context, AbsListView listView) {
+ mList = listView;
+ init(context);
+ }
+
+ public void setState(int state) {
+ switch (state) {
+ case STATE_NONE:
+ mHandler.removeCallbacks(mScrollFade);
+ mList.invalidate();
+ break;
+ case STATE_VISIBLE:
+ if (mState != STATE_VISIBLE) { // Optimization
+ resetThumbPos();
+ }
+ // Fall through
+ case STATE_DRAGGING:
+ mHandler.removeCallbacks(mScrollFade);
+ break;
+ case STATE_EXIT:
+ int viewWidth = mList.getWidth();
+ mList.invalidate(viewWidth - mThumbW, mThumbY, viewWidth, mThumbY + mThumbH);
+ break;
+ }
+ mState = state;
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ private void resetThumbPos() {
+ final int viewWidth = mList.getWidth();
+ // Bounds are always top right. Y coordinate get's translated during draw
+ mThumbDrawable.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH);
+ mThumbDrawable.setAlpha(ScrollFade.ALPHA_MAX);
+ }
+
+ private void useThumbDrawable(Drawable drawable) {
+ mThumbDrawable = drawable;
+ mThumbW = 64; //mCurrentThumb.getIntrinsicWidth();
+ mThumbH = 52; //mCurrentThumb.getIntrinsicHeight();
+ mChangedBounds = true;
+ }
+
+ private void init(Context context) {
+ // Get both the scrollbar states drawables
+ final Resources res = context.getResources();
+ useThumbDrawable(res.getDrawable(
+ com.android.internal.R.drawable.scrollbar_handle_accelerated_anim2));
+
+ mOverlayDrawable = res.getDrawable(
+ com.android.internal.R.drawable.menu_submenu_background);
+
+ mScrollCompleted = true;
+
+ getSections();
+
+ mOverlayPos = new RectF();
+ mScrollFade = new ScrollFade();
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setTextAlign(Paint.Align.CENTER);
+ mPaint.setTextSize(mOverlaySize / 2);
+ TypedArray ta = context.getTheme().obtainStyledAttributes(new int[] {
+ android.R.attr.textColorPrimary });
+ ColorStateList textColor = ta.getColorStateList(ta.getIndex(0));
+ int textColorNormal = textColor.getDefaultColor();
+ mPaint.setColor(textColorNormal);
+ mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+ mState = STATE_NONE;
+ }
+
+ void stop() {
+ setState(STATE_NONE);
+ }
+
+ public void draw(Canvas canvas) {
+
+ if (mState == STATE_NONE) {
+ // No need to draw anything
+ return;
+ }
+
+ final int y = mThumbY;
+ final int viewWidth = mList.getWidth();
+ final FastScroller.ScrollFade scrollFade = mScrollFade;
+
+ int alpha = -1;
+ if (mState == STATE_EXIT) {
+ alpha = scrollFade.getAlpha();
+ if (alpha < ScrollFade.ALPHA_MAX / 2) {
+ mThumbDrawable.setAlpha(alpha * 2);
+ }
+ int left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX;
+ mThumbDrawable.setBounds(left, 0, viewWidth, mThumbH);
+ mChangedBounds = true;
+ }
+
+ canvas.translate(0, y);
+ mThumbDrawable.draw(canvas);
+ canvas.translate(0, -y);
+
+ // If user is dragging the scroll bar, draw the alphabet overlay
+ if (mState == STATE_DRAGGING && mDrawOverlay) {
+ mOverlayDrawable.draw(canvas);
+ final Paint paint = mPaint;
+ float descent = paint.descent();
+ final RectF rectF = mOverlayPos;
+ canvas.drawText(mSectionText, (int) (rectF.left + rectF.right) / 2,
+ (int) (rectF.bottom + rectF.top) / 2 + mOverlaySize / 4 - descent, paint);
+ } else if (mState == STATE_EXIT) {
+ if (alpha == 0) { // Done with exit
+ setState(STATE_NONE);
+ } else {
+ mList.invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);
+ }
+ }
+ }
+
+ void onSizeChanged(int w, int h, int oldw, int oldh) {
+ if (mThumbDrawable != null) {
+ mThumbDrawable.setBounds(w - mThumbW, 0, w, mThumbH);
+ }
+ final RectF pos = mOverlayPos;
+ pos.left = (w - mOverlaySize) / 2;
+ pos.right = pos.left + mOverlaySize;
+ pos.top = h / 10; // 10% from top
+ pos.bottom = pos.top + mOverlaySize;
+ if (mOverlayDrawable != null) {
+ mOverlayDrawable.setBounds((int) pos.left, (int) pos.top,
+ (int) pos.right, (int) pos.bottom);
+ }
+ }
+
+ void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+
+ if (totalItemCount - visibleItemCount > 0 && mState != STATE_DRAGGING ) {
+ mThumbY = ((mList.getHeight() - mThumbH) * firstVisibleItem)
+ / (totalItemCount - visibleItemCount);
+ if (mChangedBounds) {
+ resetThumbPos();
+ mChangedBounds = false;
+ }
+ }
+ mScrollCompleted = true;
+ if (firstVisibleItem == mVisibleItem) {
+ return;
+ }
+ mVisibleItem = firstVisibleItem;
+ if (mState != STATE_DRAGGING) {
+ setState(STATE_VISIBLE);
+ mHandler.postDelayed(mScrollFade, 1500);
+ }
+ }
+
+ private void getSections() {
+ Adapter adapter = mList.getAdapter();
+ mSectionIndexer = null;
+ if (adapter instanceof HeaderViewListAdapter) {
+ mListOffset = ((HeaderViewListAdapter)adapter).getHeadersCount();
+ adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
+ }
+ if (adapter instanceof ExpandableListConnector) {
+ ExpandableListAdapter expAdapter = ((ExpandableListConnector)adapter).getAdapter();
+ if (expAdapter instanceof SectionIndexer) {
+ mSectionIndexer = (SectionIndexer) expAdapter;
+ mListAdapter = (BaseAdapter) adapter;
+ mSections = mSectionIndexer.getSections();
+ }
+ } else {
+ if (adapter instanceof SectionIndexer) {
+ mListAdapter = (BaseAdapter) adapter;
+ mSectionIndexer = (SectionIndexer) adapter;
+ mSections = mSectionIndexer.getSections();
+
+ } else {
+ mListAdapter = (BaseAdapter) adapter;
+ mSections = new String[] { " " };
+ }
+ }
+ }
+
+ private void scrollTo(float position) {
+ int count = mList.getCount();
+ mScrollCompleted = false;
+ float fThreshold = (1.0f / count) / 8;
+ final Object[] sections = mSections;
+ int sectionIndex;
+ if (sections != null && sections.length > 1) {
+ final int nSections = sections.length;
+ int section = (int) (position * nSections);
+ if (section >= nSections) {
+ section = nSections - 1;
+ }
+ int exactSection = section;
+ sectionIndex = section;
+ int index = mSectionIndexer.getPositionForSection(section);
+ // Given the expected section and index, the following code will
+ // try to account for missing sections (no names starting with..)
+ // It will compute the scroll space of surrounding empty sections
+ // and interpolate the currently visible letter's range across the
+ // available space, so that there is always some list movement while
+ // the user moves the thumb.
+ int nextIndex = count;
+ int prevIndex = index;
+ int prevSection = section;
+ int nextSection = section + 1;
+ // Assume the next section is unique
+ if (section < nSections - 1) {
+ nextIndex = mSectionIndexer.getPositionForSection(section + 1);
+ }
+
+ // Find the previous index if we're slicing the previous section
+ if (nextIndex == index) {
+ // Non-existent letter
+ while (section > 0) {
+ section--;
+ prevIndex = mSectionIndexer.getPositionForSection(section);
+ if (prevIndex != index) {
+ prevSection = section;
+ sectionIndex = section;
+ break;
+ }
+ }
+ }
+ // Find the next index, in case the assumed next index is not
+ // unique. For instance, if there is no P, then request for P's
+ // position actually returns Q's. So we need to look ahead to make
+ // sure that there is really a Q at Q's position. If not, move
+ // further down...
+ int nextNextSection = nextSection + 1;
+ while (nextNextSection < nSections &&
+ mSectionIndexer.getPositionForSection(nextNextSection) == nextIndex) {
+ nextNextSection++;
+ nextSection++;
+ }
+ // Compute the beginning and ending scroll range percentage of the
+ // currently visible letter. This could be equal to or greater than
+ // (1 / nSections).
+ float fPrev = (float) prevSection / nSections;
+ float fNext = (float) nextSection / nSections;
+ if (prevSection == exactSection && position - fPrev < fThreshold) {
+ index = prevIndex;
+ } else {
+ index = prevIndex + (int) ((nextIndex - prevIndex) * (position - fPrev)
+ / (fNext - fPrev));
+ }
+ // Don't overflow
+ if (index > count - 1) index = count - 1;
+
+ if (mList instanceof ExpandableListView) {
+ ExpandableListView expList = (ExpandableListView) mList;
+ expList.setSelectionFromTop(expList.getFlatListPosition(
+ ExpandableListView.getPackedPositionForGroup(index + mListOffset)), 0);
+ } else if (mList instanceof ListView) {
+ ((ListView)mList).setSelectionFromTop(index + mListOffset, 0);
+ } else {
+ mList.setSelection(index + mListOffset);
+ }
+ } else {
+ int index = (int) (position * count);
+ if (mList instanceof ExpandableListView) {
+ ExpandableListView expList = (ExpandableListView) mList;
+ expList.setSelectionFromTop(expList.getFlatListPosition(
+ ExpandableListView.getPackedPositionForGroup(index + mListOffset)), 0);
+ } else if (mList instanceof ListView) {
+ ((ListView)mList).setSelectionFromTop(index + mListOffset, 0);
+ } else {
+ mList.setSelection(index + mListOffset);
+ }
+ sectionIndex = -1;
+ }
+
+ if (sectionIndex >= 0) {
+ String text = mSectionText = sections[sectionIndex].toString();
+ mDrawOverlay = (text.length() != 1 || text.charAt(0) != ' ') &&
+ sectionIndex < sections.length;
+ } else {
+ mDrawOverlay = false;
+ }
+ }
+
+ private void cancelFling() {
+ // Cancel the list fling
+ MotionEvent cancelFling = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+ mList.onTouchEvent(cancelFling);
+ cancelFling.recycle();
+ }
+
+ boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mState > STATE_NONE && ev.getAction() == MotionEvent.ACTION_DOWN) {
+ if (ev.getX() > mList.getWidth() - mThumbW && ev.getY() >= mThumbY &&
+ ev.getY() <= mThumbY + mThumbH) {
+ setState(STATE_DRAGGING);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean onTouchEvent(MotionEvent me) {
+ if (mState == STATE_NONE) {
+ return false;
+ }
+ if (me.getAction() == MotionEvent.ACTION_DOWN) {
+ if (me.getX() > mList.getWidth() - mThumbW
+ && me.getY() >= mThumbY
+ && me.getY() <= mThumbY + mThumbH) {
+
+ setState(STATE_DRAGGING);
+ if (mListAdapter == null && mList != null) {
+ getSections();
+ }
+
+ cancelFling();
+ return true;
+ }
+ } else if (me.getAction() == MotionEvent.ACTION_UP) {
+ if (mState == STATE_DRAGGING) {
+ setState(STATE_VISIBLE);
+ final Handler handler = mHandler;
+ handler.removeCallbacks(mScrollFade);
+ handler.postDelayed(mScrollFade, 1000);
+ return true;
+ }
+ } else if (me.getAction() == MotionEvent.ACTION_MOVE) {
+ if (mState == STATE_DRAGGING) {
+ final int viewHeight = mList.getHeight();
+ // Jitter
+ int newThumbY = (int) me.getY() - mThumbH + 10;
+ if (newThumbY < 0) {
+ newThumbY = 0;
+ } else if (newThumbY + mThumbH > viewHeight) {
+ newThumbY = viewHeight - mThumbH;
+ }
+ if (Math.abs(mThumbY - newThumbY) < 2) {
+ return true;
+ }
+ mThumbY = newThumbY;
+ // If the previous scrollTo is still pending
+ if (mScrollCompleted) {
+ scrollTo((float) mThumbY / (viewHeight - mThumbH));
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public class ScrollFade implements Runnable {
+
+ long mStartTime;
+ long mFadeDuration;
+ static final int ALPHA_MAX = 208;
+ static final long FADE_DURATION = 200;
+
+ void startFade() {
+ mFadeDuration = FADE_DURATION;
+ mStartTime = SystemClock.uptimeMillis();
+ setState(STATE_EXIT);
+ }
+
+ int getAlpha() {
+ if (getState() != STATE_EXIT) {
+ return ALPHA_MAX;
+ }
+ int alpha;
+ long now = SystemClock.uptimeMillis();
+ if (now > mStartTime + mFadeDuration) {
+ alpha = 0;
+ } else {
+ alpha = (int) (ALPHA_MAX - ((now - mStartTime) * ALPHA_MAX) / mFadeDuration);
+ }
+ return alpha;
+ }
+
+ public void run() {
+ if (getState() != STATE_EXIT) {
+ startFade();
+ return;
+ }
+
+ if (getAlpha() > 0) {
+ mList.invalidate();
+ } else {
+ setState(STATE_NONE);
+ }
+ }
+ }
+}
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index acf9400..d886155 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -122,7 +122,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
* Whether to continuously callback on the item selected listener during a
* fling.
*/
- private boolean mShouldCallbackDuringFling;
+ private boolean mShouldCallbackDuringFling = true;
/**
* Whether to callback when an item that is not selected is clicked.
@@ -133,6 +133,13 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
* If true, do not callback to item selected listener.
*/
private boolean mSuppressSelectionChanged;
+
+ /**
+ * If true, we have received the "invoke" (center or enter buttons) key
+ * down. This is checked before we action on the "invoke" key up, and is
+ * subsequently cleared.
+ */
+ private boolean mReceivedInvokeKeyDown;
private AdapterContextMenuInfo mContextMenuInfo;
@@ -882,8 +889,8 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
*/
mParent.requestDisallowInterceptTouchEvent(true);
- // As the user scrolls, we want to callback selection changes so related
- // into on the screen is up-to-date with the user's selection
+ // As the user scrolls, we want to callback selection changes so related-
+ // info on the screen is up-to-date with the gallery's selection
if (mSuppressSelectionChanged) {
mSuppressSelectionChanged = false;
}
@@ -1062,6 +1069,11 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT);
}
return true;
+
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_ENTER:
+ mReceivedInvokeKeyDown = true;
+ // fallthrough to default handling
}
return super.onKeyDown(keyCode, event);
@@ -1072,19 +1084,26 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER: {
- if (mItemCount > 0) {
-
- dispatchPress(mSelectedChild);
- postDelayed(new Runnable() {
- public void run() {
- dispatchUnpress();
- }
- }, ViewConfiguration.getPressedStateDuration());
-
- int selectedIndex = mSelectedPosition - mFirstPosition;
- performItemClick(getChildAt(selectedIndex), mSelectedPosition, mAdapter
- .getItemId(mSelectedPosition));
+
+ if (mReceivedInvokeKeyDown) {
+ if (mItemCount > 0) {
+
+ dispatchPress(mSelectedChild);
+ postDelayed(new Runnable() {
+ public void run() {
+ dispatchUnpress();
+ }
+ }, ViewConfiguration.getPressedStateDuration());
+
+ int selectedIndex = mSelectedPosition - mFirstPosition;
+ performItemClick(getChildAt(selectedIndex), mSelectedPosition, mAdapter
+ .getItemId(mSelectedPosition));
+ }
}
+
+ // Clear the flag
+ mReceivedInvokeKeyDown = false;
+
return true;
}
}
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 268bf84..38bfc7c 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -36,7 +36,8 @@ public class GridView extends AbsListView {
public static final int NO_STRETCH = 0;
public static final int STRETCH_SPACING = 1;
public static final int STRETCH_COLUMN_WIDTH = 2;
-
+ public static final int STRETCH_SPACING_UNIFORM = 3;
+
public static final int AUTO_FIT = -1;
private int mNumColumns = AUTO_FIT;
@@ -228,12 +229,12 @@ public class GridView extends AbsListView {
}
private View makeRow(int startPos, int y, boolean flow) {
- int last;
- int nextLeft = mListPadding.left;
-
final int columnWidth = mColumnWidth;
final int horizontalSpacing = mHorizontalSpacing;
+ int last;
+ int nextLeft = mListPadding.left + ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0);
+
if (!mStackFromBottom) {
last = Math.min(startPos + mNumColumns, mItemCount);
} else {
@@ -878,6 +879,17 @@ public class GridView extends AbsListView {
mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
}
break;
+
+ case STRETCH_SPACING_UNIFORM:
+ // Stretch the spacing between columns
+ mColumnWidth = requestedColumnWidth;
+ if (mNumColumns > 1) {
+ mHorizontalSpacing = requestedHorizontalSpacing +
+ spaceLeftOver / (mNumColumns + 1);
+ } else {
+ mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
+ }
+ break;
}
break;
@@ -1379,7 +1391,6 @@ public class GridView extends AbsListView {
handled = arrowScroll(FOCUS_LEFT);
break;
-
case KeyEvent.KEYCODE_DPAD_RIGHT:
handled = arrowScroll(FOCUS_RIGHT);
break;
@@ -1420,7 +1431,6 @@ public class GridView extends AbsListView {
}
break;
}
-
}
if (!handled) {
@@ -1460,6 +1470,7 @@ public class GridView extends AbsListView {
if (nextPage >= 0) {
setSelectionInt(nextPage);
+ invokeOnItemScrollListener();
return true;
}
@@ -1478,10 +1489,12 @@ public class GridView extends AbsListView {
if (direction == FOCUS_UP) {
mLayoutMode = LAYOUT_SET_SELECTION;
setSelectionInt(0);
+ invokeOnItemScrollListener();
moved = true;
} else if (direction == FOCUS_DOWN) {
mLayoutMode = LAYOUT_SET_SELECTION;
setSelectionInt(mItemCount - 1);
+ invokeOnItemScrollListener();
moved = true;
}
@@ -1547,6 +1560,7 @@ public class GridView extends AbsListView {
if (moved) {
playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
+ invokeOnItemScrollListener();
}
return moved;
@@ -1684,7 +1698,7 @@ public class GridView extends AbsListView {
* Control how items are stretched to fill their space.
*
* @param stretchMode Either {@link #NO_STRETCH},
- * {@link #STRETCH_SPACING}, or {@link #STRETCH_COLUMN_WIDTH}.
+ * {@link #STRETCH_SPACING}, {@link #STRETCH_SPACING_UNIFORM}, or {@link #STRETCH_COLUMN_WIDTH}.
*
* @attr ref android.R.styleable#GridView_stretchMode
*/
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index de74fa4..36ed8bd 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -952,12 +952,13 @@ public class LinearLayout extends ViewGroup {
if (majorGravity != Gravity.TOP) {
switch (majorGravity) {
case Gravity.BOTTOM:
- childTop = mBottom - mTop - mPaddingBottom - mTotalLength;
+ // mTotalLength contains the padding already, we add the top
+ // padding to compensate
+ childTop = mBottom - mTop + mPaddingTop - mTotalLength;
break;
case Gravity.CENTER_VERTICAL:
- childTop += ((mBottom - mTop - mPaddingTop - mPaddingBottom) -
- mTotalLength) / 2;
+ childTop += ((mBottom - mTop) - mTotalLength) / 2;
break;
}
@@ -1039,12 +1040,13 @@ public class LinearLayout extends ViewGroup {
if (majorGravity != Gravity.LEFT) {
switch (majorGravity) {
case Gravity.RIGHT:
- childLeft = mRight - mLeft - mPaddingRight - mTotalLength;
+ // mTotalLength contains the padding already, we add the left
+ // padding to compensate
+ childLeft = mRight - mLeft + mPaddingLeft - mTotalLength;
break;
case Gravity.CENTER_HORIZONTAL:
- childLeft += ((mRight - mLeft - mPaddingLeft - mPaddingRight) -
- mTotalLength) / 2;
+ childLeft += ((mRight - mLeft) - mTotalLength) / 2;
break;
}
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index d52e51f..dfc7bc3 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -21,6 +21,7 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ColorDrawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
@@ -36,6 +37,7 @@ import android.view.ViewParent;
import android.view.SoundEffectConstants;
import com.google.android.collect.Lists;
+import com.android.internal.R;
import java.util.ArrayList;
@@ -57,6 +59,8 @@ import java.util.ArrayList;
* @attr ref android.R.styleable#ListView_divider
* @attr ref android.R.styleable#ListView_dividerHeight
* @attr ref android.R.styleable#ListView_choiceMode
+ * @attr ref android.R.styleable#ListView_headerDividersEnabled
+ * @attr ref android.R.styleable#ListView_footerDividersEnabled
*/
public class ListView extends AbsListView {
/**
@@ -92,10 +96,16 @@ public class ListView extends AbsListView {
*/
private static final int MIN_SCROLL_PREVIEW_PIXELS = 2;
- // TODO: document
- class FixedViewInfo {
+ /**
+ * A class that represents a fixed view in a list, for example a header at the top
+ * or a footer at the bottom.
+ */
+ public class FixedViewInfo {
+ /** The view to add to the list */
public View view;
+ /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
public Object data;
+ /** <code>true</code> if the fixed view should be selectable in the list */
public boolean isSelectable;
}
@@ -104,6 +114,9 @@ public class ListView extends AbsListView {
Drawable mDivider;
int mDividerHeight;
+ private boolean mClipDivider;
+ private boolean mHeaderDividersEnabled;
+ private boolean mFooterDividersEnabled;
private boolean mAreAllItemsSelectable = true;
@@ -137,8 +150,8 @@ public class ListView extends AbsListView {
public ListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- TypedArray a =
- context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ListView, defStyle, 0);
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.ListView, defStyle, 0);
CharSequence[] entries = a.getTextArray(
com.android.internal.R.styleable.ListView_entries);
@@ -149,19 +162,20 @@ public class ListView extends AbsListView {
final Drawable d = a.getDrawable(com.android.internal.R.styleable.ListView_divider);
if (d != null) {
-
// If a divider is specified use its intrinsic height for divider height
setDivider(d);
- } else {
+ }
- // Else use the height specified, zero being the default
- final int dividerHeight = a.getDimensionPixelSize(
- com.android.internal.R.styleable.ListView_dividerHeight, 0);
- if (dividerHeight != 0) {
- setDividerHeight(dividerHeight);
- }
+ // Use the height specified, zero being the default
+ final int dividerHeight = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ListView_dividerHeight, 0);
+ if (dividerHeight != 0) {
+ setDividerHeight(dividerHeight);
}
+ mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
+ mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
+
a.recycle();
}
@@ -198,8 +212,7 @@ public class ListView extends AbsListView {
// We only are looking to see if we are too low, not too high
delta = 0;
}
- }
- else {
+ } else {
// we are too high, slide all views down to align with bottom
child = getChildAt(childCount - 1);
delta = child.getBottom() - (getHeight() - mListPadding.bottom);
@@ -1989,6 +2002,7 @@ public class ListView extends AbsListView {
}
setSelectionInt(position);
+ invokeOnItemScrollListener();
invalidate();
return true;
@@ -2014,6 +2028,7 @@ public class ListView extends AbsListView {
if (position >= 0) {
mLayoutMode = LAYOUT_FORCE_TOP;
setSelectionInt(position);
+ invokeOnItemScrollListener();
}
moved = true;
}
@@ -2023,6 +2038,7 @@ public class ListView extends AbsListView {
if (position >= 0) {
mLayoutMode = LAYOUT_FORCE_BOTTOM;
setSelectionInt(position);
+ invokeOnItemScrollListener();
}
moved = true;
}
@@ -2739,36 +2755,46 @@ public class ListView extends AbsListView {
bounds.right = mRight - mLeft - mPaddingRight;
final int count = getChildCount();
- int i;
+ final int headerCount = mHeaderViewInfos.size();
+ final int footerLimit = mItemCount - mFooterViewInfos.size() - 1;
+ final boolean headerDividers = mHeaderDividersEnabled;
+ final boolean footerDividers = mFooterDividersEnabled;
+ final int first = mFirstPosition;
- if (mStackFromBottom) {
- int top;
- int listTop = mListPadding.top;
-
- for (i = 0; i < count; ++i) {
- View child = getChildAt(i);
- top = child.getTop();
- if (top > listTop) {
- bounds.top = top - dividerHeight;
- bounds.bottom = top;
- // Give the method the child ABOVE the divider, so we
- // subtract one from our child
- // position. Give -1 when there is no child above the
- // divider.
- drawDivider(canvas, bounds, i - 1);
+ if (!mStackFromBottom) {
+ int bottom;
+ int listBottom = mBottom - mTop - mListPadding.bottom;
+
+ for (int i = 0; i < count; i++) {
+ if ((headerDividers || first + i >= headerCount) &&
+ (footerDividers || first + i < footerLimit)) {
+ View child = getChildAt(i);
+ bottom = child.getBottom();
+ if (bottom < listBottom) {
+ bounds.top = bottom;
+ bounds.bottom = bottom + dividerHeight;
+ drawDivider(canvas, bounds, i);
+ }
}
}
} else {
- int bottom;
- int listBottom = getHeight() - mListPadding.bottom;
-
- for (i = 0; i < count; ++i) {
- View child = getChildAt(i);
- bottom = child.getBottom();
- if (bottom < listBottom) {
- bounds.top = bottom;
- bounds.bottom = bottom + dividerHeight;
- drawDivider(canvas, bounds, i);
+ int top;
+ int listTop = mListPadding.top;
+
+ for (int i = 0; i < count; i++) {
+ if ((headerDividers || first + i >= headerCount) &&
+ (footerDividers || first + i < footerLimit)) {
+ View child = getChildAt(i);
+ top = child.getTop();
+ if (top > listTop) {
+ bounds.top = top - dividerHeight;
+ bounds.bottom = top;
+ // Give the method the child ABOVE the divider, so we
+ // subtract one from our child
+ // position. Give -1 when there is no child above the
+ // divider.
+ drawDivider(canvas, bounds, i - 1);
+ }
}
}
}
@@ -2789,8 +2815,21 @@ public class ListView extends AbsListView {
*/
void drawDivider(Canvas canvas, Rect bounds, int childIndex) {
// This widget draws the same divider for all children
- mDivider.setBounds(bounds);
- mDivider.draw(canvas);
+ final Drawable divider = mDivider;
+ final boolean clipDivider = mClipDivider;
+
+ if (!clipDivider) {
+ divider.setBounds(bounds);
+ } else {
+ canvas.save();
+ canvas.clipRect(bounds);
+ }
+
+ divider.draw(canvas);
+
+ if (clipDivider) {
+ canvas.restore();
+ }
}
/**
@@ -2811,8 +2850,10 @@ public class ListView extends AbsListView {
public void setDivider(Drawable divider) {
if (divider != null) {
mDividerHeight = divider.getIntrinsicHeight();
+ mClipDivider = divider instanceof ColorDrawable;
} else {
mDividerHeight = 0;
+ mClipDivider = false;
}
mDivider = divider;
requestLayoutIfNecessary();
@@ -2836,6 +2877,32 @@ public class ListView extends AbsListView {
requestLayoutIfNecessary();
}
+ /**
+ * Enables or disables the drawing of the divider for header views.
+ *
+ * @param headerDividersEnabled True to draw the headers, false otherwise.
+ *
+ * @see #setFooterDividersEnabled(boolean)
+ * @see #addHeaderView(android.view.View)
+ */
+ public void setHeaderDividersEnabled(boolean headerDividersEnabled) {
+ mHeaderDividersEnabled = headerDividersEnabled;
+ invalidate();
+ }
+
+ /**
+ * Enables or disables the drawing of the divider for footer views.
+ *
+ * @param footerDividersEnabled True to draw the footers, false otherwise.
+ *
+ * @see #setHeaderDividersEnabled(boolean)
+ * @see #addFooterView(android.view.View)
+ */
+ public void setFooterDividersEnabled(boolean footerDividersEnabled) {
+ mFooterDividersEnabled = footerDividersEnabled;
+ invalidate();
+ }
+
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index ad8433f..6c0c164 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -271,6 +271,7 @@ public class MediaController extends FrameLayout {
p.y = anchorpos[1] + mAnchor.getHeight() - p.height;
p.format = PixelFormat.TRANSLUCENT;
p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+ p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
p.token = null;
p.windowAnimations = 0; // android.R.style.DropDownAnimationDown;
mWindowManager.addView(mDecor, p);
@@ -387,6 +388,7 @@ public class MediaController extends FrameLayout {
int keyCode = event.getKeyCode();
if (event.getRepeatCount() == 0 && event.isDown() && (
keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
+ keyCode == KeyEvent.KEYCODE_PLAYPAUSE ||
keyCode == KeyEvent.KEYCODE_SPACE)) {
doPauseResume();
show(sDefaultTimeout);
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 6a7b1fb..b5c4384 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -22,9 +22,9 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
-import android.view.WindowManagerImpl;
import android.view.Gravity;
import android.view.ViewGroup;
+import android.view.View.OnTouchListener;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -43,31 +43,58 @@ import android.util.AttributeSet;
*/
public class PopupWindow {
/**
- * The height of the status bar so we know how much of the screen we can
- * actually be displayed in.
- * <p>
- * TODO: This IS NOT the right way to do this.
- * Instead of knowing how much of the screen is available, a popup that
- * wants anchor and maximize space shouldn't be setting a height, instead
- * the PopupViewContainer should have its layout height as fill_parent and
- * properly position the popup.
+ * Mode for {@link #setInputMethodMode(int): the requirements for the
+ * input method should be based on the focusability of the popup. That is
+ * if it is focusable than it needs to work with the input method, else
+ * it doesn't.
+ */
+ public static final int INPUT_METHOD_FROM_FOCUSABLE = 0;
+
+ /**
+ * Mode for {@link #setInputMethodMode(int): this popup always needs to
+ * work with an input method, regardless of whether it is focusable. This
+ * means that it will always be displayed so that the user can also operate
+ * the input method while it is shown.
*/
- private static final int STATUS_BAR_HEIGHT = 30;
+
+ public static final int INPUT_METHOD_NEEDED = 1;
+
+ /**
+ * Mode for {@link #setInputMethodMode(int): this popup never needs to
+ * work with an input method, regardless of whether it is focusable. This
+ * means that it will always be displayed to use as much space on the
+ * screen as needed, regardless of whether this covers the input method.
+ */
+ public static final int INPUT_METHOD_NOT_NEEDED = 2;
+
+ private final Context mContext;
+ private final WindowManager mWindowManager;
private boolean mIsShowing;
+ private boolean mIsDropdown;
private View mContentView;
private View mPopupView;
private boolean mFocusable;
+ private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE;
+ private boolean mTouchable = true;
+ private boolean mOutsideTouchable = false;
+ private boolean mClippingEnabled = true;
+ private OnTouchListener mTouchInterceptor;
+
+ private int mWidthMode;
private int mWidth;
+ private int mHeightMode;
private int mHeight;
+ private int mPopupWidth;
+ private int mPopupHeight;
+
private int[] mDrawingLocation = new int[2];
- private int[] mRootLocation = new int[2];
+ private int[] mScreenLocation = new int[2];
private Rect mTempRect = new Rect();
- private Context mContext;
private Drawable mBackground;
private boolean mAboveAnchor;
@@ -106,6 +133,8 @@ public class PopupWindow {
*/
public PopupWindow(Context context, AttributeSet attrs, int defStyle) {
mContext = context;
+ mWindowManager = (WindowManager)context.getSystemService(
+ Context.WINDOW_SERVICE);
TypedArray a =
context.obtainStyledAttributes(
@@ -183,6 +212,9 @@ public class PopupWindow {
*/
public PopupWindow(View contentView, int width, int height,
boolean focusable) {
+ mContext = contentView.getContext();
+ mWindowManager = (WindowManager)mContext.getSystemService(
+ Context.WINDOW_SERVICE);
setContentView(contentView);
setWidth(width);
setHeight(height);
@@ -218,11 +250,15 @@ public class PopupWindow {
}
/**
- * set the flag on popup to ignore cheek press events
- * This method has to be invoked before displaying the content view
- * of the popup for the window flags to take effect and will be ignored
- * if the pop up is already displayed. By default this flag is set to false
+ * Set the flag on popup to ignore cheek press eventt; by default this flag
+ * is set to false
* which means the pop wont ignore cheek press dispatch events.
+ *
+ * <p>If the popup is showing, calling this method will take effect only
+ * the next time the popup is shown or through a manual call to one of
+ * the {@link #update()} methods.</p>
+ *
+ * @see #update()
*/
public void setIgnoreCheekPress() {
mIgnoreCheekPress = true;
@@ -230,9 +266,17 @@ public class PopupWindow {
/**
- * <p>Change the animation style for this popup.</p>
+ * <p>Change the animation style resource for this popup.</p>
+ *
+ * <p>If the popup is showing, calling this method will take effect only
+ * the next time the popup is shown or through a manual call to one of
+ * the {@link #update()} methods.</p>
*
- * @param animationStyle animation style to use when the popup appears and disappears
+ * @param animationStyle animation style to use when the popup appears
+ * and disappears. Set to -1 for the default animation, 0 for no
+ * animation, or a resource identifier for an explicit animation.
+ *
+ * @see #update()
*/
public void setAnimationStyle(int animationStyle) {
mAnimationStyle = animationStyle;
@@ -253,7 +297,8 @@ public class PopupWindow {
* <p>Change the popup's content. The content is represented by an instance
* of {@link android.view.View}.</p>
*
- * <p>This method has no effect if called when the popup is showing.</p>
+ * <p>This method has no effect if called when the popup is showing. To
+ * apply it while a popup is showing, call </p>
*
* @param contentView the new content for the popup
*
@@ -269,6 +314,14 @@ public class PopupWindow {
}
/**
+ * Set a callback for all touch events being dispatched to the popup
+ * window.
+ */
+ public void setTouchInterceptor(OnTouchListener l) {
+ mTouchInterceptor = l;
+ }
+
+ /**
* <p>Indicate whether the popup window can grab the focus.</p>
*
* @return true if the popup is focusable, false otherwise
@@ -282,21 +335,169 @@ public class PopupWindow {
/**
* <p>Changes the focusability of the popup window. When focusable, the
* window will grab the focus from the current focused widget if the popup
- * contains a focusable {@link android.view.View}.</p>
+ * contains a focusable {@link android.view.View}. By default a popup
+ * window is not focusable.</p>
*
* <p>If the popup is showing, calling this method will take effect only
- * the next time the popup is shown.</p>
+ * the next time the popup is shown or through a manual call to one of
+ * the {@link #update()} methods.</p>
*
- * @param focusable true if the popup should grab focus, false otherwise
+ * @param focusable true if the popup should grab focus, false otherwise.
*
* @see #isFocusable()
* @see #isShowing()
+ * @see #update()
*/
public void setFocusable(boolean focusable) {
mFocusable = focusable;
}
/**
+ * Return the current value in {@link #setInputMethodMode(int)}.
+ *
+ * @see #setInputMethodMode(int)
+ */
+ public int getInputMethodMode() {
+ return mInputMethodMode;
+
+ }
+
+ /**
+ * Control how the popup operates with an input method: one of
+ * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
+ * or {@link #INPUT_METHOD_NOT_NEEDED}.
+ *
+ * <p>If the popup is showing, calling this method will take effect only
+ * the next time the popup is shown or through a manual call to one of
+ * the {@link #update()} methods.</p>
+ *
+ * @see #getInputMethodMode()
+ * @see #update()
+ */
+ public void setInputMethodMode(int mode) {
+ mInputMethodMode = mode;
+ }
+
+ /**
+ * <p>Indicates whether the popup window receives touch events.</p>
+ *
+ * @return true if the popup is touchable, false otherwise
+ *
+ * @see #setTouchable(boolean)
+ */
+ public boolean isTouchable() {
+ return mTouchable;
+ }
+
+ /**
+ * <p>Changes the touchability of the popup window. When touchable, the
+ * window will receive touch events, otherwise touch events will go to the
+ * window below it. By default the window is touchable.</p>
+ *
+ * <p>If the popup is showing, calling this method will take effect only
+ * the next time the popup is shown or through a manual call to one of
+ * the {@link #update()} methods.</p>
+ *
+ * @param touchable true if the popup should receive touch events, false otherwise
+ *
+ * @see #isTouchable()
+ * @see #isShowing()
+ * @see #update()
+ */
+ public void setTouchable(boolean touchable) {
+ mTouchable = touchable;
+ }
+
+ /**
+ * <p>Indicates whether the popup window will be informed of touch events
+ * outside of its window.</p>
+ *
+ * @return true if the popup is outside touchable, false otherwise
+ *
+ * @see #setOutsideTouchable(boolean)
+ */
+ public boolean isOutsideTouchable() {
+ return mOutsideTouchable;
+ }
+
+ /**
+ * <p>Controls whether the pop-up will be informed of touch events outside
+ * of its window. This only makes sense for pop-ups that are touchable
+ * but not focusable, which means touches outside of the window will
+ * be delivered to the window behind. The default is false.</p>
+ *
+ * <p>If the popup is showing, calling this method will take effect only
+ * the next time the popup is shown or through a manual call to one of
+ * the {@link #update()} methods.</p>
+ *
+ * @param touchable true if the popup should receive outside
+ * touch events, false otherwise
+ *
+ * @see #isOutsideTouchable()
+ * @see #isShowing()
+ * @see #update()
+ */
+ public void setOutsideTouchable(boolean touchable) {
+ mOutsideTouchable = touchable;
+ }
+
+ /**
+ * <p>Indicates whether clipping of the popup window is enabled.</p>
+ *
+ * @return true if the clipping is enabled, false otherwise
+ *
+ * @see #setClippingEnabled(boolean)
+ */
+ public boolean isClippingEnabled() {
+ return mClippingEnabled;
+ }
+
+ /**
+ * <p>Allows the popup window to extend beyond the bounds of the screen. By default the
+ * window is clipped to the screen boundaries. Setting this to false will allow windows to be
+ * accurately positioned.</p>
+ *
+ * <p>If the popup is showing, calling this method will take effect only
+ * the next time the popup is shown or through a manual call to one of
+ * the {@link #update()} methods.</p>
+ *
+ * @param enabled false if the window should be allowed to extend outside of the screen
+ * @see #isShowing()
+ * @see #isClippingEnabled()
+ * @see #update()
+ */
+ public void setClippingEnabled(boolean enabled) {
+ mClippingEnabled = enabled;
+ }
+
+ /**
+ * <p>Change the width and height measure specs that are given to the
+ * window manager by the popup. By default these are 0, meaning that
+ * the current width or height is requested as an explicit size from
+ * the window manager. You can supply
+ * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or
+ * {@link ViewGroup.LayoutParams#FILL_PARENT} to have that measure
+ * spec supplied instead, replacing the absolute width and height that
+ * has been set in the popup.</p>
+ *
+ * <p>If the popup is showing, calling this method will take effect only
+ * the next time the popup is shown.</p>
+ *
+ * @param widthSpec an explicit width measure spec mode, either
+ * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
+ * {@link ViewGroup.LayoutParams#FILL_PARENT}, or 0 to use the absolute
+ * width.
+ * @param heightSpec an explicit height measure spec mode, either
+ * {@link ViewGroup.LayoutParams#WRAP_CONTENT},
+ * {@link ViewGroup.LayoutParams#FILL_PARENT}, or 0 to use the absolute
+ * height.
+ */
+ public void setWindowLayoutMode(int widthSpec, int heightSpec) {
+ mWidthMode = widthSpec;
+ mHeightMode = heightSpec;
+ }
+
+ /**
* <p>Return this popup's height MeasureSpec</p>
*
* @return the height MeasureSpec of the popup
@@ -377,11 +578,10 @@ public class PopupWindow {
}
mIsShowing = true;
+ mIsDropdown = false;
WindowManager.LayoutParams p = createPopupLayout(parent.getWindowToken());
- if (mAnimationStyle != -1) {
- p.windowAnimations = mAnimationStyle;
- }
+ p.windowAnimations = computeAnimationResource();
preparePopup(p);
if (gravity == Gravity.NO_GRAVITY) {
@@ -426,6 +626,7 @@ public class PopupWindow {
}
mIsShowing = true;
+ mIsDropdown = true;
WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
preparePopup(p);
@@ -433,13 +634,9 @@ public class PopupWindow {
mPopupView.refreshDrawableState();
}
mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff);
- if (mAnimationStyle == -1) {
- p.windowAnimations = mAboveAnchor
- ? com.android.internal.R.style.Animation_DropDownUp
- : com.android.internal.R.style.Animation_DropDownDown;
- } else {
- p.windowAnimations = mAnimationStyle;
- }
+ if (mHeightMode < 0) p.height = mHeightMode;
+ if (mWidthMode < 0) p.width = mWidthMode;
+ p.windowAnimations = computeAnimationResource();
invokePopup(p);
}
@@ -479,7 +676,8 @@ public class PopupWindow {
} else {
mPopupView = mContentView;
}
-
+ mPopupWidth = p.width;
+ mPopupHeight = p.height;
}
/**
@@ -491,8 +689,7 @@ public class PopupWindow {
* @param p the layout parameters of the popup's content view
*/
private void invokePopup(WindowManager.LayoutParams p) {
- WindowManagerImpl wm = WindowManagerImpl.getDefault();
- wm.addView(mPopupView, p);
+ mWindowManager.addView(mPopupView, p);
}
/**
@@ -518,18 +715,56 @@ public class PopupWindow {
} else {
p.format = PixelFormat.TRANSLUCENT;
}
- if(mIgnoreCheekPress) {
- p.flags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
- }
- if (!mFocusable) {
- p.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- }
+ p.flags = computeFlags(p.flags);
p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
p.token = token;
return p;
}
+ private int computeFlags(int curFlags) {
+ curFlags &= ~(
+ WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ if(mIgnoreCheekPress) {
+ curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
+ }
+ if (!mFocusable) {
+ curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ if (mInputMethodMode == INPUT_METHOD_NEEDED) {
+ curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ }
+ } else if (mInputMethodMode == INPUT_METHOD_NOT_NEEDED) {
+ curFlags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ }
+ if (!mTouchable) {
+ curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ }
+ if (mTouchable) {
+ curFlags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+ }
+ if (!mClippingEnabled) {
+ curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+ }
+ return curFlags;
+ }
+
+ private int computeAnimationResource() {
+ if (mAnimationStyle == -1) {
+ if (mIsDropdown) {
+ return mAboveAnchor
+ ? com.android.internal.R.style.Animation_DropDownUp
+ : com.android.internal.R.style.Animation_DropDownDown;
+ }
+ return 0;
+ }
+ return mAnimationStyle;
+ }
+
/**
* <p>Positions the popup window on screen. When the popup window is too
* tall to fit under the anchor, a parent scroll view is seeked and scrolled
@@ -548,33 +783,48 @@ public class PopupWindow {
anchor.getLocationInWindow(mDrawingLocation);
p.x = mDrawingLocation[0] + xoff;
p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
-
+
boolean onTop = false;
- if (p.y + p.height > WindowManagerImpl.getDefault().getDefaultDisplay().getHeight()) {
+ p.gravity = Gravity.LEFT | Gravity.TOP;
+
+ anchor.getLocationOnScreen(mScreenLocation);
+ final Rect displayFrame = new Rect();
+ anchor.getWindowVisibleDisplayFrame(displayFrame);
+
+ final View root = anchor.getRootView();
+ if (mScreenLocation[1] + anchor.getMeasuredHeight() + yoff + 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
- View root = anchor.getRootView();
- root.getLocationInWindow(mRootLocation);
- int delta = p.y + p.height - mRootLocation[1] - root.getHeight();
-
- if (delta > 0 || p.x + p.width - mRootLocation[0] - root.getWidth() > 0) {
- Rect r = new Rect(anchor.getScrollX(), anchor.getScrollY(),
- p.width, p.height + anchor.getMeasuredHeight());
-
- onTop = !anchor.requestRectangleOnScreen(r, true);
-
- if (onTop) {
- p.y -= anchor.getMeasuredHeight() + p.height;
- } else {
- anchor.getLocationOnScreen(mDrawingLocation);
- p.x = mDrawingLocation[0] + xoff;
- p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
- }
+ int scrollX = anchor.getScrollX();
+ int scrollY = anchor.getScrollY();
+ Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth,
+ scrollY + mPopupHeight + anchor.getMeasuredHeight());
+ anchor.requestRectangleOnScreen(r, true);
+
+ // now we re-evaluate the space available, and decide from that
+ // whether the pop-up will go above or below the anchor.
+ anchor.getLocationInWindow(mDrawingLocation);
+ p.x = mDrawingLocation[0] + xoff;
+ p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
+
+ // 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);
+ if (onTop) {
+ p.gravity = Gravity.LEFT | Gravity.BOTTOM;
+ p.y = root.getHeight() - mDrawingLocation[1] - yoff;
+ } else {
+ p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
}
}
+ p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
+
return onTop;
}
@@ -589,18 +839,18 @@ public class PopupWindow {
* shown.
*/
public int getMaxAvailableHeight(View anchor) {
- // TODO: read comment on STATUS_BAR_HEIGHT
- final int screenHeight = WindowManagerImpl.getDefault().getDefaultDisplay().getHeight()
- - STATUS_BAR_HEIGHT;
+ final Rect displayFrame = new Rect();
+ anchor.getWindowVisibleDisplayFrame(displayFrame);
final int[] anchorPos = mDrawingLocation;
anchor.getLocationOnScreen(anchorPos);
- anchorPos[1] -= STATUS_BAR_HEIGHT;
-
- final int distanceFromAnchorToBottom = screenHeight - (anchorPos[1] + anchor.getHeight());
+ final int distanceToBottom = displayFrame.bottom
+ - (anchorPos[1] + anchor.getHeight());
+ final int distanceToTop = anchorPos[1] - displayFrame.top;
+
// anchorPos[1] is distance from anchor to top of screen
- int returnedHeight = Math.max(anchorPos[1], distanceFromAnchorToBottom);
+ int returnedHeight = Math.max(distanceToBottom, distanceToTop);
if (mBackground != null) {
mBackground.getPadding(mTempRect);
returnedHeight -= mTempRect.top + mTempRect.bottom;
@@ -618,11 +868,11 @@ public class PopupWindow {
*/
public void dismiss() {
if (isShowing() && mPopupView != null) {
- WindowManagerImpl wm = WindowManagerImpl.getDefault();
- wm.removeView(mPopupView);
+ mWindowManager.removeView(mPopupView);
if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
((ViewGroup) mPopupView).removeView(mContentView);
}
+ mPopupView = null;
mIsShowing = false;
if (mOnDismissListener != null) {
@@ -641,8 +891,44 @@ public class PopupWindow {
}
/**
+ * Updates the state of the popup window, if it is currently being displayed,
+ * from the currently set state. This include:
+ * {@link #setClippingEnabled(boolean)}, {@link #setFocusable(boolean)},
+ * {@link #setIgnoreCheekPress()}, {@link #setInputMethodMode(int)},
+ * {@link #setTouchable(boolean)}, and {@link #setAnimationStyle(int)}.
+ */
+ public void update() {
+ if (!isShowing() || mContentView == null) {
+ return;
+ }
+
+ WindowManager.LayoutParams p = (WindowManager.LayoutParams)
+ mPopupView.getLayoutParams();
+
+ boolean update = false;
+
+ final int newAnim = computeAnimationResource();
+ if (newAnim != p.windowAnimations) {
+ p.windowAnimations = newAnim;
+ update = true;
+ }
+
+ final int newFlags = computeFlags(p.flags);
+ if (newFlags != p.flags) {
+ p.flags = newFlags;
+ update = true;
+ }
+
+ if (update) {
+ mWindowManager.updateViewLayout(mPopupView, p);
+ }
+ }
+
+ /**
* <p>Updates the position and the dimension of the popup window. Width and
- * height can be set to -1 to update location only.</p>
+ * 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
@@ -667,13 +953,15 @@ public class PopupWindow {
boolean update = false;
- if (width != -1 && p.width != width) {
- p.width = width;
+ final int finalWidth = mWidthMode < 0 ? mWidthMode : p.width;
+ if (width != -1 && p.width != finalWidth) {
+ p.width = finalWidth;
update = true;
}
- if (height != -1 && p.height != height) {
- p.height = height;
+ final int finalHeight = mHeightMode < 0 ? mHeightMode : p.height;
+ if (height != -1 && p.height != finalHeight) {
+ p.height = finalHeight;
update = true;
}
@@ -687,6 +975,18 @@ public class PopupWindow {
update = true;
}
+ final int newAnim = computeAnimationResource();
+ if (newAnim != p.windowAnimations) {
+ p.windowAnimations = newAnim;
+ update = true;
+ }
+
+ final int newFlags = computeFlags(p.flags);
+ if (newFlags != p.flags) {
+ p.flags = newFlags;
+ update = true;
+ }
+
if (update) {
if (mPopupView != mContentView) {
final View popupViewContainer = mPopupView;
@@ -704,14 +1004,15 @@ public class PopupWindow {
}
}
- WindowManagerImpl wm = WindowManagerImpl.getDefault();
- wm.updateViewLayout(mPopupView, p);
+ mWindowManager.updateViewLayout(mPopupView, p);
}
}
/**
* <p>Updates the position and the dimension of the popup window. Width and
- * height can be set to -1 to update location only.</p>
+ * 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 anchor the popup's anchor view
* @param width the new width, can be -1 to ignore
@@ -723,7 +1024,9 @@ public class PopupWindow {
/**
* <p>Updates the position and the dimension of the popup window. Width and
- * height can be set to -1 to update location only.</p>
+ * 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 anchor the popup's anchor view
* @param xoff x offset from the view's left edge
@@ -739,17 +1042,25 @@ public class PopupWindow {
WindowManager.LayoutParams p = (WindowManager.LayoutParams)
mPopupView.getLayoutParams();
- int x = p.x;
- int y = p.y;
+ if (width == -1) {
+ width = mPopupWidth;
+ } else {
+ mPopupWidth = width;
+ }
+ if (height == -1) {
+ height = mPopupHeight;
+ } else {
+ mPopupHeight = height;
+ }
+
findDropDownPosition(anchor, p, xoff, yoff);
-
- update(x, y, width, height);
+ update(p.x, p.y, width, height);
}
/**
* Listener that is called when this popup window is dismissed.
*/
- interface OnDismissListener {
+ public interface OnDismissListener {
/**
* Called when this popup window is dismissed.
*/
@@ -785,6 +1096,14 @@ public class PopupWindow {
}
@Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
+ return true;
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
@@ -793,6 +1112,9 @@ public class PopupWindow {
&& ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
dismiss();
return true;
+ } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ dismiss();
+ return true;
} else {
return super.onTouchEvent(event);
}
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index c1de010..abba6d0 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -22,6 +22,7 @@ import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Shader;
+import android.graphics.Rect;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ClipDrawable;
@@ -40,6 +41,8 @@ import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import android.widget.RemoteViews.RemoteView;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.SystemClock;
import com.android.internal.R;
@@ -427,7 +430,7 @@ public class ProgressBar extends View {
Drawable getCurrentDrawable() {
return mCurrentDrawable;
}
-
+
@Override
protected boolean verifyDrawable(Drawable who) {
return who == mProgressDrawable || who == mIndeterminateDrawable
@@ -700,7 +703,7 @@ public class ProgressBar extends View {
mAnimation = null;
mTransformation = null;
if (mIndeterminateDrawable instanceof AnimationDrawable) {
- ((AnimationDrawable)mIndeterminateDrawable).stop();
+ ((AnimationDrawable) mIndeterminateDrawable).stop();
mShouldStartAnimationDrawable = false;
}
}
@@ -754,17 +757,31 @@ public class ProgressBar extends View {
@Override
public void invalidateDrawable(Drawable dr) {
if (!mInDrawing) {
- super.invalidateDrawable(dr);
+ if (dr == mProgressDrawable || dr == mIndeterminateDrawable) {
+ final Rect dirty = dr.getBounds();
+ final int scrollX = mScrollX + mPaddingLeft;
+ final int scrollY = mScrollY + mPaddingRight;
+
+ invalidate(dirty.left + scrollX, dirty.top + scrollY,
+ dirty.right + scrollX, dirty.bottom + scrollY);
+ } else {
+ super.invalidateDrawable(dr);
+ }
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- Drawable d = mCurrentDrawable;
- if (d != null) {
- // onDraw will translate the canvas so we draw starting at 0,0
- d.setBounds(0, 0, w - mPaddingRight - mPaddingLeft,
- h - mPaddingBottom - mPaddingTop);
+ // onDraw will translate the canvas so we draw starting at 0,0
+ int right = w - mPaddingRight - mPaddingLeft;
+ int bottom = h - mPaddingBottom - mPaddingTop;
+
+ if (mIndeterminateDrawable != null) {
+ mIndeterminateDrawable.setBounds(0, 0, right, bottom);
+ }
+
+ if (mProgressDrawable != null) {
+ mProgressDrawable.setBounds(0, 0, right, bottom);
}
}
@@ -795,8 +812,9 @@ public class ProgressBar extends View {
}
d.draw(canvas);
canvas.restore();
- if (mShouldStartAnimationDrawable && mCurrentDrawable instanceof AnimationDrawable) {
- ((AnimationDrawable)mCurrentDrawable).start();
+ if (mShouldStartAnimationDrawable && d instanceof AnimationDrawable) {
+ ((AnimationDrawable) d).start();
+ mShouldStartAnimationDrawable = false;
}
}
}
@@ -817,4 +835,64 @@ public class ProgressBar extends View {
setMeasuredDimension(resolveSize(dw, widthMeasureSpec),
resolveSize(dh, heightMeasureSpec));
}
+
+ static class SavedState extends BaseSavedState {
+ int progress;
+ int secondaryProgress;
+
+ /**
+ * Constructor called from {@link ProgressBar#onSaveInstanceState()}
+ */
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ /**
+ * Constructor called from {@link #CREATOR}
+ */
+ private SavedState(Parcel in) {
+ super(in);
+ progress = in.readInt();
+ secondaryProgress = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(progress);
+ out.writeInt(secondaryProgress);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR
+ = new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ // Force our ancestor class to save its state
+ Parcelable superState = super.onSaveInstanceState();
+ SavedState ss = new SavedState(superState);
+
+ ss.progress = mProgress;
+ ss.secondaryProgress = mSecondaryProgress;
+
+ return ss;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+
+ setProgress(ss.progress);
+ setSecondaryProgress(ss.secondaryProgress);
+ }
}
diff --git a/core/java/android/widget/ScrollBarDrawable.java b/core/java/android/widget/ScrollBarDrawable.java
index 5df2b6d..17f9128 100644
--- a/core/java/android/widget/ScrollBarDrawable.java
+++ b/core/java/android/widget/ScrollBarDrawable.java
@@ -111,7 +111,10 @@ public class ScrollBarDrawable extends Drawable {
}
Rect r = getBounds();
-
+ if (canvas.quickReject(r.left, r.top, r.right, r.bottom,
+ Canvas.EdgeType.AA)) {
+ return;
+ }
if (drawTrack) {
drawTrack(canvas, r, vertical);
}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 23a27ac..a2133b2 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -20,6 +20,8 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.Config;
+import android.util.Log;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -57,6 +59,9 @@ import java.util.List;
* <p>ScrollView only supports vertical scrolling.
*/
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;
/**
@@ -194,6 +199,7 @@ public class ScrollView extends FrameLayout {
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
+ setScrollContainer(true);
}
@Override
@@ -839,12 +845,16 @@ public class ScrollView extends FrameLayout {
public final void smoothScrollBy(int dx, int dy) {
long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
if (duration > ANIMATED_SCROLL_GAP) {
+ if (localLOGV) Log.v(TAG, "Smooth scroll: mScrollY=" + mScrollY
+ + " dy=" + dy);
mScroller.startScroll(mScrollX, mScrollY, dx, dy);
invalidate();
} else {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
+ if (localLOGV) Log.v(TAG, "Immediate scroll: mScrollY=" + mScrollY
+ + " dy=" + dy);
scrollBy(dx, dy);
}
mLastScroll = AnimationUtils.currentAnimationTimeMillis();
@@ -927,14 +937,19 @@ public class ScrollView extends FrameLayout {
View child = getChildAt(0);
mScrollX = clamp(x, this.getWidth(), child.getWidth());
mScrollY = clamp(y, this.getHeight(), child.getHeight());
+ if (localLOGV) Log.v(TAG, "mScrollY=" + mScrollY + " y=" + y
+ + " height=" + this.getHeight()
+ + " child height=" + child.getHeight());
} else {
mScrollX = x;
mScrollY = y;
}
if (oldX != mScrollX || oldY != mScrollY) {
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
- postInvalidate(); // So we draw again
}
+
+ // Keep on drawing until the animation has finished.
+ postInvalidate();
}
}
@@ -1005,6 +1020,9 @@ public class ScrollView extends FrameLayout {
int scrollYDelta = 0;
+ if (localLOGV) Log.v(TAG, "child=" + rect.toShortString()
+ + " screenTop=" + screenTop + " screenBottom=" + screenBottom
+ + " height=" + height);
if (rect.bottom > screenBottom && rect.top > screenTop) {
// need to move down to get it in view: move down just enough so
// that the entire rectangle is in view (or at least the first
@@ -1021,6 +1039,8 @@ public class ScrollView extends FrameLayout {
// make sure we aren't scrolling beyond the end of our content
int bottom = getChildAt(getChildCount() - 1).getBottom();
int distanceToBottom = bottom - screenBottom;
+ if (localLOGV) Log.v(TAG, "scrollYDelta=" + scrollYDelta
+ + " distanceToBottom=" + distanceToBottom);
scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
} else if (rect.top < screenTop && rect.bottom < screenBottom) {
@@ -1098,8 +1118,7 @@ public class ScrollView extends FrameLayout {
rectangle.offset(child.getLeft() - child.getScrollX(),
child.getTop() - child.getScrollY());
- // note: until bug 1137695 is fixed, disable smooth scrolling for this api
- return scrollToChildRect(rectangle, true);//immediate);
+ return scrollToChildRect(rectangle, immediate);
}
@Override
@@ -1122,6 +1141,24 @@ public class ScrollView extends FrameLayout {
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 = mBottom - mTop;
+
+ if (isWithinDeltaOfScreen(currentFocused, maxJump)) {
+ currentFocused.getDrawingRect(mTempRect);
+ offsetDescendantRectToMyCoords(currentFocused, mTempRect);
+ int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+ doScrollY(scrollDelta);
+ }
+ }
+
/**
* Return true if child is an descendant of parent, (or equal to the parent).
*/
diff --git a/core/java/android/widget/SectionIndexer.java b/core/java/android/widget/SectionIndexer.java
new file mode 100644
index 0000000..24f894c
--- /dev/null
+++ b/core/java/android/widget/SectionIndexer.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * Interface that should be implemented on Adapters to enable fast scrolling
+ * in an {@link AbsListView} between sections of the list. A section is a group of list items
+ * to jump to that have something in common. For example, they may begin with the
+ * same letter or they may be songs from the same artist.
+ */
+public interface SectionIndexer {
+ /**
+ * This provides the list view with an array of section objects. In the simplest
+ * case these are Strings, each containing one letter of the alphabet.
+ * They could be more complex objects that indicate the grouping for the adapter's
+ * consumption. The list view will call toString() on the objects to get the
+ * preview letter to display while scrolling.
+ * @return the array of objects that indicate the different sections of the list.
+ */
+ Object[] getSections();
+
+ /**
+ * Provides the starting index in the list for a given section.
+ * @param section the index of the section to jump to.
+ * @return the starting position of that section. If the section is out of bounds, the
+ * position must be clipped to fall within the size of the list.
+ */
+ int getPositionForSection(int section);
+
+ /**
+ * This is a reverse mapping to fetch the section index for a given position
+ * in the list.
+ * @param position the position for which to return the section
+ * @return the section index. If the position is out of bounds, the section index
+ * must be clipped to fall within the size of the section array.
+ */
+ int getSectionForPosition(int position);
+}
diff --git a/core/java/android/widget/SimpleAdapter.java b/core/java/android/widget/SimpleAdapter.java
index df52b69..261da9f 100644
--- a/core/java/android/widget/SimpleAdapter.java
+++ b/core/java/android/widget/SimpleAdapter.java
@@ -115,10 +115,22 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
View v;
if (convertView == null) {
v = mInflater.inflate(resource, parent, false);
+
+ final int[] to = mTo;
+ final int count = to.length;
+ final View[] holder = new View[count];
+
+ for (int i = 0; i < count; i++) {
+ holder[i] = v.findViewById(to[i]);
+ }
+
+ v.setTag(holder);
} else {
v = convertView;
}
+
bindView(position, v);
+
return v;
}
@@ -143,12 +155,14 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
return;
}
+ final ViewBinder binder = mViewBinder;
+ final View[] holder = (View[]) view.getTag();
final String[] from = mFrom;
final int[] to = mTo;
- final int len = to.length;
+ final int count = to.length;
- for (int i = 0; i < len; i++) {
- final View v = view.findViewById(to[i]);
+ for (int i = 0; i < count; i++) {
+ final View v = holder[i];
if (v != null) {
final Object data = dataSet.get(from[i]);
String text = data == null ? "" : data.toString();
@@ -157,8 +171,8 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
}
boolean bound = false;
- if (mViewBinder != null) {
- bound = mViewBinder.setViewValue(v, data, text);
+ if (binder != null) {
+ bound = binder.setViewValue(v, data, text);
}
if (!bound) {
diff --git a/core/java/android/widget/SimpleCursorAdapter.java b/core/java/android/widget/SimpleCursorAdapter.java
index 4d2fab3..74a9964 100644
--- a/core/java/android/widget/SimpleCursorAdapter.java
+++ b/core/java/android/widget/SimpleCursorAdapter.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.view.View;
+import android.view.ViewGroup;
/**
* An easy adapter to map columns from a cursor to TextViews or ImageViews
@@ -79,14 +80,36 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter {
* are given the values of the first N columns in the from
* parameter.
*/
- public SimpleCursorAdapter(Context context, int layout, Cursor c,
- String[] from, int[] to) {
+ public SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
super(context, layout, c);
mTo = to;
mOriginalFrom = from;
findColumns(from);
}
-
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return generateViewHolder(super.newView(context, cursor, parent));
+ }
+
+ @Override
+ public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
+ return generateViewHolder(super.newDropDownView(context, cursor, parent));
+ }
+
+ private View generateViewHolder(View v) {
+ final int[] to = mTo;
+ final int count = to.length;
+ final View[] holder = new View[count];
+
+ for (int i = 0; i < count; i++) {
+ holder[i] = v.findViewById(to[i]);
+ }
+ v.setTag(holder);
+
+ return v;
+ }
+
/**
* Binds all of the field names passed into the "to" parameter of the
* constructor with their corresponding cursor columns as specified in the
@@ -113,17 +136,22 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter {
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
- for (int i = 0; i < mTo.length; i++) {
- final View v = view.findViewById(mTo[i]);
+ final View[] holder = (View[]) view.getTag();
+ final ViewBinder binder = mViewBinder;
+ final int count = mTo.length;
+ final int[] from = mFrom;
+
+ for (int i = 0; i < count; i++) {
+ final View v = holder[i];
if (v != null) {
- String text = cursor.getString(mFrom[i]);
+ String text = cursor.getString(from[i]);
if (text == null) {
text = "";
}
boolean bound = false;
- if (mViewBinder != null) {
- bound = mViewBinder.setViewValue(v, cursor, mFrom[i]);
+ if (binder != null) {
+ bound = binder.setViewValue(v, cursor, from[i]);
}
if (!bound) {
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
index d72ffb1..afa2f3b 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -101,11 +101,9 @@ public class TableLayout extends LinearLayout {
public TableLayout(Context context, AttributeSet attrs) {
super(context, attrs);
- TypedArray a =
- context.obtainStyledAttributes(attrs, R.styleable.TableLayout);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TableLayout);
- String stretchedColumns =
- a.getString(R.styleable.TableLayout_stretchColumns);
+ String stretchedColumns = a.getString(R.styleable.TableLayout_stretchColumns);
if (stretchedColumns != null) {
if (stretchedColumns.charAt(0) == '*') {
mStretchAllColumns = true;
@@ -114,8 +112,7 @@ public class TableLayout extends LinearLayout {
}
}
- String shrinkedColumns =
- a.getString(R.styleable.TableLayout_shrinkColumns);
+ String shrinkedColumns = a.getString(R.styleable.TableLayout_shrinkColumns);
if (shrinkedColumns != null) {
if (shrinkedColumns.charAt(0) == '*') {
mShrinkAllColumns = true;
@@ -124,8 +121,7 @@ public class TableLayout extends LinearLayout {
}
}
- String collapsedColumns =
- a.getString(R.styleable.TableLayout_collapseColumns);
+ String collapsedColumns = a.getString(R.styleable.TableLayout_collapseColumns);
if (collapsedColumns != null) {
mCollapsedColumns = parseColumns(collapsedColumns);
}
@@ -356,7 +352,7 @@ public class TableLayout extends LinearLayout {
* @return true if the column is shrinkable, false otherwise. Default is false.
*/
public boolean isColumnShrinkable(int columnIndex) {
- return mShrinkAllColumns || mStretchableColumns.get(columnIndex);
+ return mShrinkAllColumns || mShrinkableColumns.get(columnIndex);
}
/**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index bd5db33..9e5f019 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
@@ -27,10 +28,12 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.os.Message;
import android.text.BoringLayout;
import android.text.DynamicLayout;
import android.text.Editable;
@@ -49,12 +52,16 @@ import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextWatcher;
+import android.text.method.DateKeyListener;
+import android.text.method.DateTimeKeyListener;
import android.text.method.DialerKeyListener;
import android.text.method.DigitsKeyListener;
import android.text.method.KeyListener;
import android.text.method.LinkMovementMethod;
import android.text.method.MetaKeyKeyListener;
import android.text.method.MovementMethod;
+import android.text.method.TimeKeyListener;
+
import android.text.method.PasswordTransformationMethod;
import android.text.method.SingleLineTransformationMethod;
import android.text.method.TextKeyListener;
@@ -78,12 +85,22 @@ import android.view.ViewDebug;
import android.view.ViewTreeObserver;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.AnimationUtils;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.EditorInfo;
import android.widget.RemoteViews.RemoteView;
+import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import com.android.internal.util.FastMath;
+import com.android.internal.widget.EditableInputConnection;
+
+import org.xmlpull.v1.XmlPullParserException;
/**
* Displays text to the user and optionally allows them to edit it. A TextView
@@ -146,6 +163,7 @@ import com.android.internal.util.FastMath;
* @attr ref android.R.styleable#TextView_drawableLeft
* @attr ref android.R.styleable#TextView_lineSpacingExtra
* @attr ref android.R.styleable#TextView_lineSpacingMultiplier
+ * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
*/
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
@@ -182,22 +200,44 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private static final int SIGNED = 2;
private static final int DECIMAL = 4;
- private Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight;
- private int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
- private int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
- private boolean mDrawables;
- private int mDrawablePadding;
+ class Drawables {
+ final Rect mCompoundRect = new Rect();
+ Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight;
+ int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight;
+ int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight;
+ int mDrawablePadding;
+ };
+ private Drawables mDrawables;
private CharSequence mError;
private boolean mErrorWasChanged;
private PopupWindow mPopup;
private CharWrapper mCharWrapper = null;
- private Rect mCompoundRect;
private boolean mSelectionMoved = false;
- /*
+ private Marquee mMarquee;
+ private boolean mRestartMarquee;
+
+ private int mMarqueeRepeatLimit = 3;
+
+ class InputContentType {
+ String privateContentType;
+ Bundle extras;
+ }
+ InputContentType mInputContentType;
+
+ class InputMethodState {
+ Rect mCursorRectInWindow = new Rect();
+ RectF mTmpRectF = new RectF();
+ float[] mTmpOffset = new float[2];
+ ExtractedTextRequest mExtracting;
+ final ExtractedText mTmpExtracted = new ExtractedText();
+ }
+ InputMethodState mInputMethodState;
+
+ /*
* Kick-start the font cache for the zygote process (to pay the cost of
* initializing freetype for our default font only once).
*/
@@ -221,7 +261,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
-
mText = "";
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
@@ -250,7 +289,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Look the appearance up without checking first if it exists because
* almost every TextView has one and it greatly simplifies the logic
* to be able to parse the appearance first and then let specific tags
- * for this View override it.
+ * for this View override it.
*/
TypedArray appearance = null;
int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1);
@@ -317,6 +356,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 n = a.getIndexCount();
for (int i = 0; i < n; i++) {
@@ -461,6 +501,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
ellipsize = a.getInt(attr, ellipsize);
break;
+ case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
+ setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
+ break;
+
case com.android.internal.R.styleable.TextView_includeFontPadding:
if (!a.getBoolean(attr, true)) {
setIncludeFontPadding(false);
@@ -544,12 +588,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
mSpacingMult = a.getFloat(attr, mSpacingMult);
break;
+
+ case com.android.internal.R.styleable.TextView_inputType:
+ contentType = a.getInt(attr, mInputType);
+ break;
+
+ case com.android.internal.R.styleable.TextView_editorPrivateContentType:
+ setPrivateContentType(a.getString(attr));
+ break;
+
+ case com.android.internal.R.styleable.TextView_editorExtras:
+ try {
+ setInputExtras(a.getResourceId(attr, 0));
+ } catch (XmlPullParserException e) {
+ Log.w("TextView", "Failure reading input extras", e);
+ } catch (IOException e) {
+ Log.w("TextView", "Failure reading input extras", e);
+ }
+ break;
}
}
a.recycle();
BufferType bufferType = BufferType.EDITABLE;
+ if ((contentType&(EditorInfo.TYPE_MASK_CLASS
+ |EditorInfo.TYPE_MASK_VARIATION))
+ == (EditorInfo.TYPE_CLASS_TEXT
+ |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
+ password = true;
+ }
+
if (inputMethod != null) {
Class c;
@@ -566,27 +635,58 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
}
+ try {
+ mInputType = contentType != EditorInfo.TYPE_NULL
+ ? contentType
+ : 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
+ | 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;
} else if (numeric != 0) {
mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
(numeric & DECIMAL) != 0);
+ contentType = EditorInfo.TYPE_CLASS_NUMBER;
+ if ((numeric & SIGNED) != 0) {
+ contentType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
+ }
+ if ((numeric & DECIMAL) != 0) {
+ contentType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
+ }
+ mInputType = contentType;
} else if (autotext || autocap != -1) {
TextKeyListener.Capitalize cap;
+ contentType = EditorInfo.TYPE_CLASS_TEXT;
+ if (!singleLine) {
+ contentType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ }
+
switch (autocap) {
case 1:
cap = TextKeyListener.Capitalize.SENTENCES;
+ contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
break;
case 2:
cap = TextKeyListener.Capitalize.WORDS;
+ contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
break;
case 3:
cap = TextKeyListener.Capitalize.CHARACTERS;
+ contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
break;
default:
@@ -595,8 +695,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
mInput = TextKeyListener.getInstance(autotext, cap);
+ mInputType = contentType;
} else if (editable) {
mInput = TextKeyListener.getInstance();
+ mInputType = EditorInfo.TYPE_CLASS_TEXT;
} else {
mInput = null;
@@ -611,6 +713,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
bufferType = BufferType.EDITABLE;
break;
}
+ mInputType = EditorInfo.TYPE_CLASS_TEXT;
+ }
+
+ if (password) {
+ mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
+ | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
}
if (selectallonfocus) {
@@ -642,6 +750,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case 3:
setEllipsize(TextUtils.TruncateAt.END);
break;
+ case 4:
+ setHorizontalFadingEdgeEnabled(true);
+ setEllipsize(TextUtils.TruncateAt.MARQUEE);
+ break;
}
setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
@@ -774,11 +886,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Return the text the TextView is displaying. If setText() was called
- * with an argument of BufferType.SPANNABLE or BufferType.EDITABLE,
- * you can cast the return value from this method to Spannable
- * or Editable, respectively.
+ * Return the text the TextView is displaying. If setText() was called with
+ * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
+ * the return value from this method to Spannable or Editable, respectively.
+ *
+ * Note: The content of the return value should not be modified. If you want
+ * a modifiable one, you should make your own copy first.
*/
+ @ViewDebug.CapturedViewProperty
public CharSequence getText() {
return mText;
}
@@ -791,6 +906,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Return the text the TextView is displaying as an Editable object. If
+ * the text is not editable, null is returned.
+ *
+ * @see #getText
+ */
+ public Editable getEditableText() {
+ return (mText instanceof Editable) ? (Editable)mText : null;
+ }
+
+ /**
* @return the height of one standard line in pixels. Note that markup
* within the text can cause individual lines to be taller or shorter
* than this height, and the layout may contain additional first-
@@ -819,7 +944,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Sets the key listener to be used with this TextView. This can be null
- * to disallow user input.
+ * to disallow user input. Note that this method has significant and
+ * subtle interactions with soft keyboards and other input method:
+ * see {@link KeyListener#getInputType() KeyListener.getContentType()}
+ * for important details. Calling this method will replace the current
+ * content type of the text view with the content type returned by the
+ * key listener.
* <p>
* Be warned that if you want a TextView with a key listener or movement
* method not to be focusable, or if you want a TextView without a
@@ -835,13 +965,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_autoText
*/
public void setKeyListener(KeyListener input) {
- mInput = input;
+ setKeyListenerOnly(input);
+ fixFocusableAndClickableSettings();
+
+ if (input != null) {
+ try {
+ mInputType = mInput.getInputType();
+ } catch (IncompatibleClassChangeError e) {
+ mInputType = EditorInfo.TYPE_CLASS_TEXT;
+ }
+ if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
+ == EditorInfo.TYPE_CLASS_TEXT) {
+ if (mSingleLine) {
+ mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ } else {
+ mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ }
+ }
+ } else {
+ mInputType = EditorInfo.TYPE_NULL;
+ }
+
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) imm.restartInput(this);
+ }
+ private void setKeyListenerOnly(KeyListener input) {
+ mInput = input;
if (mInput != null && !(mText instanceof Editable))
setText(mText);
setFilters((Editable) mText, mFilters);
- fixFocusableAndClickableSettings();
}
/**
@@ -873,7 +1027,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void fixFocusableAndClickableSettings() {
- if (mMovement != null || mInput != null) {
+ if ((mMovement != null) || mInput != null) {
setFocusable(true);
setClickable(true);
setLongClickable(true);
@@ -917,10 +1071,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Drawable if any.
*/
public int getCompoundPaddingTop() {
- if (mDrawableTop == null) {
+ final Drawables dr = mDrawables;
+ if (dr == null || dr.mDrawableTop == null) {
return mPaddingTop;
} else {
- return mPaddingTop + mDrawablePadding + mDrawableSizeTop;
+ return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
}
}
@@ -929,10 +1084,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Drawable if any.
*/
public int getCompoundPaddingBottom() {
- if (mDrawableBottom == null) {
+ final Drawables dr = mDrawables;
+ if (dr == null || dr.mDrawableBottom == null) {
return mPaddingBottom;
} else {
- return mPaddingBottom + mDrawablePadding + mDrawableSizeBottom;
+ return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
}
}
@@ -941,10 +1097,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Drawable if any.
*/
public int getCompoundPaddingLeft() {
- if (mDrawableLeft == null) {
+ final Drawables dr = mDrawables;
+ if (dr == null || dr.mDrawableLeft == null) {
return mPaddingLeft;
} else {
- return mPaddingLeft + mDrawablePadding + mDrawableSizeLeft;
+ return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
}
}
@@ -953,10 +1110,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Drawable if any.
*/
public int getCompoundPaddingRight() {
- if (mDrawableRight == null) {
+ final Drawables dr = mDrawables;
+ if (dr == null || dr.mDrawableRight == null) {
return mPaddingRight;
} else {
- return mPaddingRight + mDrawablePadding + mDrawableSizeRight;
+ return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
}
}
@@ -1043,7 +1201,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Returns the total top padding of the view, including the top
+ * Returns the total top padding of the view, including the top
* Drawable if any, the extra space to keep more than maxLines
* from showing, and the vertical offset for gravity, if any.
*/
@@ -1073,62 +1231,79 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public void setCompoundDrawables(Drawable left, Drawable top,
Drawable right, Drawable bottom) {
- mDrawableLeft = left;
- mDrawableTop = top;
- mDrawableRight = right;
- mDrawableBottom = bottom;
+ Drawables dr = mDrawables;
- mDrawables = mDrawableLeft != null
- || mDrawableRight != null
- || mDrawableTop != null
- || mDrawableBottom != null;
+ final boolean drawables = left != null || top != null
+ || right != null || bottom != null;
- if (mCompoundRect == null &&
- (left != null || top != null || right != null || bottom != null)) {
- mCompoundRect = new Rect();
- }
+ if (!drawables) {
+ // Clearing drawables... can we free the data structure?
+ if (dr != null) {
+ if (dr.mDrawablePadding == 0) {
+ mDrawables = null;
+ } else {
+ // We need to retain the last set padding, so just clear
+ // out all of the fields in the existing structure.
+ dr.mDrawableLeft = null;
+ dr.mDrawableTop = null;
+ dr.mDrawableRight = null;
+ dr.mDrawableBottom = null;
+ dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
+ dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
+ dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
+ dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
+ }
+ }
+ } else {
+ if (dr == null) {
+ mDrawables = dr = new Drawables();
+ }
+
+ dr.mDrawableLeft = left;
+ dr.mDrawableTop = top;
+ dr.mDrawableRight = right;
+ dr.mDrawableBottom = bottom;
+
+ final Rect compoundRect = dr.mCompoundRect;
+ int[] state = null;
- final Rect compoundRect = mCompoundRect;
- int[] state = null;
-
- if (mDrawables) {
state = getDrawableState();
- }
-
- if (mDrawableLeft != null) {
- mDrawableLeft.setState(state);
- mDrawableLeft.copyBounds(compoundRect);
- mDrawableSizeLeft = compoundRect.width();
- mDrawableHeightLeft = compoundRect.height();
- } else {
- mDrawableSizeLeft = mDrawableHeightLeft = 0;
- }
- if (mDrawableRight != null) {
- mDrawableRight.setState(state);
- mDrawableRight.copyBounds(compoundRect);
- mDrawableSizeRight = compoundRect.width();
- mDrawableHeightRight = compoundRect.height();
- } else {
- mDrawableSizeRight = mDrawableHeightRight = 0;
- }
+ if (left != null) {
+ left.setState(state);
+ left.copyBounds(compoundRect);
+ dr.mDrawableSizeLeft = compoundRect.width();
+ dr.mDrawableHeightLeft = compoundRect.height();
+ } else {
+ dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
+ }
- if (mDrawableTop != null) {
- mDrawableTop.setState(state);
- mDrawableTop.copyBounds(compoundRect);
- mDrawableSizeTop = compoundRect.height();
- mDrawableWidthTop = compoundRect.width();
- } else {
- mDrawableSizeTop = mDrawableWidthTop = 0;
- }
+ if (right != null) {
+ right.setState(state);
+ right.copyBounds(compoundRect);
+ dr.mDrawableSizeRight = compoundRect.width();
+ dr.mDrawableHeightRight = compoundRect.height();
+ } else {
+ dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
+ }
- if (mDrawableBottom != null) {
- mDrawableBottom.setState(state);
- mDrawableBottom.copyBounds(compoundRect);
- mDrawableSizeBottom = compoundRect.height();
- mDrawableWidthBottom = compoundRect.width();
- } else {
- mDrawableSizeBottom = mDrawableWidthBottom = 0;
+ if (top != null) {
+ top.setState(state);
+ top.copyBounds(compoundRect);
+ dr.mDrawableSizeTop = compoundRect.height();
+ dr.mDrawableWidthTop = compoundRect.width();
+ } else {
+ dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
+ }
+
+ if (bottom != null) {
+ bottom.setState(state);
+ bottom.copyBounds(compoundRect);
+ dr.mDrawableSizeBottom = compoundRect.height();
+ dr.mDrawableWidthBottom = compoundRect.width();
+ } else {
+ dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
+ }
}
invalidate();
@@ -1137,8 +1312,32 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Sets the Drawables (if any) to appear to the left of, above,
+ * to the right of, and below the text. Use 0 if you do not
+ * want a Drawable there. The Drawables' bounds will be set to
+ * their intrinsic bounds.
+ *
+ * @param left Resource identifier of the left Drawable.
+ * @param top Resource identifier of the top Drawable.
+ * @param right Resource identifier of the right Drawable.
+ * @param bottom Resource identifier of the bottom Drawable.
+ *
+ * @attr ref android.R.styleable#TextView_drawableLeft
+ * @attr ref android.R.styleable#TextView_drawableTop
+ * @attr ref android.R.styleable#TextView_drawableRight
+ * @attr ref android.R.styleable#TextView_drawableBottom
+ */
+ public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
+ final Resources resources = getContext().getResources();
+ setCompoundDrawables(left != 0 ? resources.getDrawable(left) : null,
+ top != 0 ? resources.getDrawable(top) : null,
+ right != 0 ? resources.getDrawable(right) : null,
+ bottom != 0 ? resources.getDrawable(bottom) : null);
+ }
+
+ /**
+ * Sets the Drawables (if any) to appear to the left of, above,
* to the right of, and below the text. Use null if you do not
- * want a Drawable there. The Drawables' bounds will be set to
+ * want a Drawable there. The Drawables' bounds will be set to
* their intrinsic bounds.
*
* @attr ref android.R.styleable#TextView_drawableLeft
@@ -1146,24 +1345,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_drawableRight
* @attr ref android.R.styleable#TextView_drawableBottom
*/
- public void setCompoundDrawablesWithIntrinsicBounds(Drawable left,
- Drawable top,
- Drawable right, Drawable bottom) {
+ public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top,
+ Drawable right, Drawable bottom) {
+
if (left != null) {
- left.setBounds(0, 0,
- left.getIntrinsicWidth(), left.getIntrinsicHeight());
+ left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
}
if (right != null) {
- right.setBounds(0, 0,
- right.getIntrinsicWidth(), right.getIntrinsicHeight());
+ right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
}
if (top != null) {
- top.setBounds(0, 0,
- top.getIntrinsicWidth(), top.getIntrinsicHeight());
+ top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
}
if (bottom != null) {
- bottom.setBounds(0, 0,
- bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
+ bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
}
setCompoundDrawables(left, top, right, bottom);
}
@@ -1172,9 +1367,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Returns drawables for the left, top, right, and bottom borders.
*/
public Drawable[] getCompoundDrawables() {
- return new Drawable[] {
- mDrawableLeft, mDrawableTop, mDrawableRight, mDrawableBottom
- };
+ final Drawables dr = mDrawables;
+ if (dr != null) {
+ return new Drawable[] {
+ dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
+ };
+ } else {
+ return new Drawable[] { null, null, null, null };
+ }
}
/**
@@ -1184,7 +1384,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_drawablePadding
*/
public void setCompoundDrawablePadding(int pad) {
- mDrawablePadding = pad;
+ Drawables dr = mDrawables;
+ if (pad == 0) {
+ if (dr != null) {
+ dr.mDrawablePadding = pad;
+ }
+ } else {
+ if (dr == null) {
+ mDrawables = dr = new Drawables();
+ }
+ dr.mDrawablePadding = pad;
+ }
invalidate();
requestLayout();
@@ -1194,7 +1404,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Returns the padding between the compound drawables and the text.
*/
public int getCompoundDrawablePadding() {
- return mDrawablePadding;
+ final Drawables dr = mDrawables;
+ return dr != null ? dr.mDrawablePadding : 0;
}
@Override
@@ -1910,18 +2121,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
updateTextColors();
}
- int[] state = getDrawableState();
- if (mDrawableTop != null && mDrawableTop.isStateful()) {
- mDrawableTop.setState(state);
- }
- if (mDrawableBottom != null && mDrawableBottom.isStateful()) {
- mDrawableBottom.setState(state);
- }
- if (mDrawableLeft != null && mDrawableLeft.isStateful()) {
- mDrawableLeft.setState(state);
- }
- if (mDrawableRight != null && mDrawableRight.isStateful()) {
- mDrawableRight.setState(state);
+ final Drawables dr = mDrawables;
+ if (dr != null) {
+ int[] state = getDrawableState();
+ if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
+ dr.mDrawableTop.setState(state);
+ }
+ if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
+ dr.mDrawableBottom.setState(state);
+ }
+ if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
+ dr.mDrawableLeft.setState(state);
+ }
+ if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
+ dr.mDrawableRight.setState(state);
+ }
}
}
@@ -1982,12 +2196,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
-
+
// Save state if we are forced to
boolean save = mFreezesText;
int start = 0;
int end = 0;
-
+
if (mText != null) {
start = Selection.getSelectionStart(mText);
end = Selection.getSelectionEnd(mText);
@@ -1996,7 +2210,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
save = true;
}
}
-
+
if (save) {
SavedState ss = new SavedState(superState);
// XXX Should also save the current scroll position!
@@ -2030,15 +2244,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return ss;
}
-
- return null;
+
+ return superState;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
SavedState ss = (SavedState)state;
super.onRestoreInstanceState(ss.getSuperState());
-
+
// XXX restore buffer type too, as well as lots of other stuff
if (ss.text != null) {
setText(ss.text);
@@ -2165,6 +2384,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
text = "";
}
+ if (text instanceof Spanned &&
+ ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
+ setHorizontalFadingEdgeEnabled(true);
+ setEllipsize(TextUtils.TruncateAt.MARQUEE);
+ }
+
int n = mFilters.length;
for (int i = 0; i < n; i++) {
CharSequence out = mFilters[i].filter(text, 0, text.length(),
@@ -2183,11 +2408,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- if (type == BufferType.EDITABLE || mInput != null) {
+ boolean needEditableForNotification = false;
+
+ if (mListeners != null && mListeners.size() != 0) {
+ needEditableForNotification = true;
+ }
+
+ if (type == BufferType.EDITABLE || mInput != null ||
+ needEditableForNotification) {
Editable t = mEditableFactory.newEditable(text);
text = t;
-
setFilters(t, mFilters);
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) imm.restartInput(this);
} else if (type == BufferType.SPANNABLE || mMovement != null) {
text = mSpannableFactory.newSpannable(text);
} else if (!(text instanceof CharWrapper)) {
@@ -2256,7 +2489,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (mMovement != null) {
- mMovement.initialize(this, (Spannable) text);
+ mMovement.initialize(this, (Spannable) text);
/*
* Initializing the movement method will have set the
@@ -2273,6 +2506,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
sendOnTextChanged(text, 0, oldlen, textLength);
onTextChanged(text, 0, oldlen, textLength);
+
+ if (needEditableForNotification) {
+ sendAfterTextChanged((Editable) text);
+ }
}
/**
@@ -2401,8 +2638,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Sets the text to be displayed when the text of the TextView is empty.
- * Null means to use the normal empty text. The hint does not
- * currently participate in determining the size of the view.
+ * Null means to use the normal empty text. The hint does not currently
+ * participate in determining the size of the view.
+ *
+ * This method is deprecated. Use {link #setHint(int, String)} or
+ * {link #setHint(CharSequence, String)} instead.
*
* @attr ref android.R.styleable#TextView_hint
*/
@@ -2421,6 +2661,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Sets the text to be displayed when the text of the TextView is empty,
* from a resource.
*
+ * This method is deprecated. Use {link #setHint(int, String)} or
+ * {link #setHint(CharSequence, String)} instead.
+ *
* @attr ref android.R.styleable#TextView_hint
*/
public final void setHint(int resid) {
@@ -2433,11 +2676,177 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @attr ref android.R.styleable#TextView_hint
*/
+ @ViewDebug.CapturedViewProperty
public CharSequence getHint() {
return mHint;
}
/**
+ * Set the type of the content with a constant as defined for
+ * {@link EditorInfo#inputType}. This will take care of changing
+ * the key listener, by calling {@link #setKeyListener(KeyListener)}, to
+ * match the given content type. If the given content type is
+ * {@link EditorInfo#TYPE_NULL} then a soft keyboard will
+ * not be displayed for this text view.
+ *
+ * @see #getInputType()
+ * @see #setRawInputType(int)
+ * @see android.text.InputType
+ * @attr ref android.R.styleable#TextView_inputType
+ */
+ public void setInputType(int type) {
+ setInputType(type, false);
+ if ((type&(EditorInfo.TYPE_MASK_CLASS
+ |EditorInfo.TYPE_MASK_VARIATION))
+ == (EditorInfo.TYPE_CLASS_TEXT
+ |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
+ setTransformationMethod(PasswordTransformationMethod.getInstance());
+ 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);
+ }
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) imm.restartInput(this);
+ }
+
+ /**
+ * Directly change the content type integer of the text view, without
+ * modifying any other state.
+ * @see #setContentType
+ * @see android.text.InputType
+ * @attr ref android.R.styleable#TextView_inputType
+ */
+ public void setRawInputType(int type) {
+ mInputType = type;
+ }
+
+ private void setInputType(int type, boolean direct) {
+ final int cls = type & EditorInfo.TYPE_MASK_CLASS;
+ KeyListener input;
+ if (cls == EditorInfo.TYPE_CLASS_TEXT) {
+ boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT)
+ != 0;
+ TextKeyListener.Capitalize cap;
+ if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
+ cap = TextKeyListener.Capitalize.CHARACTERS;
+ } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
+ cap = TextKeyListener.Capitalize.WORDS;
+ } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
+ cap = TextKeyListener.Capitalize.SENTENCES;
+ } else {
+ cap = TextKeyListener.Capitalize.NONE;
+ }
+ input = TextKeyListener.getInstance(autotext, cap);
+ } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
+ input = DigitsKeyListener.getInstance(
+ (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
+ (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
+ } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
+ switch (type & EditorInfo.TYPE_MASK_VARIATION) {
+ case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
+ input = DateKeyListener.getInstance();
+ break;
+ case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
+ input = TimeKeyListener.getInstance();
+ break;
+ default:
+ input = DateTimeKeyListener.getInstance();
+ break;
+ }
+ } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
+ input = DialerKeyListener.getInstance();
+ } else {
+ input = TextKeyListener.getInstance();
+ }
+ mInputType = type;
+ if (direct) mInput = input;
+ else {
+ setKeyListenerOnly(input);
+ }
+ }
+
+ /**
+ * Get the type of the content.
+ *
+ * @see #setInputType(int)
+ * @see android.text.InputType
+ */
+ public int getInputType() {
+ return mInputType;
+ }
+
+ /**
+ * Set the private content type of the text, which is the
+ * {@link EditorInfo#privateContentType TextBoxAttribute.privateContentType}
+ * field that will be filled in when creating an input connection.
+ *
+ * @see #getPrivateContentType()
+ * @see EditorInfo#privateContentType
+ * @attr ref android.R.styleable#TextView_editorPrivateContentType
+ */
+ public void setPrivateContentType(String type) {
+ if (mInputContentType == null) mInputContentType = new InputContentType();
+ mInputContentType.privateContentType = type;
+ }
+
+ /**
+ * Get the private type of the content.
+ *
+ * @see #setPrivateContentType(String)
+ * @see EditorInfo#privateContentType
+ */
+ public String getPrivateContentType() {
+ return mInputContentType != null
+ ? mInputContentType.privateContentType : null;
+ }
+
+ /**
+ * Set the extra input data of the text, which is the
+ * {@link EditorInfo#extras TextBoxAttribute.extras}
+ * Bundle that will be filled in when creating an input connection. The
+ * 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 EditorInfo#extras
+ * @attr ref android.R.styleable#TextView_editorExtras
+ */
+ public void setInputExtras(int xmlResId)
+ throws XmlPullParserException, IOException {
+ XmlResourceParser parser = getResources().getXml(xmlResId);
+ if (mInputContentType == null) mInputContentType = new InputContentType();
+ mInputContentType.extras = new Bundle();
+ getResources().parseBundleExtras(parser, mInputContentType.extras);
+ }
+
+ /**
+ * Retrieve the input extras currently associated with the text view, which
+ * can be viewed as well as modified.
+ *
+ * @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 EditorInfo#extras
+ * @attr ref android.R.styleable#TextView_editorExtras
+ */
+ public Bundle getInputExtras(boolean create) {
+ if (mInputContentType == null) {
+ if (!create) return null;
+ mInputContentType = new InputContentType();
+ }
+ if (mInputContentType.extras == null) {
+ if (!create) return null;
+ mInputContentType.extras = new Bundle();
+ }
+ return mInputContentType.extras;
+ }
+
+ /**
* Returns the error message that was set to be displayed with
* {@link #setError}, or <code>null</code> if no error was set
* or if it the error was cleared by the widget after user input.
@@ -2481,8 +2890,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mError = error;
mErrorWasChanged = true;
- setCompoundDrawables(mDrawableLeft, mDrawableTop,
- icon, mDrawableBottom);
+ final Drawables dr = mDrawables;
+ if (dr != null) {
+ setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop,
+ icon, dr.mDrawableBottom);
+ } else {
+ setCompoundDrawables(null, null, icon, null);
+ }
if (error == null) {
if (mPopup != null) {
@@ -2525,9 +2939,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* The "25" is the distance between the point and the right edge
* of the background
*/
-
+
+ final Drawables dr = mDrawables;
return getWidth() - mPopup.getWidth()
- - getPaddingRight() - mDrawableSizeRight / 2 + 25;
+ - getPaddingRight()
+ - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + 25;
}
/**
@@ -2542,17 +2958,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int vspace = mBottom - mTop -
getCompoundPaddingBottom() - getCompoundPaddingTop();
- int icontop = getCompoundPaddingTop() +
- (vspace - mDrawableHeightRight) / 2;
+ final Drawables dr = mDrawables;
+ int icontop = getCompoundPaddingTop()
+ + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2;
/*
* The "2" is the distance between the point and the top edge
* of the background.
*/
- return icontop + mDrawableHeightRight - getHeight() - 2;
+ return icontop + (dr != null ? dr.mDrawableHeightRight : 0)
+ - getHeight() - 2;
}
-
+
private void hideError() {
if (mPopup != null) {
if (mPopup.isShowing()) {
@@ -2599,6 +3017,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mPopup.update(this, getErrorX(), getErrorY(), -1, -1);
}
+ if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+ mRestartMarquee = false;
+ startMarquee();
+ }
+
return result;
}
@@ -2659,10 +3082,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int boxht;
if (l == mHintLayout) {
- boxht = getMeasuredHeight() - getCompoundPaddingTop() -
+ boxht = getMeasuredHeight() - getCompoundPaddingTop() -
getCompoundPaddingBottom();
} else {
- boxht = getMeasuredHeight() - getExtendedPaddingTop() -
+ boxht = getMeasuredHeight() - getExtendedPaddingTop() -
getExtendedPaddingBottom();
}
int textht = l.getHeight();
@@ -2690,10 +3113,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int boxht;
if (l == mHintLayout) {
- boxht = getMeasuredHeight() - getCompoundPaddingTop() -
+ boxht = getMeasuredHeight() - getCompoundPaddingTop() -
getCompoundPaddingBottom();
} else {
- boxht = getMeasuredHeight() - getExtendedPaddingTop() -
+ boxht = getMeasuredHeight() - getExtendedPaddingTop() -
getExtendedPaddingBottom();
}
int textht = l.getHeight();
@@ -2713,15 +3136,32 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
invalidateCursor();
} else {
synchronized (sTempRect) {
+ /*
+ * The reason for this concern about the thickness of the
+ * cursor and doing the floor/ceil on the coordinates is that
+ * some EditTexts (notably textfields in the Browser) have
+ * anti-aliased text where not all the characters are
+ * necessarily at integer-multiple locations. This should
+ * make sure the entire cursor gets invalidated instead of
+ * sometimes missing half a pixel.
+ */
+
+ float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
+ if (thick < 1.0f) {
+ thick = 1.0f;
+ }
+
+ thick /= 2;
+
mHighlightPath.computeBounds(sTempRect, false);
int left = getCompoundPaddingLeft();
int top = getExtendedPaddingTop() + getVerticalOffset(true);
- invalidate((int) sTempRect.left + left,
- (int) sTempRect.top + top,
- (int) sTempRect.right + left + 1,
- (int) sTempRect.bottom + top + 1);
+ invalidate((int) FloatMath.floor(left + sTempRect.left - thick),
+ (int) FloatMath.floor(top + sTempRect.top - thick),
+ (int) FloatMath.ceil(left + sTempRect.right + thick),
+ (int) FloatMath.ceil(top + sTempRect.bottom + thick));
}
}
}
@@ -2837,6 +3277,35 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mPreDrawState = PREDRAW_NOT_REGISTERED;
}
}
+
+ if (mError != null) {
+ hideError();
+ }
+ }
+
+ @Override
+ protected boolean isPaddingOffsetRequired() {
+ return mShadowRadius != 0;
+ }
+
+ @Override
+ protected int getLeftPaddingOffset() {
+ return (int) Math.min(0, mShadowDx - mShadowRadius);
+ }
+
+ @Override
+ protected int getTopPaddingOffset() {
+ return (int) Math.min(0, mShadowDy - mShadowRadius);
+ }
+
+ @Override
+ protected int getBottomPaddingOffset() {
+ return (int) Math.max(0, mShadowDy + mShadowRadius);
+ }
+
+ @Override
+ protected int getRightPaddingOffset() {
+ return (int) Math.max(0, mShadowDx + mShadowRadius);
}
@Override
@@ -2855,7 +3324,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int bottom = mBottom;
final int top = mTop;
- if (mDrawables) {
+ final Drawables dr = mDrawables;
+ if (dr != null) {
/*
* Compound, not extended, because the icon is not clipped
* if the text height is smaller.
@@ -2864,37 +3334,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
- if (mDrawableLeft != null) {
+ if (dr.mDrawableLeft != null) {
canvas.save();
canvas.translate(scrollX + mPaddingLeft,
scrollY + compoundPaddingTop +
- (vspace - mDrawableHeightLeft) / 2);
- mDrawableLeft.draw(canvas);
+ (vspace - dr.mDrawableHeightLeft) / 2);
+ dr.mDrawableLeft.draw(canvas);
canvas.restore();
}
- if (mDrawableRight != null) {
+ if (dr.mDrawableRight != null) {
canvas.save();
- canvas.translate(scrollX + right - left - mPaddingRight - mDrawableSizeRight,
- scrollY + compoundPaddingTop + (vspace - mDrawableHeightRight) / 2);
- mDrawableRight.draw(canvas);
+ canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight,
+ scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
+ dr.mDrawableRight.draw(canvas);
canvas.restore();
}
- if (mDrawableTop != null) {
+ if (dr.mDrawableTop != null) {
canvas.save();
- canvas.translate(scrollX + compoundPaddingLeft + (hspace - mDrawableWidthTop) / 2,
+ canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2,
scrollY + mPaddingTop);
- mDrawableTop.draw(canvas);
+ dr.mDrawableTop.draw(canvas);
canvas.restore();
}
- if (mDrawableBottom != null) {
+ if (dr.mDrawableBottom != null) {
canvas.save();
canvas.translate(scrollX + compoundPaddingLeft +
- (hspace - mDrawableWidthBottom) / 2,
- scrollY + bottom - top - mPaddingBottom - mDrawableSizeBottom);
- mDrawableBottom.draw(canvas);
+ (hspace - dr.mDrawableWidthBottom) / 2,
+ scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
+ dr.mDrawableBottom.draw(canvas);
canvas.restore();
}
}
@@ -2929,7 +3399,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
canvas.save();
/* Would be faster if we didn't have to do this. Can we chop the
- (displayable) text so that we don't need to do this ever?
+ (displayable) text so that we don't need to do this ever?
*/
int extendedPaddingTop = getExtendedPaddingTop();
@@ -2963,7 +3433,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
}
+ if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+ if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
+ (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
+ canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
+ getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
+ }
+
+ if (mMarquee != null && mMarquee.isRunning()) {
+ canvas.translate(-mMarquee.mScroll, 0.0f);
+ }
+ }
+
Path highlight = null;
+ int selStart = -1, selEnd = -1;
// If there is no movement method, then there can be no selection.
// Check that first and attempt to skip everything having to do with
@@ -2971,19 +3454,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// XXX This is not strictly true -- a program could set the
// selection manually if it really wanted to.
if (mMovement != null && (isFocused() || isPressed())) {
- int start = Selection.getSelectionStart(mText);
- int end = Selection.getSelectionEnd(mText);
+ selStart = Selection.getSelectionStart(mText);
+ selEnd = Selection.getSelectionEnd(mText);
- if (mCursorVisible && start >= 0 && isEnabled()) {
+ if (mCursorVisible && selStart >= 0 && isEnabled()) {
if (mHighlightPath == null)
mHighlightPath = new Path();
- if (start == end) {
+ if (selStart == selEnd) {
if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK)
< BLINK) {
if (mHighlightPathBogus) {
mHighlightPath.reset();
- mLayout.getCursorPath(start, mHighlightPath, mText);
+ mLayout.getCursorPath(selStart, mHighlightPath, mText);
mHighlightPathBogus = false;
}
@@ -2996,7 +3479,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else {
if (mHighlightPathBogus) {
mHighlightPath.reset();
- mLayout.getSelectionPath(start, end, mHighlightPath);
+ mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
mHighlightPathBogus = false;
}
@@ -3020,6 +3503,31 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
*/
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (highlight != null && mInputMethodState != null && imm != null) {
+ imm.updateSelection(this, selStart, selEnd);
+
+ if (imm.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);
+ }
+ }
+
layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText);
/* Comment out until we decide what to do about animations
@@ -3050,6 +3558,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
r.left = (int) mLayout.getPrimaryHorizontal(sel);
r.right = r.left + 1;
+
+ // Adjust for padding and gravity.
+ int paddingLeft = getCompoundPaddingLeft();
+ int paddingTop = getExtendedPaddingTop();
+ if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
+ paddingTop += getVerticalOffset(false);
+ }
+ r.offset(paddingLeft, paddingTop);
}
/**
@@ -3106,20 +3622,67 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (!isEnabled()) {
+ int which = doKeyDown(keyCode, event);
+ if (which == 0) {
+ // Go through default dispatching.
return super.onKeyDown(keyCode, event);
}
+ return true;
+ }
+
+ @Override
+ public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+ KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN);
+
+ int which = doKeyDown(keyCode, down);
+ if (which == 0) {
+ // Go through default dispatching.
+ return super.onKeyMultiple(keyCode, repeatCount, event);
+ }
+
+ // 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.
+ // It would be nice if those interfaces had an onKeyMultiple() method,
+ // but adding that is a more complicated change.
+ KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP);
+ if (which == 1) {
+ mInput.onKeyUp(this, (Editable)mText, keyCode, up);
+ while (--repeatCount > 0) {
+ mInput.onKeyDown(this, (Editable)mText, keyCode, down);
+ mInput.onKeyUp(this, (Editable)mText, keyCode, up);
+ }
+ if (mError != null && !mErrorWasChanged) {
+ setError(null, null);
+ }
+
+ } else if (which == 2) {
+ mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
+ while (--repeatCount > 0) {
+ mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
+ mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
+ }
+ }
+
+ return true;
+ }
+
+ private int doKeyDown(int keyCode, KeyEvent event) {
+ if (!isEnabled()) {
+ return 0;
+ }
+
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
if (mSingleLine && mInput != null) {
- return super.onKeyDown(keyCode, event);
+ return 0;
}
}
if (mInput != null) {
- /*
+ /*
* Keep track of what the error was before doing the input
* so that if an input filter changed the error, we leave
* that error showing. Otherwise, we take down whatever
@@ -3131,7 +3694,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mError != null && !mErrorWasChanged) {
setError(null, null);
}
- return true;
+ return 1;
}
}
@@ -3140,9 +3703,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mMovement != null && mLayout != null)
if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event))
- return true;
+ return 2;
- return super.onKeyDown(keyCode, event);
+ return 0;
}
@Override
@@ -3199,6 +3762,108 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return super.onKeyUp(keyCode, event);
}
+ @Override public InputConnection createInputConnection(EditorInfo outAttrs) {
+ if (mInputType != EditorInfo.TYPE_NULL) {
+ if (mInputMethodState == null) {
+ mInputMethodState = new InputMethodState();
+ }
+ outAttrs.inputType = mInputType;
+ outAttrs.hintText = mHint;
+ if (mInputContentType != null) {
+ outAttrs.privateContentType = mInputContentType.privateContentType;
+ outAttrs.extras = mInputContentType.extras;
+ }
+ if (mText instanceof Editable) {
+ InputConnection ic = new EditableInputConnection(this);
+ outAttrs.initialSelStart = Selection.getSelectionStart(mText);
+ outAttrs.initialSelEnd = Selection.getSelectionEnd(mText);
+ outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
+ return ic;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 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.
+ */
+ public boolean extractText(ExtractedTextRequest request,
+ ExtractedText outText) {
+ Editable content = getEditableText();
+ if (content != null) {
+ outText.text = content.subSequence(0, content.length());
+ outText.startOffset = 0;
+ outText.selectionStart = Selection.getSelectionStart(content);
+ outText.selectionEnd = Selection.getSelectionEnd(content);
+ return true;
+ }
+ return false;
+ }
+
+ void reportExtractedText() {
+ if (mInputMethodState != null) {
+ final ExtractedTextRequest req = mInputMethodState.mExtracting;
+ if (req != null) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ if (extractText(req, mInputMethodState.mTmpExtracted)) {
+ imm.updateExtractedText(this, req.token,
+ mInputMethodState.mTmpExtracted);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Apply to this text view the given extracted text, as previously
+ * 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);
+ }
+
+ /**
+ * @hide
+ */
+ public void setExtracting(ExtractedTextRequest req) {
+ if (mInputMethodState != null) {
+ mInputMethodState.mExtracting = req;
+ }
+ }
+
+ /**
+ * Called by the framework in response to a text completion from
+ * the current input method, provided by it calling
+ * {@link InputConnection#commitCompletion
+ * InputConnection.commitCompletion()}. The default implementation does
+ * nothing; text views that are supporting auto-completion should override
+ * this to do their desired behavior.
+ *
+ * @param text The auto complete text the user has selected.
+ */
+ public void onCommitCompletion(CompletionInfo text) {
+ }
+
+ /**
+ * Called by the framework in response to a private command from the
+ * current method, provided by it calling
+ * {@link InputConnection#performPrivateCommand
+ * InputConnection.performPrivateCommand()}.
+ *
+ * @param action The action name of the command.
+ * @param data Any additional data for the command. This may be null.
+ * @return Return true if you handled the command, else false.
+ */
+ public boolean onPrivateIMECommand(String action, Bundle data) {
+ return false;
+ }
+
private void nullLayouts() {
if (mLayout instanceof BoringLayout && mSavedLayout == null) {
mSavedLayout = (BoringLayout) mLayout;
@@ -3240,6 +3905,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
BoringLayout.Metrics boring,
BoringLayout.Metrics hintBoring,
int ellipsisWidth, boolean bringIntoView) {
+ stopMarquee();
+
mHighlightPathBogus = true;
if (w < 0) {
@@ -3371,6 +4038,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (bringIntoView) {
registerForPreDraw();
}
+
+ if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+ final int height = mLayoutParams.height;
+ // If the size of the view does not depend on the size of the text, try to
+ // start the marquee immediately
+ if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.FILL_PARENT) {
+ startMarquee();
+ } else {
+ // Defer the start of the marquee until we know our width (see setFrame())
+ mRestartMarquee = true;
+ }
+ }
}
private static int desired(Layout layout) {
@@ -3458,8 +4137,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
width = boring.width;
}
- width = Math.max(width, mDrawableWidthTop);
- width = Math.max(width, mDrawableWidthBottom);
+ final Drawables dr = mDrawables;
+ if (dr != null) {
+ width = Math.max(width, dr.mDrawableWidthTop);
+ width = Math.max(width, dr.mDrawableWidthBottom);
+ }
if (mHint != null) {
int hintDes = -1;
@@ -3509,7 +4191,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Check against our minimum width
width = Math.max(width, getSuggestedMinimumWidth());
-
+
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(widthSize, width);
}
@@ -3596,8 +4278,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
int desired = layout.getLineTop(linecount);
- desired = Math.max(desired, mDrawableHeightLeft);
- desired = Math.max(desired, mDrawableHeightRight);
+ final Drawables dr = mDrawables;
+ if (dr != null) {
+ desired = Math.max(desired, dr.mDrawableHeightLeft);
+ desired = Math.max(desired, dr.mDrawableHeightRight);
+ }
desired += pad;
@@ -3611,8 +4296,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
desired = layout.getLineTop(mMaximum) +
layout.getBottomPadding();
- desired = Math.max(desired, mDrawableHeightLeft);
- desired = Math.max(desired, mDrawableHeightRight);
+ if (dr != null) {
+ desired = Math.max(desired, dr.mDrawableHeightLeft);
+ desired = Math.max(desired, dr.mDrawableHeightRight);
+ }
desired += pad;
linecount = mMaximum;
@@ -3632,7 +4319,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Check against our minimum height
desired = Math.max(desired, getSuggestedMinimumHeight());
-
+
return desired;
}
@@ -3978,8 +4665,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private void getInterestingRect(Rect r, int h, int top, int bottom,
int line) {
- top += getExtendedPaddingTop();
- bottom += getExtendedPaddingTop();
+ int paddingTop = getExtendedPaddingTop();
+ if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
+ paddingTop += getVerticalOffset(false);
+ }
+ top += paddingTop;
+ bottom += paddingTop;
h += getCompoundPaddingLeft();
if (line == 0)
@@ -3987,7 +4678,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (line == mLayout.getLineCount() - 1)
bottom += getExtendedPaddingBottom();
- r.set(h, top, h, bottom);
+ r.set(h, top, h+1, bottom);
r.offset(-mScrollX, -mScrollY);
}
@@ -4056,6 +4747,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public void setSingleLine(boolean singleLine) {
mSingleLine = singleLine;
+ if ((mInputType&EditorInfo.TYPE_MASK_CLASS)
+ == EditorInfo.TYPE_CLASS_TEXT) {
+ if (singleLine) {
+ mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ } else {
+ mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ }
+ }
if (singleLine) {
setLines(1);
@@ -4089,9 +4788,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Sets how many times to repeat the marquee animation. Only applied if the
+ * TextView has marquee enabled. Set to -1 to repeat indefinitely.
+ *
+ * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
+ */
+ public void setMarqueeRepeatLimit(int marqueeLimit) {
+ mMarqueeRepeatLimit = marqueeLimit;
+ }
+
+ /**
* Returns where, if anywhere, words that are longer than the view
* is wide should be ellipsized.
*/
+ @ViewDebug.ExportedProperty
public TextUtils.TruncateAt getEllipsize() {
return mEllipsize;
}
@@ -4126,6 +4836,145 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ private boolean canMarquee() {
+ int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
+ return width > 0 && mLayout.getLineWidth(0) > width;
+ }
+
+ private void startMarquee() {
+ if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
+ getLineCount() == 1 && canMarquee()) {
+ if (mMarquee == null) mMarquee = new Marquee(this);
+ mMarquee.start(mMarqueeRepeatLimit);
+ }
+ }
+
+ private void stopMarquee() {
+ if (mMarquee != null && !mMarquee.isStopped()) {
+ mMarquee.stop();
+ }
+ }
+
+ private void startStopMarquee(boolean start) {
+ if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+ if (start) {
+ startMarquee();
+ } else {
+ stopMarquee();
+ }
+ }
+ }
+
+ private static final class Marquee extends Handler {
+ // TODO: Add an option to configure this
+ private static final int MARQUEE_DELAY = 1200;
+ private static final int MARQUEE_RESTART_DELAY = 1200;
+ private static final int MARQUEE_RESOLUTION = 1000 / 30;
+ private static final int MARQUEE_PIXELS_PER_SECOND = 30;
+
+ private static final byte MARQUEE_STOPPED = 0x0;
+ private static final byte MARQUEE_STARTING = 0x1;
+ private static final byte MARQUEE_RUNNING = 0x2;
+
+ private static final int MESSAGE_START = 0x1;
+ private static final int MESSAGE_TICK = 0x2;
+ private static final int MESSAGE_RESTART = 0x3;
+
+ private final WeakReference<TextView> mView;
+
+ private byte mStatus = MARQUEE_STOPPED;
+ private float mScrollUnit;
+ private float mMaxScroll;
+ private int mRepeatLimit;
+
+ float mScroll;
+
+ Marquee(TextView v) {
+ mView = new WeakReference<TextView>(v);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_START:
+ mStatus = MARQUEE_RUNNING;
+ tick();
+ break;
+ case MESSAGE_TICK:
+ tick();
+ break;
+ case MESSAGE_RESTART:
+ if (mStatus == MARQUEE_RUNNING) {
+ if (mRepeatLimit >= 0) {
+ mRepeatLimit--;
+ }
+ start(mRepeatLimit);
+ }
+ break;
+ }
+ }
+
+ void tick() {
+ if (mStatus != MARQUEE_RUNNING) {
+ return;
+ }
+
+ removeMessages(MESSAGE_TICK);
+
+ final TextView textView = mView.get();
+ if (textView != null && (textView.isFocused() || textView.isSelected())) {
+ mScroll += mScrollUnit;
+ if (mScroll > mMaxScroll) {
+ mScroll = mMaxScroll;
+ sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
+ } else {
+ sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
+ }
+ textView.invalidate();
+ }
+ }
+
+ void stop() {
+ mStatus = MARQUEE_STOPPED;
+ removeMessages(MESSAGE_START);
+ removeMessages(MESSAGE_RESTART);
+ removeMessages(MESSAGE_TICK);
+ resetScroll();
+ }
+
+ private void resetScroll() {
+ mScroll = 0.0f;
+ final TextView textView = mView.get();
+ if (textView != null) textView.invalidate();
+ }
+
+ void start(int repeatLimit) {
+ if (repeatLimit == 0) {
+ stop();
+ return;
+ }
+ mRepeatLimit = repeatLimit;
+ final TextView textView = mView.get();
+ 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();
+ sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
+ }
+ }
+
+ boolean isRunning() {
+ return mStatus == MARQUEE_RUNNING;
+ }
+
+ boolean isStopped() {
+ return mStatus == MARQUEE_STOPPED;
+ }
+ }
+
/**
* This method is called when the text is changed, in case any
* subclasses would like to know.
@@ -4151,6 +5000,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Adds a TextWatcher to the list of those whose methods are called
* whenever this TextView's text changes.
+ * <p>
+ * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
+ * not called after {@link #setText} calls. Now, doing {@link #setText}
+ * if there are any text changed listeners forces the buffer type to
+ * Editable if it would not otherwise be and does call this method.
*/
public void addTextChangedListener(TextWatcher watcher) {
if (mListeners == null) {
@@ -4186,7 +5040,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- private void sendOnTextChanged(CharSequence text, int start, int before,
+ /**
+ * Not private so it can be called from an inner class without going
+ * through a thunk.
+ */
+ void sendOnTextChanged(CharSequence text, int start, int before,
int after) {
if (mListeners != null) {
final ArrayList<TextWatcher> list = mListeners;
@@ -4197,7 +5055,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- private void sendAfterTextChanged(Editable text) {
+ /**
+ * Not private so it can be called from an inner class without going
+ * through a thunk.
+ */
+ void sendAfterTextChanged(Editable text) {
if (mListeners != null) {
final ArrayList<TextWatcher> list = mListeners;
final int count = list.size();
@@ -4207,105 +5069,118 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- private class ChangeWatcher
- extends Handler
- implements TextWatcher, SpanWatcher {
- public void beforeTextChanged(CharSequence buffer, int start,
- int before, int after) {
- TextView.this.sendBeforeTextChanged(buffer, start, before, after);
+ /**
+ * Not private so it can be called from an inner class without going
+ * through a thunk.
+ */
+ 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();
}
- public void onTextChanged(CharSequence buffer, int start,
- int before, int after) {
- invalidate();
+ if (curs >= 0) {
+ mHighlightPathBogus = true;
- int curs = Selection.getSelectionStart(buffer);
+ if (isFocused()) {
+ mShowCursor = SystemClock.uptimeMillis();
+ makeBlink();
+ }
+ }
- if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
- Gravity.BOTTOM) {
- registerForPreDraw();
+ checkForResize();
+
+ sendOnTextChanged(buffer, start, before, after);
+ onTextChanged(buffer, start, before, after);
+ }
+
+ /**
+ * 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) {
+ // XXX Make the start and end move together if this ends up
+ // spending too much time invalidating.
+
+ if (what == Selection.SELECTION_END) {
+ mHighlightPathBogus = true;
+
+ if (!isFocused()) {
+ mSelectionMoved = true;
}
- if (curs >= 0) {
- mHighlightPathBogus = true;
+ if (o >= 0 || n >= 0) {
+ invalidateCursor(Selection.getSelectionStart(buf), o, n);
+ registerForPreDraw();
if (isFocused()) {
mShowCursor = SystemClock.uptimeMillis();
makeBlink();
}
}
-
- checkForResize();
-
- TextView.this.sendOnTextChanged(buffer, start, before, after);
- TextView.this.onTextChanged(buffer, start, before, after);
}
- public void afterTextChanged(Editable buffer) {
- TextView.this.sendAfterTextChanged(buffer);
- }
-
- private void spanChange(Spanned buf, Object what, int o, int n) {
- // XXX Make the start and end move together if this ends up
- // spending too much time invalidating.
+ if (what == Selection.SELECTION_START) {
+ mHighlightPathBogus = true;
- if (what == Selection.SELECTION_END) {
- mHighlightPathBogus = true;
-
- if (!isFocused()) {
- mSelectionMoved = true;
- }
-
- if (o >= 0 || n >= 0) {
- invalidateCursor(Selection.getSelectionStart(buf), o, n);
- registerForPreDraw();
+ if (!isFocused()) {
+ mSelectionMoved = true;
+ }
- if (isFocused()) {
- mShowCursor = SystemClock.uptimeMillis();
- makeBlink();
- }
- }
+ if (o >= 0 || n >= 0) {
+ invalidateCursor(Selection.getSelectionEnd(buf), o, n);
}
+ }
- if (what == Selection.SELECTION_START) {
- mHighlightPathBogus = true;
+ if (what instanceof UpdateLayout ||
+ what instanceof ParagraphStyle) {
+ invalidate();
+ mHighlightPathBogus = true;
+ checkForResize();
+ }
- if (!isFocused()) {
- mSelectionMoved = true;
- }
+ if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
+ mHighlightPathBogus = true;
- if (o >= 0 || n >= 0) {
- invalidateCursor(Selection.getSelectionEnd(buf), o, n);
- }
+ if (Selection.getSelectionStart(buf) >= 0) {
+ invalidateCursor();
}
+ }
+ }
- if (what instanceof UpdateLayout ||
- what instanceof ParagraphStyle) {
- invalidate();
- mHighlightPathBogus = true;
- checkForResize();
- }
+ private class ChangeWatcher
+ implements TextWatcher, SpanWatcher {
+ public void beforeTextChanged(CharSequence buffer, int start,
+ int before, int after) {
+ TextView.this.sendBeforeTextChanged(buffer, start, before, after);
+ }
- if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
- mHighlightPathBogus = true;
+ public void onTextChanged(CharSequence buffer, int start,
+ int before, int after) {
+ TextView.this.handleTextChanged(buffer, start, before, after);
+ }
- if (Selection.getSelectionStart(buf) >= 0) {
- invalidateCursor();
- }
- }
+ public void afterTextChanged(Editable buffer) {
+ TextView.this.sendAfterTextChanged(buffer);
+ TextView.this.reportExtractedText();
}
public void onSpanChanged(Spannable buf,
Object what, int s, int e, int st, int en) {
- spanChange(buf, what, s, st);
+ TextView.this.spanChange(buf, what, s, st);
}
public void onSpanAdded(Spannable buf, Object what, int s, int e) {
- spanChange(buf, what, -1, s);
+ TextView.this.spanChange(buf, what, -1, s);
}
public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
- spanChange(buf, what, s, -1);
+ TextView.this.spanChange(buf, what, s, -1);
}
}
@@ -4378,6 +5253,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ startStopMarquee(focused);
+
if (mTransformation != null) {
mTransformation.onFocusChanged(this, mText, focused, direction,
previouslyFocusedRect);
@@ -4386,6 +5263,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
+ @Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
@@ -4403,6 +5281,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mBlink.cancel();
}
}
+
+ startStopMarquee(hasWindowFocus);
+ }
+
+ @Override
+ public void setSelected(boolean selected) {
+ boolean wasSelected = isSelected();
+
+ super.setSelected(selected);
+
+ if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+ if (selected) {
+ startMarquee();
+ } else {
+ stopMarquee();
+ }
+ }
}
@Override
@@ -4421,7 +5316,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mMovement != null && mText instanceof Spannable &&
mLayout != null) {
- if (mMovement.onTouchEvent(this, (Spannable) mText, event)) {
+ boolean moved = mMovement.onTouchEvent(this, (Spannable) mText, event);
+
+ if (mText instanceof Editable
+ && mInputType != EditorInfo.TYPE_NULL) {
+ if (event.getAction() == MotionEvent.ACTION_UP && isFocused()) {
+ InputMethodManager imm = (InputMethodManager)
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(this);
+ }
+ }
+
+ if (moved) {
return true;
}
}
@@ -4490,6 +5396,52 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@Override
+ protected float getLeftFadingEdgeStrength() {
+ if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+ if (mMarquee != null && !mMarquee.isStopped()) {
+ final Marquee marquee = mMarquee;
+ return marquee.mScroll / getHorizontalFadingEdgeLength();
+ } else if (getLineCount() == 1) {
+ switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.LEFT:
+ return 0.0f;
+ case Gravity.RIGHT:
+ return (mLayout.getLineRight(0) - (mRight - mLeft) -
+ getCompoundPaddingLeft() - getCompoundPaddingRight() -
+ mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
+ case Gravity.CENTER_HORIZONTAL:
+ return 0.0f;
+ }
+ }
+ }
+ return super.getLeftFadingEdgeStrength();
+ }
+
+ @Override
+ protected float getRightFadingEdgeStrength() {
+ if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
+ if (mMarquee != null && !mMarquee.isStopped()) {
+ final Marquee marquee = mMarquee;
+ return (marquee.mMaxScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
+ } else if (getLineCount() == 1) {
+ switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.LEFT:
+ return (mLayout.getLineRight(0) - mScrollX - (mRight - mLeft) -
+ getCompoundPaddingLeft() - getCompoundPaddingRight()) /
+ getHorizontalFadingEdgeLength();
+ case Gravity.RIGHT:
+ return 0.0f;
+ case Gravity.CENTER_HORIZONTAL:
+ return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
+ getCompoundPaddingLeft() - getCompoundPaddingRight())) /
+ getHorizontalFadingEdgeLength();
+ }
+ }
+ }
+ return super.getRightFadingEdgeStrength();
+ }
+
+ @Override
protected int computeHorizontalScrollRange() {
if (mLayout != null)
return mLayout.getWidth();
@@ -4599,6 +5551,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private boolean canCut() {
+ if (mTransformation instanceof PasswordTransformationMethod) {
+ return false;
+ }
+
if (mText.length() > 0 && getSelectionStart() >= 0) {
if (mText instanceof Editable && mInput != null) {
return true;
@@ -4609,6 +5565,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private boolean canCopy() {
+ if (mTransformation instanceof PasswordTransformationMethod) {
+ return false;
+ }
+
if (mText.length() > 0 && getSelectionStart() >= 0) {
return true;
}
@@ -4632,8 +5592,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
protected void onCreateContextMenu(ContextMenu menu) {
super.onCreateContextMenu(menu);
+ boolean added = false;
if (!isFocused()) {
+ if (isFocusable() && mInput != null) {
+ if (canCopy()) {
+ MenuHandler handler = new MenuHandler();
+ int name = com.android.internal.R.string.copyAll;
+
+ menu.add(0, ID_COPY, 0, name).
+ setOnMenuItemClickListener(handler).
+ setAlphabeticShortcut('c');
+ menu.setHeaderTitle(com.android.internal.R.string.
+ editTextMenuTitle);
+ }
+ }
+
return;
}
@@ -4644,6 +5618,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
com.android.internal.R.string.selectAll).
setOnMenuItemClickListener(handler).
setAlphabeticShortcut('a');
+ added = true;
}
boolean selection = getSelectionStart() != getSelectionEnd();
@@ -4659,6 +5634,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
menu.add(0, ID_CUT, 0, name).
setOnMenuItemClickListener(handler).
setAlphabeticShortcut('x');
+ added = true;
}
if (canCopy()) {
@@ -4672,12 +5648,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
menu.add(0, ID_COPY, 0, name).
setOnMenuItemClickListener(handler).
setAlphabeticShortcut('c');
+ added = true;
}
if (canPaste()) {
menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
setOnMenuItemClickListener(handler).
setAlphabeticShortcut('v');
+ added = true;
}
if (mText instanceof Spanned) {
@@ -4693,15 +5671,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
menu.add(0, ID_COPY_URL, 0,
com.android.internal.R.string.copyUrl).
setOnMenuItemClickListener(handler);
+ added = true;
}
}
+
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null && imm.isActive(this)) {
+ menu.add(1, ID_SWITCH_IME, 0, com.android.internal.R.string.inputMethod).
+ setOnMenuItemClickListener(handler);
+ added = true;
+ }
+
+ if (added) {
+ menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
+ }
}
- private static final int ID_SELECT_ALL = 101;
- private static final int ID_CUT = 102;
- private static final int ID_COPY = 103;
- private static final int ID_PASTE = 104;
- private static final int ID_COPY_URL = 105;
+ private static final int ID_SELECT_ALL = com.android.internal.R.id.selectAll;
+ 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;
private class MenuHandler implements MenuItem.OnMenuItemClickListener {
public boolean onMenuItemClick(MenuItem item) {
@@ -4713,6 +5704,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
+ if (!isFocused()) {
+ selStart = 0;
+ selEnd = mText.length();
+ }
+
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
@@ -4769,7 +5765,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
return true;
- }
+
+ case ID_SWITCH_IME:
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.showInputMethodPicker();
+ }
+ return true;
+ }
return false;
}
@@ -4790,10 +5793,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private CharSequence mTransformed;
private BufferType mBufferType = BufferType.NORMAL;
+ private int mInputType = EditorInfo.TYPE_NULL;
private CharSequence mHint;
private Layout mHintLayout;
private KeyListener mInput;
+
private MovementMethod mMovement;
private TransformationMethod mTransformation;
private ChangeWatcher mChangeWatcher;
@@ -4842,7 +5847,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// tmp primitives, so we don't alloc them on each draw
private Path mHighlightPath;
private boolean mHighlightPathBogus = true;
- private static RectF sTempRect = new RectF();
+ private static final RectF sTempRect = new RectF();
// XXX should be much larger
private static final int VERY_WIDE = 16384;
@@ -4858,8 +5863,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private BoringLayout mSavedLayout, mSavedHintLayout;
-
-
private static final InputFilter[] NO_FILTERS = new InputFilter[0];
private InputFilter[] mFilters = NO_FILTERS;
private static final Spanned EMPTY_SPANNED = new SpannedString("");
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index da3c2aa..df40156 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -182,6 +182,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
try {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setOnPreparedListener(mPreparedListener);
+ mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
mIsPrepared = false;
mMediaPlayer.setOnCompletionListener(mCompletionListener);
mMediaPlayer.setOnErrorListener(mErrorListener);
@@ -220,6 +221,17 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
}
}
+ MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
+ new MediaPlayer.OnVideoSizeChangedListener() {
+ public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+ mVideoWidth = mp.getVideoWidth();
+ mVideoHeight = mp.getVideoHeight();
+ if (mVideoWidth != 0 && mVideoHeight != 0) {
+ getHolder().setFixedSize(mVideoWidth, mVideoHeight);
+ }
+ }
+ };
+
MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
public void onPrepared(MediaPlayer mp) {
// briefly show the mediacontroller
@@ -241,26 +253,32 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
// start the video here instead of in the callback.
if (mSeekWhenPrepared != 0) {
mMediaPlayer.seekTo(mSeekWhenPrepared);
+ mSeekWhenPrepared = 0;
}
if (mStartWhenPrepared) {
mMediaPlayer.start();
+ mStartWhenPrepared = false;
if (mMediaController != null) {
mMediaController.show();
}
- } else if (!isPlaying() && (mSeekWhenPrepared != 0 || getCurrentPosition() > 0)) {
+ } else if (!isPlaying() &&
+ (mSeekWhenPrepared != 0 || getCurrentPosition() > 0)) {
if (mMediaController != null) {
- mMediaController.show(0); // show the media controls when we're paused into a video and make 'em stick.
+ // Show the media controls when we're paused into a video and make 'em stick.
+ mMediaController.show(0);
}
}
}
} else {
- Log.d("VideoView", "Couldn't get video size after prepare(): " +
- mVideoWidth + "/" + mVideoHeight);
- // The file was probably truncated or corrupt. Start anyway, so
- // that we play whatever short snippet is there and then get
- // the "playback completed" event.
+ // We don't know the video size yet, but should start anyway.
+ // The video size might be reported to us later.
+ if (mSeekWhenPrepared != 0) {
+ mMediaPlayer.seekTo(mSeekWhenPrepared);
+ mSeekWhenPrepared = 0;
+ }
if (mStartWhenPrepared) {
mMediaPlayer.start();
+ mStartWhenPrepared = false;
}
}
}
@@ -370,9 +388,10 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
{
mSurfaceWidth = w;
mSurfaceHeight = h;
- if (mIsPrepared && mVideoWidth == w && mVideoHeight == h) {
+ if (mMediaPlayer != null && mIsPrepared && mVideoWidth == w && mVideoHeight == h) {
if (mSeekWhenPrepared != 0) {
mMediaPlayer.seekTo(mSeekWhenPrepared);
+ mSeekWhenPrepared = 0;
}
mMediaPlayer.start();
if (mMediaController != null) {
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index 53b9654..989f972 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -34,6 +34,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
+import android.view.WindowManager;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
@@ -68,23 +69,33 @@ public class AlertController {
private View mView;
- private Button mButton1;
+ private int mViewSpacingLeft;
+
+ private int mViewSpacingTop;
+
+ private int mViewSpacingRight;
+
+ private int mViewSpacingBottom;
+
+ private boolean mViewSpacingSpecified = false;
+
+ private Button mButtonPositive;
- private CharSequence mButton1Text;
+ private CharSequence mButtonPositiveText;
- private Message mButton1Message;
+ private Message mButtonPositiveMessage;
- private Button mButton2;
+ private Button mButtonNegative;
- private CharSequence mButton2Text;
+ private CharSequence mButtonNegativeText;
- private Message mButton2Message;
+ private Message mButtonNegativeMessage;
- private Button mButton3;
+ private Button mButtonNeutral;
- private CharSequence mButton3Text;
+ private CharSequence mButtonNeutralText;
- private Message mButton3Message;
+ private Message mButtonNeutralMessage;
private ScrollView mScrollView;
@@ -111,12 +122,12 @@ public class AlertController {
View.OnClickListener mButtonHandler = new View.OnClickListener() {
public void onClick(View v) {
Message m = null;
- if (v == mButton1 && mButton1Message != null) {
- m = Message.obtain(mButton1Message);
- } else if (v == mButton2 && mButton2Message != null) {
- m = Message.obtain(mButton2Message);
- } else if (v == mButton3 && mButton3Message != null) {
- m = Message.obtain(mButton3Message);
+ if (v == mButtonPositive && mButtonPositiveMessage != null) {
+ m = Message.obtain(mButtonPositiveMessage);
+ } else if (v == mButtonNegative && mButtonNegativeMessage != null) {
+ m = Message.obtain(mButtonNegativeMessage);
+ } else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
+ m = Message.obtain(mButtonNeutralMessage);
}
if (m != null) {
m.sendToTarget();
@@ -142,9 +153,9 @@ public class AlertController {
public void handleMessage(Message msg) {
switch (msg.what) {
- case DialogInterface.BUTTON1:
- case DialogInterface.BUTTON2:
- case DialogInterface.BUTTON3:
+ case DialogInterface.BUTTON_POSITIVE:
+ case DialogInterface.BUTTON_NEGATIVE:
+ case DialogInterface.BUTTON_NEUTRAL:
((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
break;
@@ -165,6 +176,10 @@ public class AlertController {
/* We use a custom title so never request a window title */
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+ if (mView == null) {
+ mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ }
mWindow.setContentView(com.android.internal.R.layout.alert_dialog);
setupView();
}
@@ -191,66 +206,64 @@ public class AlertController {
}
/**
- * Set the view to display in that dialog.
+ * Set the view to display in the dialog.
*/
public void setView(View view) {
mView = view;
+ mViewSpacingSpecified = false;
}
-
- public void setButton(CharSequence text, Message msg) {
- mButton1Text = text;
- mButton1Message = msg;
- }
-
- public void setButton2(CharSequence text, Message msg) {
- mButton2Text = text;
- mButton2Message = msg;
- }
-
- public void setButton3(CharSequence text, Message msg) {
- mButton3Text = text;
- mButton3Message = msg;
- }
-
+
/**
- * Set a listener to be invoked when button 1 of the dialog is pressed.
- * @param text The text to display in button 1.
- * @param listener The {@link DialogInterface.OnClickListener} to use.
+ * Set the view to display in the dialog along with the spacing around that view
*/
- public void setButton(CharSequence text, final DialogInterface.OnClickListener listener) {
- mButton1Text = text;
- if (listener != null) {
- mButton1Message = mHandler.obtainMessage(DialogInterface.BUTTON1, listener);
- } else {
- mButton1Message = null;
- }
+ public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
+ int viewSpacingBottom) {
+ mView = view;
+ mViewSpacingSpecified = true;
+ mViewSpacingLeft = viewSpacingLeft;
+ mViewSpacingTop = viewSpacingTop;
+ mViewSpacingRight = viewSpacingRight;
+ mViewSpacingBottom = viewSpacingBottom;
}
/**
- * Set a listener to be invoked when button 2 of the dialog is pressed.
- * @param text The text to display in button 2.
+ * Sets a click listener or a message to be sent when the button is clicked.
+ * You only need to pass one of {@code listener} or {@code msg}.
+ *
+ * @param whichButton Which button, can be one of
+ * {@link DialogInterface#BUTTON_POSITIVE},
+ * {@link DialogInterface#BUTTON_NEGATIVE}, or
+ * {@link DialogInterface#BUTTON_NEUTRAL}
+ * @param text The text to display in positive button.
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ * @param msg The {@link Message} to be sent when clicked.
*/
- public void setButton2(CharSequence text, final DialogInterface.OnClickListener listener) {
- mButton2Text = text;
- if (listener != null) {
- mButton2Message = mHandler.obtainMessage(DialogInterface.BUTTON2, listener);
- } else {
- mButton2Message = null;
+ public void setButton(int whichButton, CharSequence text,
+ DialogInterface.OnClickListener listener, Message msg) {
+
+ if (msg == null && listener != null) {
+ msg = mHandler.obtainMessage(whichButton, listener);
}
- }
+
+ switch (whichButton) {
- /**
- * Set a listener to be invoked when button 3 of the dialog is pressed.
- * @param text The text to display in button 3.
- * @param listener The {@link DialogInterface.OnClickListener} to use.
- */
- public void setButton3(CharSequence text, final DialogInterface.OnClickListener listener) {
- mButton3Text = text;
- if (listener != null) {
- mButton3Message = mHandler.obtainMessage(DialogInterface.BUTTON3, listener);
- } else {
- mButton3Message = null;
+ case DialogInterface.BUTTON_POSITIVE:
+ mButtonPositiveText = text;
+ mButtonPositiveMessage = msg;
+ break;
+
+ case DialogInterface.BUTTON_NEGATIVE:
+ mButtonNegativeText = text;
+ mButtonNegativeMessage = msg;
+ break;
+
+ case DialogInterface.BUTTON_NEUTRAL:
+ mButtonNeutralText = text;
+ mButtonNeutralMessage = msg;
+ break;
+
+ default:
+ throw new IllegalArgumentException("Button does not exist");
}
}
@@ -285,6 +298,19 @@ public class AlertController {
return mListView;
}
+ public Button getButton(int whichButton) {
+ switch (whichButton) {
+ case DialogInterface.BUTTON_POSITIVE:
+ return mButtonPositiveMessage != null ? mButtonPositive : null;
+ case DialogInterface.BUTTON_NEGATIVE:
+ return mButtonNegativeMessage != null ? mButtonNegative : null;
+ case DialogInterface.BUTTON_NEUTRAL:
+ return mButtonNeutralMessage != null ? mButtonNeutral : null;
+ default:
+ return null;
+ }
+ }
+
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (mScrollView != null && mScrollView.executeKeyEvent(event)) return true;
return false;
@@ -303,7 +329,7 @@ public class AlertController {
LinearLayout topPanel = (LinearLayout) mWindow.findViewById(R.id.topPanel);
TypedArray a = mContext.obtainStyledAttributes(
null, com.android.internal.R.styleable.AlertDialog, com.android.internal.R.attr.alertDialogStyle, 0);
- boolean hasTitle = setupTitle(topPanel, hasButtons);
+ boolean hasTitle = setupTitle(topPanel);
View buttonPanel = mWindow.findViewById(R.id.buttonPanel);
if (!hasButtons) {
@@ -315,6 +341,10 @@ public class AlertController {
customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
custom.addView(mView, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
+ if (mViewSpacingSpecified) {
+ custom.setPadding(mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
+ mViewSpacingBottom);
+ }
} else {
mWindow.findViewById(R.id.customPanel).setVisibility(View.GONE);
}
@@ -331,7 +361,7 @@ public class AlertController {
a.recycle();
}
- private boolean setupTitle(LinearLayout topPanel, boolean hasButtons) {
+ private boolean setupTitle(LinearLayout topPanel) {
boolean hasTitle = true;
if (mCustomTitleView != null) {
@@ -352,40 +382,13 @@ public class AlertController {
/* Display the title if a title is supplied, else hide it */
mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
-
+
mTitleView.setText(mTitle);
-
- /* The title font size and icon varies depending on
- * what else is displayed within the dialog.
- */
- if (mListView != null) {
-
- /* If a ListView is displayed then ensure the title
- * is only 1 line and use a special icon.
- */
- mTitleView.setSingleLine();
- mTitleView.setEllipsize(TruncateAt.END);
- mIconView.setImageResource(
- R.drawable.ic_dialog_menu_generic);
- } else if ((mMessage != null) && hasButtons) {
-
- /* Has a message and buttons, we want the title to
- * be a single line but large.
- */
- mTitleView.setSingleLine();
- mTitleView.setEllipsize(TruncateAt.END);
- mTitleView.setTextSize(getLargeTextSize());
- } else {
-
- /* We have a Title and buttons or we have title,
- * and custom content. In either case the layout
- * handles it so do nothing.
- */
- }
+ mIconView.setImageResource(R.drawable.ic_dialog_menu_generic);
/* Do this last so that if the user has supplied any
* icons we use them instead of the default ones. If the
- * user has specified 0 then make it dissapear.
+ * user has specified 0 then make it disappear.
*/
if (mIconId > 0) {
mIconView.setImageResource(mIconId);
@@ -414,17 +417,6 @@ public class AlertController {
return hasTitle;
}
- private int getLargeTextSize() {
- TypedArray a =
- mContext.obtainStyledAttributes(
- R.style.TextAppearance_Large,
- R.styleable.TextAppearance);
- int textSize = a.getDimensionPixelSize(
- R.styleable.TextAppearance_textSize, 22);
- a.recycle();
- return textSize;
- }
-
private void setupContent(LinearLayout contentPanel) {
mScrollView = (ScrollView) mWindow.findViewById(R.id.scrollView);
mScrollView.setFocusable(false);
@@ -452,65 +444,65 @@ public class AlertController {
private boolean setupButtons() {
View defaultButton = null;
- int BUTTON1 = 1;
- int BUTTON2 = 2;
- int BUTTON3 = 4;
- int whichButton = 0;
- mButton1 = (Button) mWindow.findViewById(R.id.button1);
- mButton1.setOnClickListener(mButtonHandler);
-
- if (TextUtils.isEmpty(mButton1Text)) {
- mButton1.setVisibility(View.GONE);
+ int BIT_BUTTON_POSITIVE = 1;
+ int BIT_BUTTON_NEGATIVE = 2;
+ int BIT_BUTTON_NEUTRAL = 4;
+ int whichButtons = 0;
+ mButtonPositive = (Button) mWindow.findViewById(R.id.button1);
+ mButtonPositive.setOnClickListener(mButtonHandler);
+
+ if (TextUtils.isEmpty(mButtonPositiveText)) {
+ mButtonPositive.setVisibility(View.GONE);
} else {
- mButton1.setText(mButton1Text);
- mButton1.setVisibility(View.VISIBLE);
- defaultButton = mButton1;
- whichButton = whichButton | BUTTON1;
+ mButtonPositive.setText(mButtonPositiveText);
+ mButtonPositive.setVisibility(View.VISIBLE);
+ defaultButton = mButtonPositive;
+ whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
}
- mButton2 = (Button) mWindow.findViewById(R.id.button2);
- mButton2.setOnClickListener(mButtonHandler);
+ mButtonNegative = (Button) mWindow.findViewById(R.id.button2);
+ mButtonNegative.setOnClickListener(mButtonHandler);
- if (TextUtils.isEmpty(mButton2Text)) {
- mButton2.setVisibility(View.GONE);
+ if (TextUtils.isEmpty(mButtonNegativeText)) {
+ mButtonNegative.setVisibility(View.GONE);
} else {
- mButton2.setText(mButton2Text);
- mButton2.setVisibility(View.VISIBLE);
+ mButtonNegative.setText(mButtonNegativeText);
+ mButtonNegative.setVisibility(View.VISIBLE);
if (defaultButton == null) {
- defaultButton = mButton2;
+ defaultButton = mButtonNegative;
}
- whichButton = whichButton | BUTTON2;
+ whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
}
- mButton3 = (Button) mWindow.findViewById(R.id.button3);
- mButton3.setOnClickListener(mButtonHandler);
+ mButtonNeutral = (Button) mWindow.findViewById(R.id.button3);
+ mButtonNeutral.setOnClickListener(mButtonHandler);
- if (TextUtils.isEmpty(mButton3Text)) {
- mButton3.setVisibility(View.GONE);
+ if (TextUtils.isEmpty(mButtonNeutralText)) {
+ mButtonNeutral.setVisibility(View.GONE);
} else {
- mButton3.setText(mButton3Text);
- mButton3.setVisibility(View.VISIBLE);
+ mButtonNeutral.setText(mButtonNeutralText);
+ mButtonNeutral.setVisibility(View.VISIBLE);
if (defaultButton == null) {
- defaultButton = mButton3;
+ defaultButton = mButtonNeutral;
}
- whichButton = whichButton | BUTTON3;
+ whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
}
/*
* If we only have 1 button it should be centered on the layout and
* expand to fill 50% of the available space.
*/
- if (whichButton == BUTTON1) {
- centerButton(mButton1);
- } else if (whichButton == BUTTON2) {
- centerButton(mButton3);
- } else if (whichButton == BUTTON3) {
- centerButton(mButton3);
+ if (whichButtons == BIT_BUTTON_POSITIVE) {
+ centerButton(mButtonPositive);
+ } else if (whichButtons == BIT_BUTTON_NEGATIVE) {
+ centerButton(mButtonNeutral);
+ } else if (whichButtons == BIT_BUTTON_NEUTRAL) {
+ centerButton(mButtonNeutral);
}
- return whichButton != 0;
+ return whichButtons != 0;
}
private void centerButton(Button button) {
@@ -677,6 +669,11 @@ public class AlertController {
public ListAdapter mAdapter;
public DialogInterface.OnClickListener mOnClickListener;
public View mView;
+ public int mViewSpacingLeft;
+ public int mViewSpacingTop;
+ public int mViewSpacingRight;
+ public int mViewSpacingBottom;
+ public boolean mViewSpacingSpecified = false;
public boolean[] mCheckedItems;
public boolean mIsMultiChoice;
public boolean mIsSingleChoice;
@@ -726,13 +723,16 @@ public class AlertController {
dialog.setMessage(mMessage);
}
if (mPositiveButtonText != null) {
- dialog.setButton(mPositiveButtonText, mPositiveButtonListener);
+ dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
+ mPositiveButtonListener, null);
}
if (mNegativeButtonText != null) {
- dialog.setButton2(mNegativeButtonText, mNegativeButtonListener);
+ dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
+ mNegativeButtonListener, null);
}
if (mNeutralButtonText != null) {
- dialog.setButton3(mNeutralButtonText, mNeutralButtonListener);
+ dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
+ mNeutralButtonListener, null);
}
if (mForceInverseBackground) {
dialog.setInverseBackgroundForced(true);
@@ -743,7 +743,12 @@ public class AlertController {
createListView(dialog);
}
if (mView != null) {
- dialog.setView(mView);
+ if (mViewSpacingSpecified) {
+ dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
+ mViewSpacingBottom);
+ } else {
+ dialog.setView(mView);
+ }
}
/*
@@ -764,8 +769,7 @@ public class AlertController {
adapter = new ArrayAdapter<CharSequence>(
mContext, R.layout.select_dialog_multichoice, R.id.text1, mItems) {
@Override
- public View getView(int position, View convertView,
- ViewGroup parent) {
+ public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
if (mCheckedItems != null) {
boolean isItemChecked = mCheckedItems[position];
@@ -778,24 +782,27 @@ public class AlertController {
};
} else {
adapter = new CursorAdapter(mContext, mCursor, false) {
-
+ private final int mLabelIndex;
+ private final int mIsCheckedIndex;
+
+ {
+ final Cursor cursor = getCursor();
+ mLabelIndex = cursor.getColumnIndexOrThrow(mLabelColumn);
+ mIsCheckedIndex = cursor.getColumnIndexOrThrow(mIsCheckedColumn);
+ }
+
@Override
- public void bindView(View view, Context context,
- Cursor cursor) {
+ public void bindView(View view, Context context, Cursor cursor) {
CheckedTextView text = (CheckedTextView) view.findViewById(R.id.text1);
- text.setText(cursor.getString(cursor.getColumnIndexOrThrow(mLabelColumn)));
+ text.setText(cursor.getString(mLabelIndex));
+ listView.setItemChecked(cursor.getPosition(),
+ cursor.getInt(mIsCheckedIndex) == 1);
}
@Override
- public View newView(Context context, Cursor cursor,
- ViewGroup parent) {
- View view = mInflater.inflate(
- R.layout.select_dialog_multichoice, parent, false);
- bindView(view, context, cursor);
- if (cursor.getInt(cursor.getColumnIndexOrThrow(mIsCheckedColumn)) == 1) {
- listView.setItemChecked(cursor.getPosition(), true);
- }
- return view;
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return mInflater.inflate(R.layout.select_dialog_multichoice,
+ parent, false);
}
};
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index bb9fa0b..434850c 100755..100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -16,9 +16,14 @@
package com.android.internal.app;
+import com.android.internal.os.BatteryStatsImpl;
+
interface IBatteryStats {
+ BatteryStatsImpl 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 setOnBattery(boolean onBattery);
long getAwakeTimeBattery();
long getAwakeTimePlugged();
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index d5a9f8c..e907a04 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -17,9 +17,6 @@
package com.android.internal.app;
import com.android.internal.R;
-
-import android.app.Activity;
-import android.app.ListActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
@@ -33,19 +30,17 @@ import android.os.Bundle;
import android.os.PatternMatcher;
import android.util.Config;
import android.util.Log;
-import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
-import android.view.Window;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
-import android.widget.ListView;
import android.widget.TextView;
-
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -55,35 +50,37 @@ import java.util.Set;
* which there is more than one matching activity, allowing the user to decide
* which to go to. It is not normally used directly by application developers.
*/
-public class ResolverActivity extends AlertActivity implements
+public class ResolverActivity extends AlertActivity implements
DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {
private ResolveListAdapter mAdapter;
private CheckBox mAlwaysCheck;
private TextView mClearDefaultHint;
-
+ private PackageManager mPm;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
onCreate(savedInstanceState, new Intent(getIntent()),
getResources().getText(com.android.internal.R.string.whichApplication),
true);
}
-
+
protected void onCreate(Bundle savedInstanceState, Intent intent,
CharSequence title, boolean alwaysUseOption) {
super.onCreate(savedInstanceState);
-
+ mPm = getPackageManager();
intent.setComponent(null);
AlertController.AlertParams ap = mAlertParams;
-
+
ap.mTitle = title;
ap.mOnClickListener = this;
-
+
if (alwaysUseOption) {
LayoutInflater inflater = (LayoutInflater) getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
ap.mView = inflater.inflate(R.layout.always_use_checkbox, null);
mAlwaysCheck = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
+ mAlwaysCheck.setText(R.string.alwaysUse);
mAlwaysCheck.setOnCheckedChangeListener(this);
mClearDefaultHint = (TextView)ap.mView.findViewById(
com.android.internal.R.id.clearDefaultHint);
@@ -99,10 +96,10 @@ public class ResolverActivity extends AlertActivity implements
} else {
ap.mMessage = getResources().getText(com.android.internal.R.string.noApplications);
}
-
+
setupAlert();
}
-
+
public void onClick(DialogInterface dialog, int which) {
ResolveInfo ri = mAdapter.resolveInfoForPosition(which);
Intent intent = mAdapter.intentForPosition(which);
@@ -110,7 +107,7 @@ public class ResolverActivity extends AlertActivity implements
if ((mAlwaysCheck != null) && mAlwaysCheck.isChecked()) {
// Build a reasonable intent filter, based on what matched.
IntentFilter filter = new IntentFilter();
-
+
if (intent.getAction() != null) {
filter.addAction(intent.getAction());
}
@@ -121,7 +118,7 @@ public class ResolverActivity extends AlertActivity implements
}
}
filter.addCategory(Intent.CATEGORY_DEFAULT);
-
+
int cat = ri.match&IntentFilter.MATCH_CATEGORY_MASK;
Uri data = intent.getData();
if (cat == IntentFilter.MATCH_CATEGORY_TYPE) {
@@ -136,7 +133,7 @@ public class ResolverActivity extends AlertActivity implements
}
} else if (data != null && data.getScheme() != null) {
filter.addDataScheme(data.getScheme());
-
+
// Look through the resolved filter to determine which part
// of it matched the original Intent.
Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator();
@@ -163,13 +160,13 @@ public class ResolverActivity extends AlertActivity implements
}
}
}
-
+
if (filter != null) {
final int N = mAdapter.mList.size();
ComponentName[] set = new ComponentName[N];
int bestMatch = 0;
for (int i=0; i<N; i++) {
- ResolveInfo r = mAdapter.mList.get(i);
+ ResolveInfo r = mAdapter.mList.get(i).ri;
set[i] = new ComponentName(r.activityInfo.packageName,
r.activityInfo.name);
if (r.match > bestMatch) bestMatch = r.match;
@@ -178,51 +175,135 @@ public class ResolverActivity extends AlertActivity implements
intent.getComponent());
}
}
-
+
if (intent != null) {
startActivity(intent);
}
finish();
}
-
+
+ private final class DisplayResolveInfo {
+ ResolveInfo ri;
+ CharSequence displayLabel;
+ CharSequence extendedInfo;
+
+ DisplayResolveInfo(ResolveInfo pri, CharSequence pLabel, CharSequence pInfo) {
+ ri = pri;
+ displayLabel = pLabel;
+ extendedInfo = pInfo;
+ }
+ }
+
private final class ResolveListAdapter extends BaseAdapter {
private final Intent mIntent;
private final LayoutInflater mInflater;
- private List<ResolveInfo> mList;
-
+ private List<DisplayResolveInfo> mList;
+
public ResolveListAdapter(Context context, Intent intent) {
mIntent = new Intent(intent);
mIntent.setComponent(null);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- PackageManager pm = context.getPackageManager();
- mList = pm.queryIntentActivities(
+ List<ResolveInfo> rList = mPm.queryIntentActivities(
intent, PackageManager.MATCH_DEFAULT_ONLY
| (mAlwaysCheck != null ? PackageManager.GET_RESOLVED_FILTER : 0));
- if (mList != null) {
- int N = mList.size();
+ int N;
+ if ((rList != null) && ((N = rList.size()) > 0)) {
+ // Only display the first matches that are either of equal
+ // priority or have asked to be default options.
+ ResolveInfo r0 = rList.get(0);
+ for (int i=1; i<N; i++) {
+ ResolveInfo ri = rList.get(i);
+ if (Config.LOGV) Log.v(
+ "ResolveListActivity",
+ r0.activityInfo.name + "=" +
+ r0.priority + "/" + r0.isDefault + " vs " +
+ ri.activityInfo.name + "=" +
+ ri.priority + "/" + ri.isDefault);
+ if (r0.priority != ri.priority ||
+ r0.isDefault != ri.isDefault) {
+ while (i < N) {
+ rList.remove(i);
+ N--;
+ }
+ }
+ }
if (N > 1) {
- // Only display the first matches that are either of equal
- // priority or have asked to be default options.
- ResolveInfo r0 = mList.get(0);
- for (int i=1; i<N; i++) {
- ResolveInfo ri = mList.get(i);
- if (Config.LOGV) Log.v(
- "ResolveListActivity",
- r0.activityInfo.name + "=" +
- r0.priority + "/" + r0.isDefault + " vs " +
- ri.activityInfo.name + "=" +
- ri.priority + "/" + ri.isDefault);
- if (r0.priority != ri.priority ||
- r0.isDefault != ri.isDefault) {
- while (i < N) {
- mList.remove(i);
- N--;
- }
+ ResolveInfo.DisplayNameComparator rComparator =
+ new ResolveInfo.DisplayNameComparator(mPm);
+ Collections.sort(rList, rComparator);
+ }
+ // Check for applications with same name and use application name or
+ // package name if necessary
+ mList = new ArrayList<DisplayResolveInfo>();
+ r0 = rList.get(0);
+ int start = 0;
+ CharSequence r0Label = r0.loadLabel(mPm);
+ for (int i = 1; i < N; i++) {
+ if (r0Label == null) {
+ r0Label = r0.activityInfo.packageName;
+ }
+ ResolveInfo ri = rList.get(i);
+ CharSequence riLabel = ri.loadLabel(mPm);
+ if (riLabel == null) {
+ riLabel = ri.activityInfo.packageName;
+ }
+ if (riLabel.equals(r0Label)) {
+ continue;
+ }
+ processGroup(rList, start, (i-1), r0, r0Label);
+ r0 = ri;
+ r0Label = riLabel;
+ start = i;
+ }
+ // Process last group
+ processGroup(rList, start, (N-1), r0, r0Label);
+ }
+ }
+
+ private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro,
+ CharSequence roLabel) {
+ // Process labels from start to i
+ int num = end - start+1;
+ if (num == 1) {
+ // No duplicate labels. Use label for entry at start
+ mList.add(new DisplayResolveInfo(ro, roLabel, null));
+ } else {
+ boolean usePkg = false;
+ CharSequence startApp = ro.activityInfo.applicationInfo.loadLabel(mPm);
+ if (startApp == null) {
+ usePkg = true;
+ }
+ if (!usePkg) {
+ // Use HashSet to track duplicates
+ HashSet<CharSequence> duplicates =
+ new HashSet<CharSequence>();
+ duplicates.add(startApp);
+ for (int j = start+1; j <= end ; j++) {
+ ResolveInfo jRi = rList.get(j);
+ CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm);
+ if ( (jApp == null) || (duplicates.contains(jApp))) {
+ usePkg = true;
+ break;
+ } else {
+ duplicates.add(jApp);
}
}
- Collections.sort(mList, new ResolveInfo.DisplayNameComparator(pm));
+ // Clear HashSet for later use
+ duplicates.clear();
+ }
+ for (int k = start; k <= end; k++) {
+ ResolveInfo add = rList.get(k);
+ if (usePkg) {
+ // Use application name for all entries from start to end-1
+ mList.add(new DisplayResolveInfo(add, roLabel,
+ add.activityInfo.packageName));
+ } else {
+ // Use package name for all entries from start to end-1
+ mList.add(new DisplayResolveInfo(add, roLabel,
+ add.activityInfo.applicationInfo.loadLabel(mPm)));
+ }
}
}
}
@@ -232,7 +313,7 @@ public class ResolverActivity extends AlertActivity implements
return null;
}
- return mList.get(position);
+ return mList.get(position).ri;
}
public Intent intentForPosition(int position) {
@@ -243,7 +324,7 @@ public class ResolverActivity extends AlertActivity implements
Intent intent = new Intent(mIntent);
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
|Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
- ActivityInfo ai = mList.get(position).activityInfo;
+ ActivityInfo ai = mList.get(position).ri.activityInfo;
intent.setComponent(new ComponentName(
ai.applicationInfo.packageName, ai.name));
return intent;
@@ -273,22 +354,24 @@ public class ResolverActivity extends AlertActivity implements
return view;
}
- private final void bindView(View view, ResolveInfo info) {
+ private final void bindView(View view, DisplayResolveInfo info) {
TextView text = (TextView)view.findViewById(com.android.internal.R.id.text1);
+ TextView text2 = (TextView)view.findViewById(com.android.internal.R.id.text2);
ImageView icon = (ImageView)view.findViewById(R.id.icon);
-
- PackageManager pm = getPackageManager();
-
- CharSequence label = info.loadLabel(pm);
- if (label == null) label = info.activityInfo.name;
- text.setText(label);
- icon.setImageDrawable(info.loadIcon(pm));
+ text.setText(info.displayLabel);
+ if (info.extendedInfo != null) {
+ text2.setVisibility(View.VISIBLE);
+ text2.setText(info.extendedInfo);
+ } else {
+ text2.setVisibility(View.GONE);
+ }
+ icon.setImageDrawable(info.ri.loadIcon(mPm));
}
}
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (mClearDefaultHint == null) return;
-
+
if(isChecked) {
mClearDefaultHint.setVisibility(View.VISIBLE);
} else {
diff --git a/core/java/com/android/internal/app/RingtonePickerActivity.java b/core/java/com/android/internal/app/RingtonePickerActivity.java
index 63fe952..9c83aa3 100644
--- a/core/java/com/android/internal/app/RingtonePickerActivity.java
+++ b/core/java/com/android/internal/app/RingtonePickerActivity.java
@@ -139,6 +139,9 @@ public final class RingtonePickerActivity extends AlertActivity implements
}
mCursor = mRingtoneManager.getCursor();
+
+ // The volume keys will control the stream that we are choosing a ringtone for
+ setVolumeControlStream(mRingtoneManager.inferStreamType());
// Get the URI whose list item should have a checkmark
mExistingUri = intent
diff --git a/core/java/com/android/internal/app/UsbStorageActivity.java b/core/java/com/android/internal/app/UsbStorageActivity.java
index 5b2bde0..b8a2136 100644
--- a/core/java/com/android/internal/app/UsbStorageActivity.java
+++ b/core/java/com/android/internal/app/UsbStorageActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 The Android Open Source Project
+ * 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.
@@ -61,7 +61,7 @@ public class UsbStorageActivity extends AlertActivity implements DialogInterface
p.mPositiveButtonText = getString(com.android.internal.R.string.usb_storage_button_mount);
p.mPositiveButtonListener = this;
p.mNegativeButtonText = getString(com.android.internal.R.string.usb_storage_button_unmount);
- p.mPositiveButtonListener = this;
+ p.mNegativeButtonListener = this;
setupAlert();
}
diff --git a/core/java/com/android/internal/database/ArrayListCursor.java b/core/java/com/android/internal/database/ArrayListCursor.java
index bcbcc41..2e1d8f1 100644
--- a/core/java/com/android/internal/database/ArrayListCursor.java
+++ b/core/java/com/android/internal/database/ArrayListCursor.java
@@ -17,6 +17,7 @@
package com.android.internal.database;
import android.database.AbstractCursor;
+import android.database.CursorWindow;
import java.lang.System;
import java.util.ArrayList;
@@ -25,105 +26,146 @@ import java.util.ArrayList;
* A convenience class that presents a two-dimensional ArrayList
* as a Cursor.
*/
-public class ArrayListCursor extends AbstractCursor
-{
- public ArrayListCursor(String[] columnNames, ArrayList<ArrayList> rows)
- {
+public class ArrayListCursor extends AbstractCursor {
+ private String[] mColumnNames;
+ private ArrayList<Object>[] mRows;
+
+ @SuppressWarnings({"unchecked"})
+ public ArrayListCursor(String[] columnNames, ArrayList<ArrayList> rows) {
int colCount = columnNames.length;
boolean foundID = false;
// Add an _id column if not in columnNames
- for (int i=0; i<colCount; ++i) {
+ for (int i = 0; i < colCount; ++i) {
if (columnNames[i].compareToIgnoreCase("_id") == 0) {
mColumnNames = columnNames;
foundID = true;
break;
}
}
-
+
if (!foundID) {
mColumnNames = new String[colCount + 1];
System.arraycopy(columnNames, 0, mColumnNames, 0, columnNames.length);
mColumnNames[colCount] = "_id";
}
-
+
int rowCount = rows.size();
mRows = new ArrayList[rowCount];
-
- for (int i = 0; i<rowCount; ++i) {
+
+ for (int i = 0; i < rowCount; ++i) {
mRows[i] = rows.get(i);
if (!foundID) {
- mRows[i].add(Long.valueOf(i));
+ mRows[i].add(i);
}
}
+ }
- }
+ @Override
+ public void fillWindow(int position, CursorWindow window) {
+ if (position < 0 || position > getCount()) {
+ return;
+ }
+
+ window.acquireReference();
+ try {
+ int oldpos = mPos;
+ mPos = position - 1;
+ window.clear();
+ window.setStartPosition(position);
+ int columnNum = getColumnCount();
+ window.setNumColumns(columnNum);
+ while (moveToNext() && window.allocRow()) {
+ for (int i = 0; i < columnNum; i++) {
+ final Object data = mRows[mPos].get(i);
+ if (data != null) {
+ if (data instanceof byte[]) {
+ byte[] field = (byte[]) data;
+ if (!window.putBlob(field, mPos, i)) {
+ window.freeLastRow();
+ break;
+ }
+ } else {
+ String field = data.toString();
+ if (!window.putString(field, mPos, i)) {
+ window.freeLastRow();
+ break;
+ }
+ }
+ } else {
+ if (!window.putNull(mPos, i)) {
+ window.freeLastRow();
+ break;
+ }
+ }
+ }
+ }
+
+ mPos = oldpos;
+ } catch (IllegalStateException e){
+ // simply ignore it
+ } finally {
+ window.releaseReference();
+ }
+ }
@Override
- public int getCount()
- {
+ public int getCount() {
return mRows.length;
}
@Override
- public boolean deleteRow()
- {
+ public boolean deleteRow() {
return false;
}
@Override
- public String[] getColumnNames()
- {
+ public String[] getColumnNames() {
return mColumnNames;
}
@Override
- public String getString(int columnIndex)
- {
+ public byte[] getBlob(int columnIndex) {
+ return (byte[]) mRows[mPos].get(columnIndex);
+ }
+
+ @Override
+ public String getString(int columnIndex) {
Object cell = mRows[mPos].get(columnIndex);
return (cell == null) ? null : cell.toString();
}
-
+
@Override
- public short getShort(int columnIndex)
- {
- Number num = (Number)mRows[mPos].get(columnIndex);
+ public short getShort(int columnIndex) {
+ Number num = (Number) mRows[mPos].get(columnIndex);
return num.shortValue();
}
@Override
- public int getInt(int columnIndex)
- {
- Number num = (Number)mRows[mPos].get(columnIndex);
+ public int getInt(int columnIndex) {
+ Number num = (Number) mRows[mPos].get(columnIndex);
return num.intValue();
}
@Override
- public long getLong(int columnIndex)
- {
- Number num = (Number)mRows[mPos].get(columnIndex);
+ public long getLong(int columnIndex) {
+ Number num = (Number) mRows[mPos].get(columnIndex);
return num.longValue();
}
@Override
- public float getFloat(int columnIndex)
- {
- Number num = (Number)mRows[mPos].get(columnIndex);
+ public float getFloat(int columnIndex) {
+ Number num = (Number) mRows[mPos].get(columnIndex);
return num.floatValue();
}
@Override
- public double getDouble(int columnIndex)
- {
- Number num = (Number)mRows[mPos].get(columnIndex);
+ public double getDouble(int columnIndex) {
+ Number num = (Number) mRows[mPos].get(columnIndex);
return num.doubleValue();
}
@Override
- public boolean isNull(int columnIndex)
- {
+ public boolean isNull(int columnIndex) {
return mRows[mPos].get(columnIndex) == null;
}
-
- private String[] mColumnNames;
- private ArrayList<Object>[] mRows;
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.aidl b/core/java/com/android/internal/os/BatteryStatsImpl.aidl
new file mode 100644
index 0000000..0c26a3c
--- /dev/null
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+parcelable BatteryStatsImpl;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
new file mode 100644
index 0000000..8912960
--- /dev/null
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -0,0 +1,1661 @@
+/*
+ * Copyright (C) 2006-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;
+
+import android.os.BatteryStats;
+import android.os.Parcel;
+import android.os.ParcelFormatException;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
+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;
+
+/**
+ * All information we are collecting about things that can happen that impact
+ * battery life. All times are represented in microseconds except where indicated
+ * otherwise.
+ */
+public final class BatteryStatsImpl extends BatteryStats implements Parcelable {
+
+ // 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 final File mFile;
+ private final File mBackupFile;
+
+ /**
+ * The statistics we have collected organized by uids.
+ */
+ final SparseArray<BatteryStatsImpl.Uid> mUidStats =
+ new SparseArray<BatteryStatsImpl.Uid>();
+
+ // A set of pools of currently active timers. When a timer is queried, we will divide the
+ // elapsed time by the number of active timers to arrive at that timer's share of the time.
+ // In order to do this, we must refresh each timer whenever the number of active timers
+ // changes.
+ 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>();
+
+ int mStartCount;
+
+ long mBatteryUptime;
+ long mBatteryLastUptime;
+ long mBatteryRealtime;
+ long mBatteryLastRealtime;
+
+ long mUptime;
+ long mUptimeStart;
+ long mLastUptime;
+ long mRealtime;
+ long mRealtimeStart;
+ long mLastRealtime;
+
+ /**
+ * These provide time bases that discount the time the device is plugged
+ * in to power.
+ */
+ boolean mOnBattery;
+ long mTrackBatteryPastUptime;
+ long mTrackBatteryUptimeStart;
+ long mTrackBatteryPastRealtime;
+ long mTrackBatteryRealtimeStart;
+
+ long mLastWriteTime = 0; // Milliseconds
+
+ // For debugging
+ public BatteryStatsImpl() {
+ mFile = mBackupFile = null;
+ }
+
+ /**
+ * State for keeping track of timing information.
+ */
+ public static final class Timer extends BatteryStats.Timer {
+ ArrayList<Timer> mTimerPool;
+
+ int mType;
+ int mNesting;
+
+ int mCount;
+ int mLoadedCount;
+ int mLastCount;
+
+ // Times are in microseconds for better accuracy when dividing by the lock count
+
+ long mTotalTime; // Add mUnpluggedTotalTime to get true value
+ long mLoadedTotalTime;
+ long mLastTotalTime;
+ long mStartTime;
+ long mUpdateTime;
+
+ /**
+ * The value of mTotalTime when unplug() was last called, initially 0.
+ */
+ long mTotalTimeAtLastUnplug;
+
+ /** Constructor used for unmarshalling only. */
+ Timer() {}
+
+ Timer(int type, ArrayList<Timer> timerPool) {
+ mType = type;
+ mTimerPool = timerPool;
+ }
+
+ public void writeToParcel(Parcel out) {
+ out.writeInt(mType);
+ out.writeInt(mNesting);
+ out.writeInt(mCount);
+ out.writeInt(mLoadedCount);
+ out.writeInt(mLastCount);
+ out.writeLong(mTotalTime);
+ out.writeLong(mLoadedTotalTime);
+ out.writeLong(mLastTotalTime);
+ out.writeLong(mStartTime);
+ out.writeLong(mUpdateTime);
+ out.writeLong(mTotalTimeAtLastUnplug);
+ }
+
+ 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;
+ }
+
+ /**
+ * 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) {
+ if (timer == null) {
+ out.writeInt(0); // indicates null
+ return;
+ }
+ out.writeInt(1); // indicates non-null
+
+ timer.writeToParcel(out);
+ }
+
+ @Override
+ public long getTotalTime(long now, int which) {
+ long val;
+ if (which == STATS_LAST) {
+ val = mLastTotalTime;
+ } else {
+ val = computeRunTimeLocked(now);
+ if (which != STATS_UNPLUGGED) {
+ val += mTotalTimeAtLastUnplug;
+ }
+ if ((which == STATS_CURRENT) || (which == STATS_UNPLUGGED)) {
+ val -= mLoadedTotalTime;
+ }
+ }
+
+ return val;
+ }
+
+ @Override
+ public int getCount(int which) {
+ int val;
+ if (which == STATS_LAST) {
+ val = mLastCount;
+ } else {
+ val = mCount;
+ if ((which == STATS_CURRENT) || (which == STATS_UNPLUGGED)) {
+ val -= mLoadedCount;
+ }
+ }
+
+ return val;
+ }
+
+ 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);
+ // Increment the count
+ mCount++;
+ }
+ }
+
+ void stopRunningLocked(BatteryStatsImpl stats) {
+ // Ignore attempt to stop a timer that isn't running
+ if (mNesting == 0) {
+ 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--;
+ }
+ }
+
+ // 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;
+ }
+ mUpdateTime = stats.getBatteryUptimeLocked(realtime);
+ }
+
+ private long computeRunTimeLocked(long curBatteryUptime) {
+ return mTotalTime +
+ (mNesting > 0 ? ((curBatteryUptime * 1000) - mUpdateTime) / mCount : 0);
+ }
+
+ void writeSummaryFromParcelLocked(Parcel out, long curBatteryUptime) {
+ long runTime = computeRunTimeLocked(curBatteryUptime);
+ // Divide by 1000 for backwards compatibility
+ out.writeLong((runTime + 500) / 1000);
+ out.writeLong(((runTime - mLoadedTotalTime) + 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();
+ mCount = mLoadedCount = in.readInt();
+ mLastCount = in.readInt();
+ mNesting = 0;
+ }
+ }
+
+ public void unplugTimers() {
+ ArrayList<Timer> timers;
+
+ timers = mPartialTimers;
+ for (int i = timers.size() - 1; i >= 0; i--) {
+ timers.get(i).unplug();
+ }
+ timers = mFullTimers;
+ for (int i = timers.size() - 1; i >= 0; i--) {
+ timers.get(i).unplug();
+ }
+ timers = mWindowTimers;
+ for (int i = timers.size() - 1; i >= 0; i--) {
+ timers.get(i).unplug();
+ }
+ timers = mSensorTimers;
+ for (int i = timers.size() - 1; i >= 0; i--) {
+ timers.get(i).unplug();
+ }
+ }
+
+ @Override
+ public SparseArray<? extends BatteryStats.Uid> getUidStats() {
+ return mUidStats;
+ }
+
+ /**
+ * The statistics associated with a particular uid.
+ */
+ public final class Uid extends BatteryStats.Uid {
+
+ /**
+ * The statistics we have collected for this uid's wake locks.
+ */
+ final HashMap<String, Wakelock> mWakelockStats = new HashMap<String, Wakelock>();
+
+ /**
+ * The statistics we have collected for this uid's sensor activations.
+ */
+ final HashMap<Integer, Sensor> mSensorStats = new HashMap<Integer, Sensor>();
+
+ /**
+ * The statistics we have collected for this uid's processes.
+ */
+ final HashMap<String, Proc> mProcessStats = new HashMap<String, Proc>();
+
+ /**
+ * The statistics we have collected for this uid's processes.
+ */
+ final HashMap<String, Pkg> mPackageStats = new HashMap<String, Pkg>();
+
+ @Override
+ public Map<String, ? extends BatteryStats.Uid.Wakelock> getWakelockStats() {
+ return mWakelockStats;
+ }
+
+ @Override
+ public Map<Integer, ? extends BatteryStats.Uid.Sensor> getSensorStats() {
+ return mSensorStats;
+ }
+
+ @Override
+ public Map<String, ? extends BatteryStats.Uid.Proc> getProcessStats() {
+ return mProcessStats;
+ }
+
+ @Override
+ public Map<String, ? extends BatteryStats.Uid.Pkg> getPackageStats() {
+ return mPackageStats;
+ }
+
+ void writeToParcelLocked(Parcel out) {
+ 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);
+ }
+
+ 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);
+ }
+
+ out.writeInt(mProcessStats.size());
+ for (Map.Entry<String, Uid.Proc> procEntry : mProcessStats.entrySet()) {
+ out.writeString(procEntry.getKey());
+ Uid.Proc proc = procEntry.getValue();
+ proc.writeToParcelLocked(out);
+ }
+
+ out.writeInt(mPackageStats.size());
+ for (Map.Entry<String, Uid.Pkg> pkgEntry : mPackageStats.entrySet()) {
+ out.writeString(pkgEntry.getKey());
+ Uid.Pkg pkg = pkgEntry.getValue();
+ pkg.writeToParcelLocked(out);
+ }
+ }
+
+ void readFromParcelLocked(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);
+ mWakelockStats.put(wakelockName, wakelock);
+ }
+
+ int numSensors = in.readInt();
+ mSensorStats.clear();
+ for (int k = 0; k < numSensors; k++) {
+ int sensorNumber = in.readInt();
+ Uid.Sensor sensor = new Sensor();
+ sensor.readFromParcelLocked(in);
+ mSensorStats.put(sensorNumber, sensor);
+ }
+
+ int numProcs = in.readInt();
+ mProcessStats.clear();
+ for (int k = 0; k < numProcs; k++) {
+ String processName = in.readString();
+ Uid.Proc proc = new Proc();
+ proc.readFromParcelLocked(in);
+ mProcessStats.put(processName, proc);
+ }
+
+ int numPkgs = in.readInt();
+ mPackageStats.clear();
+ for (int l = 0; l < numPkgs; l++) {
+ String packageName = in.readString();
+ Uid.Pkg pkg = new Pkg();
+ pkg.readFromParcelLocked(in);
+ mPackageStats.put(packageName, pkg);
+ }
+ }
+
+ /**
+ * The statistics associated with a particular wake lock.
+ */
+ public final class Wakelock extends BatteryStats.Uid.Wakelock {
+ /**
+ * How long (in ms) this uid has been keeping the device partially awake.
+ */
+ Timer wakeTimePartial;
+
+ /**
+ * How long (in ms) this uid has been keeping the device fully awake.
+ */
+ Timer wakeTimeFull;
+
+ /**
+ * How long (in ms) this uid has had a window keeping the device awake.
+ */
+ Timer wakeTimeWindow;
+
+ /**
+ * Reads a possibly null Timer from a Parcel. The timer is associated with the
+ * proper timer pool from the given BatteryStatsImpl object.
+ *
+ * @param in the Parcel to be read from.
+ * return a new Timer, or null.
+ */
+ private Timer readTimerFromParcel(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;
+ }
+
+ void readFromParcelLocked(Parcel in) {
+ wakeTimePartial = readTimerFromParcel(in);
+ wakeTimeFull = readTimerFromParcel(in);
+ wakeTimeWindow = readTimerFromParcel(in);
+ }
+
+ void writeToParcelLocked(Parcel out) {
+ Timer.writeTimerToParcel(out, wakeTimePartial);
+ Timer.writeTimerToParcel(out, wakeTimeFull);
+ Timer.writeTimerToParcel(out, wakeTimeWindow);
+ }
+
+ @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;
+ default: throw new IllegalArgumentException("type = " + type);
+ }
+ }
+ }
+
+ public final class Sensor extends BatteryStats.Uid.Sensor {
+ Timer sensorTime;
+
+ private Timer readTimerFromParcel(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);
+ }
+ return timer;
+ }
+
+ void readFromParcelLocked(Parcel in) {
+ sensorTime = readTimerFromParcel(in);
+ }
+
+ void writeToParcelLocked(Parcel out) {
+ Timer.writeTimerToParcel(out, sensorTime);
+ }
+
+ @Override
+ public Timer getSensorTime() {
+ return sensorTime;
+ }
+ }
+
+ /**
+ * The statistics associated with a particular process.
+ */
+ public final class Proc extends BatteryStats.Uid.Proc {
+ /**
+ * Total time (in 1/100 sec) spent executing in user code.
+ */
+ long mUserTime;
+
+ /**
+ * Total time (in 1/100 sec) spent executing in kernel code.
+ */
+ long mSystemTime;
+
+ /**
+ * Number of times the process has been started.
+ */
+ int mStarts;
+
+ /**
+ * The amount of user time loaded from a previous save.
+ */
+ long mLoadedUserTime;
+
+ /**
+ * The amount of system time loaded from a previous save.
+ */
+ long mLoadedSystemTime;
+
+ /**
+ * The number of times the process has started from a previous save.
+ */
+ int mLoadedStarts;
+
+ /**
+ * The amount of user time loaded from the previous run.
+ */
+ long mLastUserTime;
+
+ /**
+ * The amount of system time loaded from the previous run.
+ */
+ long mLastSystemTime;
+
+ /**
+ * The number of times the process has started from the previous run.
+ */
+ int mLastStarts;
+
+ void writeToParcelLocked(Parcel out) {
+ out.writeLong(mUserTime);
+ out.writeLong(mSystemTime);
+ out.writeInt(mStarts);
+ out.writeLong(mLoadedUserTime);
+ out.writeLong(mLoadedSystemTime);
+ out.writeInt(mLoadedStarts);
+ out.writeLong(mLastUserTime);
+ out.writeLong(mLastSystemTime);
+ out.writeInt(mLastStarts);
+ }
+
+ void readFromParcelLocked(Parcel in) {
+ mUserTime = in.readLong();
+ mSystemTime = in.readLong();
+ mStarts = in.readInt();
+ mLoadedUserTime = in.readLong();
+ mLoadedSystemTime = in.readLong();
+ mLoadedStarts = in.readInt();
+ mLastUserTime = in.readLong();
+ mLastSystemTime = in.readLong();
+ mLastStarts = in.readInt();
+ }
+
+ public BatteryStatsImpl getBatteryStats() {
+ return BatteryStatsImpl.this;
+ }
+
+ public void addCpuTimeLocked(int utime, int stime) {
+ mUserTime += utime;
+ mSystemTime += stime;
+ }
+
+ public void incStartsLocked() {
+ mStarts++;
+ }
+
+ @Override
+ public long getUserTime(int which) {
+ long val;
+ if (which == STATS_LAST) {
+ val = mLastUserTime;
+ } else {
+ val = mUserTime;
+ if (which == STATS_CURRENT) {
+ val -= mLoadedUserTime;
+ }
+ }
+ return val;
+ }
+
+ @Override
+ public long getSystemTime(int which) {
+ long val;
+ if (which == STATS_LAST) {
+ val = mLastSystemTime;
+ } else {
+ val = mSystemTime;
+ if (which == STATS_CURRENT) {
+ val -= mLoadedSystemTime;
+ }
+ }
+ return val;
+ }
+
+ @Override
+ public int getStarts(int which) {
+ int val;
+ if (which == STATS_LAST) {
+ val = mLastStarts;
+ } else {
+ val = mStarts;
+ if (which == STATS_CURRENT) {
+ val -= mLoadedStarts;
+ }
+ }
+ return val;
+ }
+ }
+
+ /**
+ * The statistics associated with a particular package.
+ */
+ public final class Pkg extends BatteryStats.Uid.Pkg {
+ /**
+ * Number of times this package has done something that could wake up the
+ * device from sleep.
+ */
+ int mWakeups;
+
+ /**
+ * Number of things that could wake up the device loaded from a
+ * previous save.
+ */
+ int mLoadedWakeups;
+
+ /**
+ * Number of things that could wake up the device as of the
+ * last run.
+ */
+ int mLastWakeups;
+
+ /**
+ * The statics we have collected for this package's services.
+ */
+ final HashMap<String, Serv> mServiceStats = new HashMap<String, Serv>();
+
+ void readFromParcelLocked(Parcel in) {
+ mWakeups = in.readInt();
+ mLoadedWakeups = in.readInt();
+ mLastWakeups = in.readInt();
+
+ int numServs = in.readInt();
+ mServiceStats.clear();
+ for (int m = 0; m < numServs; m++) {
+ String serviceName = in.readString();
+ Uid.Pkg.Serv serv = new Serv();
+ mServiceStats.put(serviceName, serv);
+
+ serv.readFromParcelLocked(in);
+ }
+ }
+
+ void writeToParcelLocked(Parcel out) {
+ out.writeInt(mWakeups);
+ out.writeInt(mLoadedWakeups);
+ out.writeInt(mLastWakeups);
+
+ out.writeInt(mServiceStats.size());
+ for (Map.Entry<String, Uid.Pkg.Serv> servEntry : mServiceStats.entrySet()) {
+ out.writeString(servEntry.getKey());
+ Uid.Pkg.Serv serv = servEntry.getValue();
+
+ serv.writeToParcelLocked(out);
+ }
+ }
+
+ @Override
+ public Map<String, ? extends BatteryStats.Uid.Pkg.Serv> getServiceStats() {
+ return mServiceStats;
+ }
+
+ @Override
+ public int getWakeups(int which) {
+ int val;
+ if (which == STATS_LAST) {
+ val = mLastWakeups;
+ } else {
+ val = mWakeups;
+ if (which == STATS_CURRENT) {
+ val -= mLoadedWakeups;
+ }
+ }
+
+ return val;
+ }
+
+ /**
+ * The statistics associated with a particular service.
+ */
+ public final class Serv extends BatteryStats.Uid.Pkg.Serv {
+ /**
+ * Total time (ms) the service has been left started.
+ */
+ long mStartTime;
+
+ /**
+ * If service has been started and not yet stopped, this is
+ * when it was started.
+ */
+ long mRunningSince;
+
+ /**
+ * True if we are currently running.
+ */
+ boolean mRunning;
+
+ /**
+ * Total number of times startService() has been called.
+ */
+ int mStarts;
+
+ /**
+ * Total time (ms) the service has been left launched.
+ */
+ long mLaunchedTime;
+
+ /**
+ * If service has been launched and not yet exited, this is
+ * when it was launched.
+ */
+ long mLaunchedSince;
+
+ /**
+ * True if we are currently launched.
+ */
+ boolean mLaunched;
+
+ /**
+ * Total number times the service has been launched.
+ */
+ int mLaunches;
+
+ /**
+ * The amount of time spent started loaded from a previous save.
+ */
+ long mLoadedStartTime;
+
+ /**
+ * The number of starts loaded from a previous save.
+ */
+ int mLoadedStarts;
+
+ /**
+ * The number of launches loaded from a previous save.
+ */
+ int mLoadedLaunches;
+
+ /**
+ * The amount of time spent started as of the last run.
+ */
+ long mLastStartTime;
+
+ /**
+ * The number of starts as of the last run.
+ */
+ int mLastStarts;
+
+ /**
+ * The number of launches as of the last run.
+ */
+ int mLastLaunches;
+
+ void readFromParcelLocked(Parcel in) {
+ mStartTime = in.readLong();
+ mRunningSince = in.readLong();
+ mRunning = in.readInt() != 0;
+ mStarts = in.readInt();
+ mLaunchedTime = in.readLong();
+ mLaunchedSince = in.readLong();
+ mLaunched = in.readInt() != 0;
+ mLaunches = in.readInt();
+ mLoadedStartTime = in.readLong();
+ mLoadedStarts = in.readInt();
+ mLoadedLaunches = in.readInt();
+ mLastStartTime = in.readLong();
+ mLastStarts = in.readInt();
+ mLastLaunches = in.readInt();
+ }
+
+ void writeToParcelLocked(Parcel out) {
+ out.writeLong(mStartTime);
+ out.writeLong(mRunningSince);
+ out.writeInt(mRunning ? 1 : 0);
+ out.writeInt(mStarts);
+ out.writeLong(mLaunchedTime);
+ out.writeLong(mLaunchedSince);
+ out.writeInt(mLaunched ? 1 : 0);
+ out.writeInt(mLaunches);
+ out.writeLong(mLoadedStartTime);
+ out.writeInt(mLoadedStarts);
+ out.writeInt(mLoadedLaunches);
+ out.writeLong(mLastStartTime);
+ out.writeInt(mLastStarts);
+ out.writeInt(mLastLaunches);
+ }
+
+ long getLaunchTimeToNowLocked(long batteryUptime) {
+ if (!mLaunched) return mLaunchedTime;
+ return mLaunchedTime + batteryUptime - mLaunchedSince;
+ }
+
+ long getStartTimeToNowLocked(long batteryUptime) {
+ if (!mRunning) return mStartTime;
+ return mStartTime + batteryUptime - mRunningSince;
+ }
+
+ public void startLaunchedLocked() {
+ if (!mLaunched) {
+ mLaunches++;
+ mLaunchedSince = getBatteryUptimeLocked();
+ mLaunched = true;
+ }
+ }
+
+ public void stopLaunchedLocked() {
+ if (mLaunched) {
+ long time = getBatteryUptimeLocked() - mLaunchedSince;
+ if (time > 0) {
+ mLaunchedTime += time;
+ } else {
+ mLaunches--;
+ }
+ mLaunched = false;
+ }
+ }
+
+ public void startRunningLocked() {
+ if (!mRunning) {
+ mStarts++;
+ mRunningSince = getBatteryUptimeLocked();
+ mRunning = true;
+ }
+ }
+
+ public void stopRunningLocked() {
+ if (mRunning) {
+ long time = getBatteryUptimeLocked() - mRunningSince;
+ if (time > 0) {
+ mStartTime += time;
+ } else {
+ mStarts--;
+ }
+ mRunning = false;
+ }
+ }
+
+ public BatteryStatsImpl getBatteryStats() {
+ return BatteryStatsImpl.this;
+ }
+
+ @Override
+ public int getLaunches(int which) {
+ int val;
+
+ if (which == STATS_LAST) {
+ val = mLastLaunches;
+ } else {
+ val = mLaunches;
+ if (which == STATS_CURRENT) {
+ val -= mLoadedLaunches;
+ }
+ }
+
+ return val;
+ }
+
+ @Override
+ public long getStartTime(long now, int which) {
+ long val;
+ if (which == STATS_LAST) {
+ val = mLastStartTime;
+ } else {
+ val = getStartTimeToNowLocked(now);
+ if (which == STATS_CURRENT) {
+ val -= mLoadedStartTime;
+ }
+ }
+
+ return val;
+ }
+
+ @Override
+ public int getStarts(int which) {
+ int val;
+ if (which == STATS_LAST) {
+ val = mLastStarts;
+ } else {
+ val = mStarts;
+ if (which == STATS_CURRENT) {
+ val -= mLoadedStarts;
+ }
+ }
+
+ return val;
+ }
+ }
+
+ public BatteryStatsImpl getBatteryStats() {
+ return BatteryStatsImpl.this;
+ }
+
+ public void incWakeupsLocked() {
+ mWakeups++;
+ }
+
+ final Serv newServiceStatsLocked() {
+ return new Serv();
+ }
+ }
+
+ /**
+ * Retrieve the statistics object for a particular process, creating
+ * if needed.
+ */
+ public Proc getProcessStatsLocked(String name) {
+ Proc ps = mProcessStats.get(name);
+ if (ps == null) {
+ ps = new Proc();
+ mProcessStats.put(name, ps);
+ }
+
+ return ps;
+ }
+
+ /**
+ * Retrieve the statistics object for a particular service, creating
+ * if needed.
+ */
+ public Pkg getPackageStatsLocked(String name) {
+ Pkg ps = mPackageStats.get(name);
+ if (ps == null) {
+ ps = new Pkg();
+ mPackageStats.put(name, ps);
+ }
+
+ return ps;
+ }
+
+ /**
+ * Retrieve the statistics object for a particular service, creating
+ * if needed.
+ */
+ public Pkg.Serv getServiceStatsLocked(String pkg, String serv) {
+ Pkg ps = getPackageStatsLocked(pkg);
+ Pkg.Serv ss = ps.mServiceStats.get(serv);
+ if (ss == null) {
+ ss = ps.newServiceStatsLocked();
+ ps.mServiceStats.put(serv, ss);
+ }
+
+ return ss;
+ }
+
+ public Timer getWakeTimerLocked(String name, int type) {
+ Wakelock wl = mWakelockStats.get(name);
+ if (wl == null) {
+ wl = new Wakelock();
+ mWakelockStats.put(name, wl);
+ }
+ Timer t = null;
+ switch (type) {
+ case WAKE_TYPE_PARTIAL:
+ t = wl.wakeTimePartial;
+ if (t == null) {
+ t = new Timer(WAKE_TYPE_PARTIAL, mPartialTimers);
+ wl.wakeTimePartial = t;
+ }
+ return t;
+ case WAKE_TYPE_FULL:
+ t = wl.wakeTimeFull;
+ if (t == null) {
+ t = new Timer(WAKE_TYPE_FULL, mFullTimers);
+ wl.wakeTimeFull = t;
+ }
+ return t;
+ case WAKE_TYPE_WINDOW:
+ t = wl.wakeTimeWindow;
+ if (t == null) {
+ t = new Timer(WAKE_TYPE_WINDOW, mWindowTimers);
+ wl.wakeTimeWindow = t;
+ }
+ return t;
+ default:
+ throw new IllegalArgumentException("type=" + type);
+ }
+ }
+
+ public Timer getSensorTimerLocked(int sensor, boolean create) {
+ Integer sId = Integer.valueOf(sensor);
+ Sensor se = mSensorStats.get(sId);
+ if (se == null) {
+ if (!create) {
+ return null;
+ }
+ se = new Sensor();
+ mSensorStats.put(sId, se);
+ }
+ Timer t = se.sensorTime;
+ if (t == null) {
+ t = new Timer(0, mSensorTimers);
+ se.sensorTime = t;
+ }
+ return t;
+ }
+
+ public void noteStartWakeLocked(String name, int type) {
+ Timer t = getWakeTimerLocked(name, type);
+ if (t != null) {
+ t.startRunningLocked(BatteryStatsImpl.this);
+ }
+ }
+
+ public void noteStopWakeLocked(String name, int type) {
+ Timer t = getWakeTimerLocked(name, type);
+ if (t != null) {
+ t.stopRunningLocked(BatteryStatsImpl.this);
+ }
+ }
+
+ public void noteStartSensor(int sensor) {
+ Timer t = getSensorTimerLocked(sensor, true);
+ if (t != null) {
+ t.startRunningLocked(BatteryStatsImpl.this);
+ }
+ }
+
+ public void noteStopSensor(int sensor) {
+ // Don't create a timer if one doesn't already exist
+ Timer t = getSensorTimerLocked(sensor, false);
+ if (t != null) {
+ t.stopRunningLocked(BatteryStatsImpl.this);
+ }
+ }
+
+ public BatteryStatsImpl getBatteryStats() {
+ return BatteryStatsImpl.this;
+ }
+ }
+
+ public BatteryStatsImpl(String filename) {
+ mFile = new File(filename);
+ mBackupFile = new File(filename + ".bak");
+ mStartCount++;
+ mOnBattery = true;
+ mTrackBatteryPastUptime = 0;
+ mTrackBatteryPastRealtime = 0;
+ mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000;
+ mRealtimeStart = mTrackBatteryRealtimeStart = SystemClock.elapsedRealtime() * 1000;
+ }
+
+ public BatteryStatsImpl(Parcel p) {
+ mFile = mBackupFile = null;
+ readFromParcel(p);
+ }
+
+ @Override
+ public int getStartCount() {
+ return mStartCount;
+ }
+
+ public boolean isOnBattery() {
+ return mOnBattery;
+ }
+
+ public void setOnBattery(boolean onBattery) {
+ synchronized(this) {
+ if (mOnBattery != onBattery) {
+ long uptime = SystemClock.uptimeMillis() * 1000;
+ long mSecRealtime = SystemClock.elapsedRealtime();
+ long realtime = mSecRealtime * 1000;
+ if (onBattery) {
+ mTrackBatteryUptimeStart = uptime;
+ mTrackBatteryRealtimeStart = realtime;
+ unplugTimers();
+ } else {
+ mTrackBatteryPastUptime += uptime - mTrackBatteryUptimeStart;
+ mTrackBatteryPastRealtime += realtime - mTrackBatteryRealtimeStart;
+ }
+ mOnBattery = onBattery;
+ if ((mLastWriteTime + (60 * 1000)) < mSecRealtime) {
+ if (mFile != null) {
+ writeLocked();
+ }
+ }
+ }
+ }
+ }
+
+ public long getAwakeTimeBattery() {
+ return computeBatteryUptime(getBatteryUptimeLocked(), STATS_CURRENT);
+ }
+
+ public long getAwakeTimePlugged() {
+ return (SystemClock.uptimeMillis() * 1000) - getAwakeTimeBattery();
+ }
+
+ @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);
+ }
+ return 0;
+ }
+
+ @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);
+ }
+ return 0;
+ }
+
+ @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);
+ }
+ return 0;
+ }
+
+ @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);
+ }
+ return 0;
+ }
+
+ long getBatteryUptimeLocked(long curTime) {
+ long time = mTrackBatteryPastUptime;
+ if (mOnBattery) {
+ time += curTime - mTrackBatteryUptimeStart;
+ }
+ return time;
+ }
+
+ long getBatteryUptimeLocked() {
+ return getBatteryUptime(SystemClock.uptimeMillis() * 1000);
+ }
+
+ @Override
+ public long getBatteryUptime(long curTime) {
+ return getBatteryUptimeLocked(curTime);
+ }
+
+ long getBatteryRealtimeLocked(long curTime) {
+ long time = mTrackBatteryPastRealtime;
+ if (mOnBattery) {
+ time += curTime - mTrackBatteryRealtimeStart;
+ }
+ return time;
+ }
+
+ @Override
+ public long getBatteryRealtime(long curTime) {
+ return getBatteryRealtimeLocked(curTime);
+ }
+
+ /**
+ * Retrieve the statistics object for a particular uid, creating if needed.
+ */
+ public Uid getUidStatsLocked(int uid) {
+ Uid u = mUidStats.get(uid);
+ if (u == null) {
+ u = new Uid();
+ mUidStats.put(uid, u);
+ }
+ return u;
+ }
+
+ /**
+ * Remove the statistics object for a particular uid.
+ */
+ public void removeUidStatsLocked(int uid) {
+ mUidStats.remove(uid);
+ }
+
+ /**
+ * Retrieve the statistics object for a particular process, creating
+ * if needed.
+ */
+ public Uid.Proc getProcessStatsLocked(int uid, String name) {
+ Uid u = getUidStatsLocked(uid);
+ return u.getProcessStatsLocked(name);
+ }
+
+ /**
+ * Retrieve the statistics object for a particular process, creating
+ * if needed.
+ */
+ public Uid.Pkg getPackageStatsLocked(int uid, String pkg) {
+ Uid u = getUidStatsLocked(uid);
+ return u.getPackageStatsLocked(pkg);
+ }
+
+ /**
+ * Retrieve the statistics object for a particular service, creating
+ * if needed.
+ */
+ public Uid.Pkg.Serv getServiceStatsLocked(int uid, String pkg, String name) {
+ Uid u = getUidStatsLocked(uid);
+ return u.getServiceStatsLocked(pkg, name);
+ }
+
+ public void writeLocked() {
+ if ((mFile == null) || (mBackupFile == null)) {
+ Log.w("BatteryStats", "writeLocked: no file associated with this instance");
+ return;
+ }
+
+ // Keep the old file around until we know the new one has
+ // been successfully written.
+ if (mFile.exists()) {
+ if (mBackupFile.exists()) {
+ mBackupFile.delete();
+ }
+ mFile.renameTo(mBackupFile);
+ }
+
+ try {
+ FileOutputStream stream = new FileOutputStream(mFile);
+ Parcel out = Parcel.obtain();
+ writeSummaryToParcel(out);
+ stream.write(out.marshall());
+ out.recycle();
+
+ stream.flush();
+ stream.close();
+ mBackupFile.delete();
+
+ mLastWriteTime = SystemClock.elapsedRealtime();
+ } catch (IOException e) {
+ Log.e("BatteryStats", "Error writing battery statistics", e);
+ }
+ }
+
+ static byte[] readFully(FileInputStream stream) throws java.io.IOException {
+ int pos = 0;
+ int avail = stream.available();
+ byte[] data = new byte[avail];
+ while (true) {
+ int amt = stream.read(data, pos, data.length-pos);
+ //Log.i("foo", "Read " + amt + " bytes at " + pos
+ // + " of avail " + data.length);
+ if (amt <= 0) {
+ //Log.i("foo", "**** FINISHED READING: pos=" + pos
+ // + " len=" + data.length);
+ return data;
+ }
+ pos += amt;
+ avail = stream.available();
+ if (avail > data.length-pos) {
+ byte[] newData = new byte[pos+avail];
+ System.arraycopy(data, 0, newData, 0, pos);
+ data = newData;
+ }
+ }
+ }
+
+ public void readLocked() {
+ if ((mFile == null) || (mBackupFile == null)) {
+ Log.w("BatteryStats", "readLocked: no file associated with this instance");
+ return;
+ }
+
+ mUidStats.clear();
+
+ FileInputStream stream = null;
+ if (mBackupFile.exists()) {
+ try {
+ stream = new FileInputStream(mBackupFile);
+ } catch (java.io.IOException e) {
+ // We'll try for the normal settings file.
+ }
+ }
+
+ try {
+ if (stream == null) {
+ if (!mFile.exists()) {
+ return;
+ }
+ stream = new FileInputStream(mFile);
+ }
+
+ byte[] raw = readFully(stream);
+ Parcel in = Parcel.obtain();
+ in.unmarshall(raw, 0, raw.length);
+ in.setDataPosition(0);
+ stream.close();
+
+ readSummaryFromParcel(in);
+ } catch(java.io.IOException e) {
+ Log.e("BatteryStats", "Error reading battery statistics", e);
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ private void readSummaryFromParcel(Parcel in) {
+ final int version = in.readInt();
+ if (version != VERSION) {
+ Log.e("BatteryStats", "readFromParcel: version got " + version
+ + ", expected " + VERSION);
+ return;
+ }
+
+ mStartCount = in.readInt();
+ mBatteryUptime = in.readLong();
+ mBatteryLastUptime = in.readLong();
+ mBatteryRealtime = in.readLong();
+ mBatteryLastRealtime = in.readLong();
+ mUptime = in.readLong();
+ mLastUptime = in.readLong();
+ mRealtime = in.readLong();
+ mLastRealtime = in.readLong();
+ mStartCount++;
+
+ final int NU = in.readInt();
+ for (int iu=0; iu<NU; iu++) {
+ int uid = in.readInt();
+ Uid u = new Uid();
+ mUidStats.put(uid, u);
+
+ int NW = in.readInt();
+ for (int iw=0; iw<NW; iw++) {
+ String wlName = in.readString();
+ if (in.readInt() != 0) {
+ u.getWakeTimerLocked(wlName, WAKE_TYPE_FULL).readSummaryFromParcelLocked(in);
+ }
+ if (in.readInt() != 0) {
+ u.getWakeTimerLocked(wlName, WAKE_TYPE_PARTIAL).readSummaryFromParcelLocked(in);
+ }
+ if (in.readInt() != 0) {
+ u.getWakeTimerLocked(wlName, WAKE_TYPE_WINDOW).readSummaryFromParcelLocked(in);
+ }
+ }
+
+ 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 ip=0; ip<NP; ip++) {
+ String procName = in.readString();
+ Uid.Proc p = u.getProcessStatsLocked(procName);
+ p.mUserTime = p.mLoadedUserTime = in.readLong();
+ p.mLastUserTime = in.readLong();
+ p.mSystemTime = p.mLoadedSystemTime = in.readLong();
+ p.mLastSystemTime = in.readLong();
+ p.mStarts = p.mLoadedStarts = in.readInt();
+ p.mLastStarts = in.readInt();
+ }
+
+ NP = in.readInt();
+ 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++) {
+ String servName = in.readString();
+ Uid.Pkg.Serv s = u.getServiceStatsLocked(pkgName, servName);
+ s.mStartTime = s.mLoadedStartTime = in.readLong();
+ s.mLastStartTime = in.readLong();
+ s.mStarts = s.mLoadedStarts = in.readInt();
+ s.mLastStarts = in.readInt();
+ s.mLaunches = s.mLoadedLaunches = in.readInt();
+ s.mLastLaunches = in.readInt();
+ }
+ }
+ }
+ }
+
+ /**
+ * Writes a summary of the statistics to a Parcel, in a format suitable to be written to
+ * disk. This format does not allow a lossless round-trip.
+ *
+ * @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;
+
+ out.writeInt(VERSION);
+
+ out.writeInt(mStartCount);
+ out.writeLong(computeBatteryUptime(NOW_SYS, STATS_TOTAL));
+ out.writeLong(computeBatteryUptime(NOW_SYS, STATS_CURRENT));
+ out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_TOTAL));
+ out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_CURRENT));
+ out.writeLong(computeUptime(NOW_SYS, STATS_TOTAL));
+ out.writeLong(computeUptime(NOW_SYS, STATS_CURRENT));
+ out.writeLong(computeRealtime(NOWREAL_SYS, STATS_TOTAL));
+ out.writeLong(computeRealtime(NOWREAL_SYS, STATS_CURRENT));
+
+ final int NU = mUidStats.size();
+ out.writeInt(NU);
+ for (int iu=0; iu<NU; iu++) {
+ out.writeInt(mUidStats.keyAt(iu));
+ Uid u = mUidStats.valueAt(iu);
+
+ int NW = u.mWakelockStats.size();
+ out.writeInt(NW);
+ if (NW > 0) {
+ for (Map.Entry<String, BatteryStatsImpl.Uid.Wakelock> ent
+ : u.mWakelockStats.entrySet()) {
+ out.writeString(ent.getKey());
+ Uid.Wakelock wl = ent.getValue();
+ if (wl.wakeTimeFull != null) {
+ out.writeInt(1);
+ wl.wakeTimeFull.writeSummaryFromParcelLocked(out, NOW);
+ } else {
+ out.writeInt(0);
+ }
+ if (wl.wakeTimePartial != null) {
+ out.writeInt(1);
+ wl.wakeTimePartial.writeSummaryFromParcelLocked(out, NOW);
+ } else {
+ out.writeInt(0);
+ }
+ if (wl.wakeTimeWindow != null) {
+ out.writeInt(1);
+ wl.wakeTimeWindow.writeSummaryFromParcelLocked(out, NOW);
+ } else {
+ out.writeInt(0);
+ }
+ }
+ }
+
+ int NSE = u.mSensorStats.size();
+ out.writeInt(NSE);
+ if (NSE > 0) {
+ for (Map.Entry<Integer, BatteryStatsImpl.Uid.Sensor> ent
+ : u.mSensorStats.entrySet()) {
+ out.writeInt(ent.getKey());
+ Uid.Sensor se = ent.getValue();
+ if (se.sensorTime != null) {
+ out.writeInt(1);
+ se.sensorTime.writeSummaryFromParcelLocked(out, NOW);
+ } else {
+ out.writeInt(0);
+ }
+ }
+ }
+
+ int NP = u.mProcessStats.size();
+ out.writeInt(NP);
+ if (NP > 0) {
+ for (Map.Entry<String, BatteryStatsImpl.Uid.Proc> ent
+ : u.mProcessStats.entrySet()) {
+ out.writeString(ent.getKey());
+ Uid.Proc ps = ent.getValue();
+ out.writeLong(ps.mUserTime);
+ out.writeLong(ps.mUserTime - ps.mLoadedUserTime);
+ out.writeLong(ps.mSystemTime);
+ out.writeLong(ps.mSystemTime - ps.mLoadedSystemTime);
+ out.writeInt(ps.mStarts);
+ out.writeInt(ps.mStarts - ps.mLoadedStarts);
+ }
+ }
+
+ NP = u.mPackageStats.size();
+ out.writeInt(NP);
+ if (NP > 0) {
+ for (Map.Entry<String, BatteryStatsImpl.Uid.Pkg> ent
+ : u.mPackageStats.entrySet()) {
+ out.writeString(ent.getKey());
+ Uid.Pkg ps = ent.getValue();
+ out.writeInt(ps.mWakeups);
+ out.writeInt(ps.mWakeups - ps.mLoadedWakeups);
+ final int NS = ps.mServiceStats.size();
+ out.writeInt(NS);
+ if (NS > 0) {
+ for (Map.Entry<String, BatteryStatsImpl.Uid.Pkg.Serv> sent
+ : ps.mServiceStats.entrySet()) {
+ out.writeString(sent.getKey());
+ BatteryStatsImpl.Uid.Pkg.Serv ss = sent.getValue();
+ long time = ss.getStartTimeToNowLocked(NOW);
+ out.writeLong(time);
+ out.writeLong(time - ss.mLoadedStartTime);
+ out.writeInt(ss.mStarts);
+ out.writeInt(ss.mStarts - ss.mLoadedStarts);
+ out.writeInt(ss.mLaunches);
+ out.writeInt(ss.mLaunches - ss.mLoadedLaunches);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public void readFromParcel(Parcel in) {
+ readFromParcelLocked(in);
+ }
+
+ void readFromParcelLocked(Parcel in) {
+ int magic = in.readInt();
+ if (magic != MAGIC) {
+ throw new ParcelFormatException("Bad magic number");
+ }
+
+ mStartCount = in.readInt();
+ mBatteryUptime = in.readLong();
+ mBatteryLastUptime = in.readLong();
+ mBatteryRealtime = in.readLong();
+ mBatteryLastRealtime = in.readLong();
+ mUptime = in.readLong();
+ mUptimeStart = in.readLong();
+ mLastUptime = in.readLong();
+ mRealtime = in.readLong();
+ mRealtimeStart = in.readLong();
+ mLastRealtime = in.readLong();
+ mOnBattery = in.readInt() != 0;
+ mTrackBatteryPastUptime = in.readLong();
+ mTrackBatteryUptimeStart = in.readLong();
+ mTrackBatteryPastRealtime = in.readLong();
+ mTrackBatteryRealtimeStart = in.readLong();
+ mLastWriteTime = in.readLong();
+
+ mPartialTimers.clear();
+ mFullTimers.clear();
+ mWindowTimers.clear();
+
+ 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);
+ }
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ writeToParcelLocked(out, flags);
+ }
+
+ @SuppressWarnings("unused")
+ void writeToParcelLocked(Parcel out, int flags) {
+ out.writeInt(MAGIC);
+ out.writeInt(mStartCount);
+ out.writeLong(mBatteryUptime);
+ out.writeLong(mBatteryLastUptime);
+ out.writeLong(mBatteryRealtime);
+ out.writeLong(mBatteryLastRealtime);
+ out.writeLong(mUptime);
+ out.writeLong(mUptimeStart);
+ out.writeLong(mLastUptime);
+ out.writeLong(mRealtime);
+ out.writeLong(mRealtimeStart);
+ out.writeLong(mLastRealtime);
+ out.writeInt(mOnBattery ? 1 : 0);
+ out.writeLong(mTrackBatteryPastUptime);
+ out.writeLong(mTrackBatteryUptimeStart);
+ out.writeLong(mTrackBatteryPastRealtime);
+ out.writeLong(mTrackBatteryRealtimeStart);
+ out.writeLong(mLastWriteTime);
+
+ int size = mUidStats.size();
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ out.writeInt(mUidStats.keyAt(i));
+ Uid uid = mUidStats.valueAt(i);
+
+ uid.writeToParcelLocked(out);
+ }
+ }
+
+ public static final Parcelable.Creator<BatteryStatsImpl> CREATOR =
+ new Parcelable.Creator<BatteryStatsImpl>() {
+ public BatteryStatsImpl createFromParcel(Parcel in) {
+ return new BatteryStatsImpl(in);
+ }
+
+ public BatteryStatsImpl[] newArray(int size) {
+ return new BatteryStatsImpl[size];
+ }
+ };
+}
diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
new file mode 100644
index 0000000..fd8fd5a
--- /dev/null
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -0,0 +1,166 @@
+package com.android.internal.os;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+public class HandlerCaller {
+ private static final String TAG = "HandlerCaller";
+ private static final boolean DEBUG = false;
+
+ public final Context mContext;
+
+ final Looper mMainLooper;
+ final Handler mH;
+
+ final Callback mCallback;
+
+ public static class SomeArgs {
+ SomeArgs next;
+
+ public Object arg1;
+ public Object arg2;
+ Object arg3;
+ public int argi1;
+ public int argi2;
+ public int argi3;
+ public int argi4;
+ }
+
+ static final int ARGS_POOL_MAX_SIZE = 10;
+ int mArgsPoolSize;
+ SomeArgs mArgsPool;
+
+ class MyHandler extends Handler {
+ MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ mCallback.executeMessage(msg);
+ }
+ }
+
+ public interface Callback {
+ public void executeMessage(Message msg);
+ }
+
+ public HandlerCaller(Context context, Callback callback) {
+ mContext = context;
+ mMainLooper = context.getMainLooper();
+ mH = new MyHandler(mMainLooper);
+ mCallback = callback;
+ }
+
+ public SomeArgs obtainArgs() {
+ synchronized (mH) {
+ SomeArgs args = mArgsPool;
+ if (args != null) {
+ mArgsPool = args.next;
+ args.next = null;
+ mArgsPoolSize--;
+ return args;
+ }
+ }
+ return new SomeArgs();
+ }
+
+ public void recycleArgs(SomeArgs args) {
+ synchronized (mH) {
+ if (mArgsPoolSize < ARGS_POOL_MAX_SIZE) {
+ args.next = mArgsPool;
+ mArgsPool = args;
+ mArgsPoolSize++;
+ }
+ }
+ }
+
+ public void executeOrSendMessage(Message msg) {
+ // If we are calling this from the main thread, then we can call
+ // right through. Otherwise, we need to send the message to the
+ // main thread.
+ if (Looper.myLooper() == mMainLooper) {
+ mCallback.executeMessage(msg);
+ msg.recycle();
+ return;
+ }
+
+ mH.sendMessage(msg);
+ }
+
+ public void sendMessage(Message msg) {
+ mH.sendMessage(msg);
+ }
+
+ public Message obtainMessage(int what) {
+ return mH.obtainMessage(what);
+ }
+
+ public Message obtainMessageBO(int what, boolean arg1, Object arg2) {
+ return mH.obtainMessage(what, arg1 ? 1 : 0, 0, arg2);
+ }
+
+ public Message obtainMessageBOO(int what, boolean arg1, Object arg2, Object arg3) {
+ SomeArgs args = obtainArgs();
+ args.arg1 = arg2;
+ args.arg2 = arg3;
+ return mH.obtainMessage(what, arg1 ? 1 : 0, 0, args);
+ }
+
+ public Message obtainMessageO(int what, Object arg1) {
+ return mH.obtainMessage(what, 0, 0, arg1);
+ }
+
+ public Message obtainMessageIO(int what, int arg1, Object arg2) {
+ return mH.obtainMessage(what, arg1, 0, arg2);
+ }
+
+ public Message obtainMessageIO(int what, int arg1, int arg2, Object arg3) {
+ return mH.obtainMessage(what, arg1, arg2, arg3);
+ }
+
+ public Message obtainMessageIOO(int what, int arg1, Object arg2, Object arg3) {
+ SomeArgs args = obtainArgs();
+ args.arg1 = arg2;
+ args.arg2 = arg3;
+ return mH.obtainMessage(what, arg1, 0, args);
+ }
+
+ public Message obtainMessageOO(int what, Object arg1, Object arg2) {
+ SomeArgs args = obtainArgs();
+ args.arg1 = arg1;
+ args.arg2 = arg2;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
+ public Message obtainMessageOOO(int what, Object arg1, Object arg2, Object arg3) {
+ SomeArgs args = obtainArgs();
+ args.arg1 = arg1;
+ args.arg2 = arg2;
+ args.arg3 = arg3;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
+ public Message obtainMessageIIII(int what, int arg1, int arg2,
+ int arg3, int arg4) {
+ SomeArgs args = obtainArgs();
+ args.argi1 = arg1;
+ args.argi2 = arg2;
+ args.argi3 = arg3;
+ args.argi4 = arg4;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
+ public Message obtainMessageIIIIO(int what, int arg1, int arg2,
+ int arg3, int arg4, Object arg5) {
+ SomeArgs args = obtainArgs();
+ args.arg1 = arg5;
+ args.argi1 = arg1;
+ args.argi2 = arg2;
+ args.argi3 = arg3;
+ args.argi4 = arg4;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 566332b..631e7d8 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -210,7 +210,7 @@ class ZygoteConnection {
}
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid,
- parsedArgs.gids, parsedArgs.enableDebugger, rlimits);
+ parsedArgs.gids, parsedArgs.debugFlags, rlimits);
} catch (IllegalArgumentException ex) {
logAndPrintError (newStderr, "Invalid zygote arguments", ex);
pid = -1;
@@ -295,8 +295,8 @@ class ZygoteConnection {
/** from --peer-wait */
boolean peerWait;
- /** from --enable-debugger */
- boolean enableDebugger;
+ /** from --enable-debugger, --enable-checkjni, --enable-assert */
+ int debugFlags;
/** from --classpath */
String classpath;
@@ -362,7 +362,11 @@ class ZygoteConnection {
gid = Integer.parseInt(
arg.substring(arg.indexOf('=') + 1));
} else if (arg.equals("--enable-debugger")) {
- enableDebugger = true;
+ debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+ } else if (arg.equals("--enable-checkjni")) {
+ debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
+ } else if (arg.equals("--enable-assert")) {
+ debugFlags |= Zygote.DEBUG_ENABLE_ASSERT;
} else if (arg.equals("--peer-wait")) {
peerWait = true;
} else if (arg.equals("--runtime-init")) {
@@ -567,7 +571,7 @@ class ZygoteConnection {
*/
private static void applyDebuggerSecurityPolicy(Arguments args) {
if ("1".equals(SystemProperties.get("ro.debuggable"))) {
- args.enableDebugger = true;
+ args.debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
}
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 5714b99..f21b62f 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -260,7 +260,7 @@ public class ZygoteInit {
int count = 0;
String line;
- boolean gotMissingClass = false;
+ String missingClasses = null;
while ((line = br.readLine()) != null) {
// Skip comments and blank lines.
line = line.trim();
@@ -285,14 +285,19 @@ public class ZygoteInit {
count++;
} catch (ClassNotFoundException e) {
Log.e(TAG, "Class not found for preloading: " + line);
- gotMissingClass = true;
+ if (missingClasses == null) {
+ missingClasses = line;
+ } else {
+ missingClasses += " " + line;
+ }
}
}
- if (gotMissingClass &&
+ if (missingClasses != null &&
"1".equals(SystemProperties.get("persist.service.adb.enable"))) {
throw new IllegalStateException(
- "Missing class(es) for preloading, update preloaded-classes");
+ "Missing class(es) for preloading, update preloaded-classes ["
+ + missingClasses + "]");
}
Log.i(TAG, "...preloaded " + count + " classes in "
@@ -425,7 +430,7 @@ public class ZygoteInit {
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,3001,3002,3003",
- "--capabilities=88161312,88161312",
+ "--capabilities=121715744,121715744",
"--runtime-init",
"--nice-name=system_server",
"com.android.server.SystemServer",
@@ -442,13 +447,14 @@ public class ZygoteInit {
* indicate it should be debuggable or the ro.debuggable system property
* is set to "1"
*/
- boolean debuggableBuild = "1".equals(SystemProperties.get("ro.debuggable"));
- boolean enableDebugger = parsedArgs.enableDebugger || debuggableBuild;
+ int debugFlags = parsedArgs.debugFlags;
+ if ("1".equals(SystemProperties.get("ro.debuggable")))
+ debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
/* Request to fork the system server process */
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
- parsedArgs.gids, enableDebugger, null);
+ parsedArgs.gids, debugFlags, null);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
diff --git a/core/java/com/android/internal/provider/Settings.java b/core/java/com/android/internal/provider/Settings.java
deleted file mode 100644
index 85ef17e..0000000
--- a/core/java/com/android/internal/provider/Settings.java
+++ /dev/null
@@ -1,199 +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 com.android.internal.provider;
-
-import android.provider.BaseColumns;
-import android.net.Uri;
-
-/**
- * Settings related utilities.
- */
-public class Settings {
- /**
- * Favorite intents
- */
- public static final class Favorites implements BaseColumns {
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI = Uri.parse("content://" +
- android.provider.Settings.AUTHORITY + "/favorites?notify=true");
-
- /**
- * The content:// style URL for this table. When this Uri is used, no notification is
- * sent if the content changes.
- */
- public static final Uri CONTENT_URI_NO_NOTIFICATION =
- Uri.parse("content://" + android.provider.Settings.AUTHORITY +
- "/favorites?notify=false");
-
- /**
- * The content:// style URL for a given row, identified by its id.
- *
- * @param id The row id.
- * @param notify True to send a notification is the content changes.
- *
- * @return The unique content URL for the specified row.
- */
- public static Uri getContentUri(long id, boolean notify) {
- return Uri.parse("content://" + android.provider.Settings.AUTHORITY +
- "/favorites/" + id + "?notify=" + notify);
- }
-
- /**
- * The row ID.
- * <p>Type: INTEGER</p>
- */
- public static final String ID = "_id";
-
- /**
- * Descriptive name of the favorite that can be displayed to the user.
- * <P>Type: TEXT</P>
- */
- public static final String TITLE = "title";
-
- /**
- * The Intent URL of the favorite, describing what it points to. This
- * value is given to {@link android.content.Intent#getIntent} to create
- * an Intent that can be launched.
- * <P>Type: TEXT</P>
- */
- public static final String INTENT = "intent";
-
- /**
- * The container holding the favorite
- * <P>Type: INTEGER</P>
- */
- public static final String CONTAINER = "container";
-
- /**
- * The icon is a resource identified by a package name and an integer id.
- */
- public static final int CONTAINER_DESKTOP = -100;
-
- /**
- * The screen holding the favorite (if container is CONTAINER_DESKTOP)
- * <P>Type: INTEGER</P>
- */
- public static final String SCREEN = "screen";
-
- /**
- * The X coordinate of the cell holding the favorite
- * (if container is CONTAINER_DESKTOP or CONTAINER_DOCK)
- * <P>Type: INTEGER</P>
- */
- public static final String CELLX = "cellX";
-
- /**
- * The Y coordinate of the cell holding the favorite
- * (if container is CONTAINER_DESKTOP)
- * <P>Type: INTEGER</P>
- */
- public static final String CELLY = "cellY";
-
- /**
- * The X span of the cell holding the favorite
- * <P>Type: INTEGER</P>
- */
- public static final String SPANX = "spanX";
-
- /**
- * The Y span of the cell holding the favorite
- * <P>Type: INTEGER</P>
- */
- public static final String SPANY = "spanY";
-
- /**
- * The type of the favorite
- *
- * <P>Type: INTEGER</P>
- */
- public static final String ITEM_TYPE = "itemType";
-
- /**
- * The favorite is an application
- */
- public static final int ITEM_TYPE_APPLICATION = 0;
-
- /**
- * The favorite is an application created shortcut
- */
- public static final int ITEM_TYPE_SHORTCUT = 1;
-
- /**
- * The favorite is a user created folder
- */
- public static final int ITEM_TYPE_USER_FOLDER = 2;
-
- /**
- * The favorite is a clock
- */
- public static final int ITEM_TYPE_WIDGET_CLOCK = 1000;
-
- /**
- * The favorite is a search widget
- */
- public static final int ITEM_TYPE_WIDGET_SEARCH = 1001;
-
- /**
- * The favorite is a photo frame
- */
- public static final int ITEM_TYPE_WIDGET_PHOTO_FRAME = 1002;
-
- /**
- * Indicates whether this favorite is an application-created shortcut or not.
- * If the value is 0, the favorite is not an application-created shortcut, if the
- * value is 1, it is an application-created shortcut.
- * <P>Type: INTEGER</P>
- */
- public static final String IS_SHORTCUT = "isShortcut";
-
- /**
- * The icon type.
- * <P>Type: INTEGER</P>
- */
- public static final String ICON_TYPE = "iconType";
-
- /**
- * The icon is a resource identified by a package name and an integer id.
- */
- public static final int ICON_TYPE_RESOURCE = 0;
-
- /**
- * The icon is a bitmap.
- */
- public static final int ICON_TYPE_BITMAP = 1;
-
- /**
- * The icon package name, if icon type is ICON_TYPE_RESOURCE.
- * <P>Type: TEXT</P>
- */
- public static final String ICON_PACKAGE = "iconPackage";
-
- /**
- * The icon resource id, if icon type is ICON_TYPE_RESOURCE.
- * <P>Type: TEXT</P>
- */
- public static final String ICON_RESOURCE = "iconResource";
-
- /**
- * The custom icon bitmap, if icon type is ICON_TYPE_BITMAP.
- * <P>Type: BLOB</P>
- */
- public static final String ICON = "icon";
- }
-}
diff --git a/core/java/com/android/internal/util/FastXmlSerializer.java b/core/java/com/android/internal/util/FastXmlSerializer.java
index b4dae94..592a8fa 100644
--- a/core/java/com/android/internal/util/FastXmlSerializer.java
+++ b/core/java/com/android/internal/util/FastXmlSerializer.java
@@ -125,7 +125,7 @@ public class FastXmlSerializer implements XmlSerializer {
String escape = escapes[c];
if (escape == null) continue;
if (lastPos < pos) append(string, lastPos, pos-lastPos);
- lastPos = pos;
+ lastPos = pos + 1;
append(escape);
}
if (lastPos < pos) append(string, lastPos, pos-lastPos);
@@ -143,7 +143,7 @@ public class FastXmlSerializer implements XmlSerializer {
String escape = escapes[c];
if (escape == null) continue;
if (lastPos < pos) append(buf, lastPos, pos-lastPos);
- lastPos = pos;
+ lastPos = pos + 1;
append(escape);
}
if (lastPos < pos) append(buf, lastPos, pos-lastPos);
diff --git a/core/java/com/android/internal/view/IInputConnectionCallback.aidl b/core/java/com/android/internal/view/IInputConnectionCallback.aidl
new file mode 100644
index 0000000..5b5b3df
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputConnectionCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.view;
+
+import android.graphics.Rect;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.TextBoxAttribute;
+import com.android.internal.view.IInputContext;
+import android.os.IBinder;
+
+/**
+ * {@hide}
+ */
+oneway interface IInputMethodCallback {
+ void finishedEvent(int seq, boolean handled);
+}
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
new file mode 100644
index 0000000..c5966ee
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -0,0 +1,252 @@
+package com.android.internal.view;
+
+import com.android.internal.view.IInputContext;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+
+public class IInputConnectionWrapper extends IInputContext.Stub {
+ static final String TAG = "IInputConnectionWrapper";
+
+ private static final int DO_GET_TEXT_AFTER_CURSOR = 10;
+ private static final int DO_GET_TEXT_BEFORE_CURSOR = 20;
+ private static final int DO_GET_CURSOR_CAPS_MODE = 30;
+ 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_COMPOSING_TEXT = 60;
+ private static final int DO_SEND_KEY_EVENT = 70;
+ private static final int DO_DELETE_SURROUNDING_TEXT = 80;
+ private static final int DO_HIDE_STATUS_ICON = 100;
+ private static final int DO_SHOW_STATUS_ICON = 110;
+ private static final int DO_PERFORM_PRIVATE_COMMAND = 120;
+ private static final int DO_CLEAR_META_KEY_STATES = 130;
+
+ private InputConnection mInputConnection;
+
+ private Looper mMainLooper;
+ private Handler mH;
+
+ static class SomeArgs {
+ Object arg1;
+ Object arg2;
+ IInputContextCallback callback;
+ int seq;
+ }
+
+ class MyHandler extends Handler {
+ MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ executeMessage(msg);
+ }
+ }
+
+ public IInputConnectionWrapper(Looper mainLooper, InputConnection conn) {
+ mInputConnection = 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 void getTextBeforeCursor(int length, int seq, IInputContextCallback callback) {
+ dispatchMessage(obtainMessageISC(DO_GET_TEXT_BEFORE_CURSOR, length, seq, callback));
+ }
+
+ public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) {
+ dispatchMessage(obtainMessageISC(DO_GET_CURSOR_CAPS_MODE, reqModes, seq, callback));
+ }
+
+ public void getExtractedText(ExtractedTextRequest request,
+ int flags, int seq, IInputContextCallback callback) {
+ dispatchMessage(obtainMessageIOSC(DO_GET_EXTRACTED_TEXT, flags,
+ request, seq, callback));
+ }
+
+ public void commitText(CharSequence text, int newCursorPosition) {
+ dispatchMessage(obtainMessageIO(DO_COMMIT_TEXT, newCursorPosition, text));
+ }
+
+ public void commitCompletion(CompletionInfo text) {
+ dispatchMessage(obtainMessageO(DO_COMMIT_COMPLETION, text));
+ }
+
+ public void setComposingText(CharSequence text, int newCursorPosition) {
+ dispatchMessage(obtainMessageIO(DO_SET_COMPOSING_TEXT, newCursorPosition, text));
+ }
+
+ public void sendKeyEvent(KeyEvent event) {
+ dispatchMessage(obtainMessageO(DO_SEND_KEY_EVENT, event));
+ }
+
+ public void clearMetaKeyStates(int states) {
+ dispatchMessage(obtainMessageII(DO_CLEAR_META_KEY_STATES, states, 0));
+ }
+
+ public void deleteSurroundingText(int leftLength, int rightLength) {
+ dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT,
+ leftLength, rightLength));
+ }
+
+ 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 performPrivateCommand(String action, Bundle data) {
+ dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data));
+ }
+
+ void dispatchMessage(Message msg) {
+ // If we are calling this from the main thread, then we can call
+ // right through. Otherwise, we need to send the message to the
+ // main thread.
+ if (Looper.myLooper() == mMainLooper) {
+ executeMessage(msg);
+ msg.recycle();
+ return;
+ }
+
+ mH.sendMessage(msg);
+ }
+
+ void executeMessage(Message msg) {
+ switch (msg.what) {
+ case DO_GET_TEXT_AFTER_CURSOR: {
+ SomeArgs args = (SomeArgs)msg.obj;
+ try {
+ args.callback.setTextAfterCursor(mInputConnection.getTextAfterCursor(msg.arg1),
+ args.seq);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Got RemoteException calling setTextAfterCursor", e);
+ }
+ return;
+ }
+ case DO_GET_TEXT_BEFORE_CURSOR: {
+ SomeArgs args = (SomeArgs)msg.obj;
+ try {
+ args.callback.setTextBeforeCursor(mInputConnection.getTextBeforeCursor(msg.arg1),
+ args.seq);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Got RemoteException calling setTextBeforeCursor", e);
+ }
+ return;
+ }
+ case DO_GET_CURSOR_CAPS_MODE: {
+ SomeArgs args = (SomeArgs)msg.obj;
+ try {
+ args.callback.setCursorCapsMode(mInputConnection.getCursorCapsMode(msg.arg1),
+ args.seq);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Got RemoteException calling setCursorCapsMode", e);
+ }
+ return;
+ }
+ case DO_GET_EXTRACTED_TEXT: {
+ SomeArgs args = (SomeArgs)msg.obj;
+ try {
+ args.callback.setExtractedText(mInputConnection.getExtractedText(
+ (ExtractedTextRequest)args.arg1, msg.arg1), args.seq);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Got RemoteException calling setExtractedText", e);
+ }
+ return;
+ }
+ case DO_COMMIT_TEXT: {
+ mInputConnection.commitText((CharSequence)msg.obj, msg.arg1);
+ return;
+ }
+ case DO_COMMIT_COMPLETION: {
+ mInputConnection.commitCompletion((CompletionInfo)msg.obj);
+ return;
+ }
+ case DO_SET_COMPOSING_TEXT: {
+ mInputConnection.setComposingText((CharSequence)msg.obj, msg.arg1);
+ return;
+ }
+ case DO_SEND_KEY_EVENT: {
+ mInputConnection.sendKeyEvent((KeyEvent)msg.obj);
+ return;
+ }
+ case DO_CLEAR_META_KEY_STATES: {
+ mInputConnection.clearMetaKeyStates(msg.arg1);
+ return;
+ }
+ case DO_DELETE_SURROUNDING_TEXT: {
+ mInputConnection.deleteSurroundingText(msg.arg1, msg.arg2);
+ return;
+ }
+ case DO_HIDE_STATUS_ICON: {
+ mInputConnection.hideStatusIcon();
+ return;
+ }
+ case DO_SHOW_STATUS_ICON: {
+ mInputConnection.showStatusIcon((String)msg.obj, msg.arg1);
+ return;
+ }
+ case DO_PERFORM_PRIVATE_COMMAND: {
+ SomeArgs args = (SomeArgs)msg.obj;
+ mInputConnection.performPrivateCommand((String)args.arg1,
+ (Bundle)args.arg2);
+ return;
+ }
+ }
+ Log.w(TAG, "Unhandled message code: " + msg.what);
+ }
+
+ Message obtainMessage(int what) {
+ return mH.obtainMessage(what);
+ }
+
+ Message obtainMessageII(int what, int arg1, int arg2) {
+ return mH.obtainMessage(what, arg1, arg2);
+ }
+
+ Message obtainMessageO(int what, Object arg1) {
+ return mH.obtainMessage(what, 0, 0, arg1);
+ }
+
+ Message obtainMessageISC(int what, int arg1, int seq, IInputContextCallback callback) {
+ SomeArgs args = new SomeArgs();
+ args.callback = callback;
+ args.seq = seq;
+ return mH.obtainMessage(what, arg1, 0, args);
+ }
+
+ Message obtainMessageIOSC(int what, int arg1, Object arg2, int seq,
+ IInputContextCallback callback) {
+ SomeArgs args = new SomeArgs();
+ args.arg1 = arg2;
+ args.callback = callback;
+ args.seq = seq;
+ return mH.obtainMessage(what, arg1, 0, args);
+ }
+
+ Message obtainMessageIO(int what, int arg1, Object arg2) {
+ return mH.obtainMessage(what, arg1, 0, arg2);
+ }
+
+ Message obtainMessageOO(int what, Object arg1, Object arg2) {
+ SomeArgs args = new SomeArgs();
+ args.arg1 = arg1;
+ args.arg2 = arg2;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+}
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
new file mode 100644
index 0000000..7ea65a0
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -0,0 +1,58 @@
+/*
+ * 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.view;
+
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedTextRequest;
+
+import com.android.internal.view.IInputContextCallback;
+
+/**
+ * Interface from an input method to the application, allowing it to perform
+ * edits on the current input field and other interactions with the application.
+ * {@hide}
+ */
+ oneway interface IInputContext {
+ void getTextBeforeCursor(int length, int seq, IInputContextCallback callback);
+
+ void getTextAfterCursor(int length, int seq, IInputContextCallback callback);
+
+ void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback);
+
+ void getExtractedText(in ExtractedTextRequest request, int flags, int seq,
+ IInputContextCallback callback);
+
+ void deleteSurroundingText(int leftLength, int rightLength);
+
+ void setComposingText(CharSequence text, int newCursorPosition);
+
+ void commitText(CharSequence text, int newCursorPosition);
+
+ void commitCompletion(in CompletionInfo completion);
+
+ 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/IInputContextCallback.aidl b/core/java/com/android/internal/view/IInputContextCallback.aidl
new file mode 100644
index 0000000..9b8c43c
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputContextCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.view;
+
+import android.view.inputmethod.ExtractedText;
+
+/**
+ * {@hide}
+ */
+oneway interface IInputContextCallback {
+ void setTextBeforeCursor(CharSequence textBeforeCursor, int seq);
+ void setTextAfterCursor(CharSequence textAfterCursor, int seq);
+ void setCursorCapsMode(int capsMode, int seq);
+ void setExtractedText(in ExtractedText extractedText, int seq);
+}
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
new file mode 100644
index 0000000..87bf473
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -0,0 +1,54 @@
+/*
+ * 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.view;
+
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodCallback;
+import com.android.internal.view.IInputMethodSession;
+
+/**
+ * Top-level interface to an input method component (implemented in a
+ * Service).
+ * {@hide}
+ */
+oneway interface IInputMethod {
+ void attachToken(IBinder token);
+
+ void bindInput(in InputBinding binding);
+
+ void unbindInput();
+
+ void startInput(in EditorInfo attribute);
+
+ void restartInput(in EditorInfo attribute);
+
+ void createSession(IInputMethodCallback callback);
+
+ void setSessionEnabled(IInputMethodSession session, boolean enabled);
+
+ void revokeSession(IInputMethodSession session);
+
+ void showSoftInput();
+
+ void hideSoftInput();
+}
diff --git a/core/java/com/android/internal/view/IInputMethodCallback.aidl b/core/java/com/android/internal/view/IInputMethodCallback.aidl
new file mode 100644
index 0000000..480cc0e
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputMethodCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.view;
+
+import android.graphics.Rect;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodSession;
+import android.os.IBinder;
+
+/**
+ * Helper interface for IInputMethod to allow the input method to call back
+ * to its client with results from incoming calls.
+ * {@hide}
+ */
+oneway interface IInputMethodCallback {
+ void finishedEvent(int seq, boolean handled);
+ void sessionCreated(IInputMethodSession session);
+}
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
new file mode 100644
index 0000000..ce4312d
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.view;
+
+import com.android.internal.view.InputBindResult;
+
+/**
+ * Interface a client of the IInputMethodManager implements, to identify
+ * itself and receive information about changes to the global manager state.
+ */
+oneway interface IInputMethodClient {
+ void setUsingInputMethod(boolean state);
+ void onBindMethod(in InputBindResult res);
+ void onUnbindMethod(int sequence);
+ void setActive(boolean active);
+}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
new file mode 100644
index 0000000..b4cfe26
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -0,0 +1,51 @@
+/*
+ * 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.view;
+
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.EditorInfo;
+import com.android.internal.view.InputBindResult;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+
+/**
+ * Public interface to the global input method manager, used by all client
+ * applications.
+ */
+interface IInputMethodManager {
+ List<InputMethodInfo> getInputMethodList();
+ List<InputMethodInfo> getEnabledInputMethodList();
+
+ void addClient(in IInputMethodClient client,
+ in IInputContext inputContext, int uid, int pid);
+ void removeClient(in IInputMethodClient client);
+
+ InputBindResult startInput(in IInputMethodClient client,
+ in EditorInfo attribute, boolean initial, boolean needResult);
+ void finishInput(in IInputMethodClient client);
+ void showSoftInput(in IInputMethodClient client);
+ void hideSoftInput(in IInputMethodClient client);
+ void windowGainedFocus(in IInputMethodClient client,
+ boolean viewHasFocus, int softInputMode, boolean first,
+ int windowFlags);
+
+ void showInputMethodPickerFromClient(in IInputMethodClient client);
+ void setInputMethod(in IBinder token, String id);
+ void hideMySoftInput(in IBinder token);
+ void updateStatusIcon(int iconId, String iconPackage);
+}
+
diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl
new file mode 100644
index 0000000..4f28593
--- /dev/null
+++ b/core/java/com/android/internal/view/IInputMethodSession.aidl
@@ -0,0 +1,48 @@
+/*
+ * 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.view;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedText;
+import com.android.internal.view.IInputMethodCallback;
+
+/**
+ * Sub-interface of IInputMethod which is safe to give to client applications.
+ * {@hide}
+ */
+oneway interface IInputMethodSession {
+ void finishInput();
+
+ void updateExtractedText(int token, in ExtractedText text);
+
+ void updateSelection(int oldSelStart, int oldSelEnd,
+ int newSelStart, int newSelEnd);
+
+ void updateCursor(in Rect newCursor);
+
+ void displayCompletions(in CompletionInfo[] completions);
+
+ void dispatchKeyEvent(int seq, in KeyEvent event, IInputMethodCallback callback);
+
+ void dispatchTrackballEvent(int seq, in MotionEvent event, IInputMethodCallback callback);
+
+ void appPrivateCommand(String action, in Bundle data);
+}
diff --git a/core/java/com/android/internal/view/InputBindResult.aidl b/core/java/com/android/internal/view/InputBindResult.aidl
new file mode 100644
index 0000000..7ff5c4e
--- /dev/null
+++ b/core/java/com/android/internal/view/InputBindResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+parcelable InputBindResult;
diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java
new file mode 100644
index 0000000..658f098
--- /dev/null
+++ b/core/java/com/android/internal/view/InputBindResult.java
@@ -0,0 +1,91 @@
+/*
+ * 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 com.android.internal.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Bundle of information returned by input method manager about a successful
+ * binding to an input method.
+ */
+public final class InputBindResult implements Parcelable {
+ static final String TAG = "InputBindResult";
+
+ /**
+ * The input method service.
+ */
+ public final IInputMethodSession method;
+
+ /**
+ * The ID for this input method, as found in InputMethodInfo; null if
+ * no input method will be bound.
+ */
+ public final String id;
+
+ /**
+ * Sequence number of this binding.
+ */
+ public final int sequence;
+
+ public InputBindResult(IInputMethodSession _method, String _id, int _sequence) {
+ method = _method;
+ id = _id;
+ sequence = _sequence;
+ }
+
+ InputBindResult(Parcel source) {
+ method = IInputMethodSession.Stub.asInterface(source.readStrongBinder());
+ id = source.readString();
+ sequence = source.readInt();
+ }
+
+ @Override
+ public String toString() {
+ return "InputBindResult{" + method + " " + id
+ + " #" + sequence + "}";
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongInterface(method);
+ dest.writeString(id);
+ dest.writeInt(sequence);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<InputBindResult> CREATOR = new Parcelable.Creator<InputBindResult>() {
+ public InputBindResult createFromParcel(Parcel source) {
+ return new InputBindResult(source);
+ }
+
+ public InputBindResult[] newArray(int size) {
+ return new InputBindResult[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
new file mode 100644
index 0000000..5bfcfe9
--- /dev/null
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -0,0 +1,306 @@
+package com.android.internal.view;
+
+import com.android.internal.view.IInputContext;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+
+public class InputConnectionWrapper implements InputConnection {
+ private static final int MAX_WAIT_TIME_MILLIS = 2000;
+ private final IInputContext mIInputContext;
+
+ static class InputContextCallback extends IInputContextCallback.Stub {
+ private static final String TAG = "InputConnectionWrapper.ICC";
+ public int mSeq;
+ public boolean mHaveValue;
+ public CharSequence mTextBeforeCursor;
+ public CharSequence mTextAfterCursor;
+ public ExtractedText mExtractedText;
+ public int mCursorCapsMode;
+
+ // A 'pool' of one InputContextCallback. Each ICW request will attempt to gain
+ // exclusive access to this object.
+ private static InputContextCallback sInstance = new InputContextCallback();
+ private static int sSequenceNumber = 1;
+
+ /**
+ * Returns an InputContextCallback object that is guaranteed not to be in use by
+ * any other thread. The returned object's 'have value' flag is cleared and its expected
+ * sequence number is set to a new integer. We use a sequence number so that replies that
+ * occur after a timeout has expired are not interpreted as replies to a later request.
+ */
+ private static InputContextCallback getInstance() {
+ synchronized (InputContextCallback.class) {
+ // Return sInstance if it's non-null, otherwise construct a new callback
+ InputContextCallback callback;
+ if (sInstance != null) {
+ callback = sInstance;
+ sInstance = null;
+
+ // Reset the callback
+ callback.mHaveValue = false;
+ } else {
+ callback = new InputContextCallback();
+ }
+
+ // Set the sequence number
+ callback.mSeq = sSequenceNumber++;
+ return callback;
+ }
+ }
+
+ /**
+ * Makes the given InputContextCallback available for use in the future.
+ */
+ private void dispose() {
+ synchronized (InputContextCallback.class) {
+ // If sInstance is non-null, just let this object be garbage-collected
+ if (sInstance == null) {
+ // Allow any objects being held to be gc'ed
+ mTextAfterCursor = null;
+ mTextBeforeCursor = null;
+ mExtractedText = null;
+ sInstance = this;
+ }
+ }
+ }
+
+ public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
+ synchronized (this) {
+ if (seq == mSeq) {
+ mTextBeforeCursor = textBeforeCursor;
+ mHaveValue = true;
+ notifyAll();
+ } else {
+ Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ + ") in setTextBeforeCursor, ignoring.");
+ }
+ }
+ }
+
+ public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
+ synchronized (this) {
+ if (seq == mSeq) {
+ mTextAfterCursor = textAfterCursor;
+ mHaveValue = true;
+ notifyAll();
+ } else {
+ Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ + ") in setTextAfterCursor, ignoring.");
+ }
+ }
+ }
+
+ public void setCursorCapsMode(int capsMode, int seq) {
+ synchronized (this) {
+ if (seq == mSeq) {
+ mCursorCapsMode = capsMode;
+ mHaveValue = true;
+ notifyAll();
+ } else {
+ Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ + ") in setCursorCapsMode, ignoring.");
+ }
+ }
+ }
+
+ public void setExtractedText(ExtractedText extractedText, int seq) {
+ synchronized (this) {
+ if (seq == mSeq) {
+ mExtractedText = extractedText;
+ mHaveValue = true;
+ notifyAll();
+ } else {
+ Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ + ") in setExtractedText, ignoring.");
+ }
+ }
+ }
+
+ /**
+ * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
+ *
+ * <p>The caller must be synchronized on this callback object.
+ */
+ void waitForResultLocked() {
+ long startTime = SystemClock.uptimeMillis();
+ long endTime = startTime + MAX_WAIT_TIME_MILLIS;
+
+ while (!mHaveValue) {
+ long remainingTime = endTime - SystemClock.uptimeMillis();
+ if (remainingTime <= 0) {
+ Log.w(TAG, "Timed out waiting on IInputContextCallback");
+ return;
+ }
+ try {
+ wait(remainingTime);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+
+ public InputConnectionWrapper(IInputContext inputContext) {
+ mIInputContext = inputContext;
+ }
+
+ public CharSequence getTextAfterCursor(int length) {
+ CharSequence value = null;
+ try {
+ InputContextCallback callback = InputContextCallback.getInstance();
+ mIInputContext.getTextAfterCursor(length, callback.mSeq, callback);
+ synchronized (callback) {
+ callback.waitForResultLocked();
+ if (callback.mHaveValue) {
+ value = callback.mTextAfterCursor;
+ }
+ }
+ callback.dispose();
+ } catch (RemoteException e) {
+ return null;
+ }
+ return value;
+ }
+
+ public CharSequence getTextBeforeCursor(int length) {
+ CharSequence value = null;
+ try {
+ InputContextCallback callback = InputContextCallback.getInstance();
+ mIInputContext.getTextBeforeCursor(length, callback.mSeq, callback);
+ synchronized (callback) {
+ callback.waitForResultLocked();
+ if (callback.mHaveValue) {
+ value = callback.mTextBeforeCursor;
+ }
+ }
+ callback.dispose();
+ } catch (RemoteException e) {
+ return null;
+ }
+ return value;
+ }
+
+ public int getCursorCapsMode(int reqModes) {
+ int value = 0;
+ try {
+ InputContextCallback callback = InputContextCallback.getInstance();
+ mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback);
+ synchronized (callback) {
+ callback.waitForResultLocked();
+ if (callback.mHaveValue) {
+ value = callback.mCursorCapsMode;
+ }
+ }
+ callback.dispose();
+ } catch (RemoteException e) {
+ return 0;
+ }
+ return value;
+ }
+
+ public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+ ExtractedText value = null;
+ try {
+ InputContextCallback callback = InputContextCallback.getInstance();
+ mIInputContext.getExtractedText(request, flags, callback.mSeq, callback);
+ synchronized (callback) {
+ callback.waitForResultLocked();
+ if (callback.mHaveValue) {
+ value = callback.mExtractedText;
+ }
+ }
+ callback.dispose();
+ } catch (RemoteException e) {
+ return null;
+ }
+ return value;
+ }
+
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ try {
+ mIInputContext.commitText(text, newCursorPosition);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ public boolean commitCompletion(CompletionInfo text) {
+ try {
+ mIInputContext.commitCompletion(text);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ try {
+ mIInputContext.setComposingText(text, newCursorPosition);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ public boolean sendKeyEvent(KeyEvent event) {
+ try {
+ mIInputContext.sendKeyEvent(event);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ public boolean clearMetaKeyStates(int states) {
+ try {
+ mIInputContext.clearMetaKeyStates(states);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ public boolean deleteSurroundingText(int leftLength, int rightLength) {
+ try {
+ mIInputContext.deleteSurroundingText(leftLength, rightLength);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ public boolean hideStatusIcon() {
+ try {
+ mIInputContext.showStatusIcon(null, 0);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ public boolean showStatusIcon(String packageName, int resId) {
+ try {
+ mIInputContext.showStatusIcon(packageName, resId);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ public boolean performPrivateCommand(String action, Bundle data) {
+ try {
+ mIInputContext.performPrivateCommand(action, data);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index 156e20a..3b11a64 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -26,6 +26,7 @@ import android.util.AttributeSet;
import android.view.Gravity;
import android.view.SoundEffectConstants;
import android.view.View;
+import android.view.ViewDebug;
import android.widget.TextView;
/**
@@ -176,6 +177,9 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
// Set the compound drawables
setCompoundDrawables(null, icon, null, null);
+
+ // When there is an icon, make sure the text is at the bottom
+ setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
/*
* Request a layout to reposition the icon. The positioning of icon
@@ -185,13 +189,17 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
requestLayout();
} else {
setCompoundDrawables(null, null, null, null);
+
+ // When there is no icon, make sure the text is centered vertically
+ setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL);
}
}
public void setItemInvoker(ItemInvoker itemInvoker) {
mItemInvoker = itemInvoker;
}
-
+
+ @ViewDebug.CapturedViewProperty(retrieveReturn = true)
public MenuItemImpl getItemData() {
return mItemData;
}
diff --git a/core/java/com/android/internal/view/menu/IconMenuView.java b/core/java/com/android/internal/view/menu/IconMenuView.java
index fc76a04..781c608 100644
--- a/core/java/com/android/internal/view/menu/IconMenuView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuView.java
@@ -26,6 +26,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.Layout;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
@@ -57,6 +58,8 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
private int mRowHeight;
/** Maximum number of rows to be shown */
private int mMaxRows;
+ /** Maximum number of items to show in the icon menu. */
+ private int mMaxItems;
/** Maximum number of items per row */
private int mMaxItemsPerRow;
/** Actual number of items (the 'More' view does not count as an item) shown */
@@ -76,15 +79,15 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
/** Set of vertical divider positions where the vertical divider will be drawn */
private ArrayList<Rect> mVerticalDividerRects;
+ /** Icon for the 'More' button */
+ private Drawable mMoreIcon;
+
/** Item view for the 'More' button */
private IconMenuItemView mMoreItemView;
/** Background of each item (should contain the selected and focused states) */
private Drawable mItemBackground;
- /** Icon for the 'More' button */
- private Drawable mMoreIcon;
-
/** Default animations for this menu */
private int mAnimations;
@@ -108,6 +111,20 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
* we broadcasted to children.
*/
private boolean mLastChildrenCaptionMode;
+
+ /**
+ * The layout to use for menu items. Each index is the row number (0 is the
+ * top-most). Each value contains the number of items in that row.
+ * <p>
+ * The length of this array should not be used to get the number of rows in
+ * the current layout, instead use {@link #mLayoutNumRows}.
+ */
+ private int[] mLayout;
+
+ /**
+ * The number of rows in the current layout.
+ */
+ private int mLayoutNumRows;
/**
* Instantiates the IconMenuView that is linked with the provided MenuBuilder.
@@ -119,6 +136,7 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.IconMenuView, 0, 0);
mRowHeight = a.getDimensionPixelSize(com.android.internal.R.styleable.IconMenuView_rowHeight, 64);
mMaxRows = a.getInt(com.android.internal.R.styleable.IconMenuView_maxRows, 2);
+ mMaxItems = a.getInt(com.android.internal.R.styleable.IconMenuView_maxItems, 6);
mMaxItemsPerRow = a.getInt(com.android.internal.R.styleable.IconMenuView_maxItemsPerRow, 3);
mMoreIcon = a.getDrawable(com.android.internal.R.styleable.IconMenuView_moreIcon);
a.recycle();
@@ -144,6 +162,8 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
if (mVerticalDividerWidth == -1) mVerticalDividerWidth = 1;
}
+ mLayout = new int[mMaxRows];
+
// This view will be drawing the dividers
setWillNotDraw(false);
@@ -152,13 +172,106 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
// This is so our children can still be arrow-key focused
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
}
-
+
+ /**
+ * Figures out the layout for the menu items.
+ *
+ * @param width The available width for the icon menu.
+ */
+ private void layoutItems(int width) {
+ int numItems = getChildCount();
+
+ // Start with the least possible number of rows
+ int curNumRows =
+ Math.min((int) Math.ceil(numItems / (float) mMaxItemsPerRow), mMaxRows);
+
+ /*
+ * Increase the number of rows until we find a configuration that fits
+ * all of the items' titles. Worst case, we use mMaxRows.
+ */
+ for (; curNumRows <= mMaxRows; curNumRows++) {
+ layoutItemsUsingGravity(curNumRows, numItems);
+
+ if (curNumRows >= numItems) {
+ // Can't have more rows than items
+ break;
+ }
+
+ if (doItemsFit()) {
+ // All the items fit, so this is a good configuration
+ break;
+ }
+ }
+ }
+
/**
- * Calculates the minimum number of rows needed to the items to be shown.
- * @return the minimum number of rows
+ * Figures out the layout for the menu items by equally distributing, and
+ * adding any excess items equally to lower rows.
+ *
+ * @param numRows The total number of rows for the menu view
+ * @param numItems The total number of items (across all rows) contained in
+ * the menu view
+ * @return int[] Where the value of index i contains the number of items for row i
*/
- private int calculateNumberOfRows() {
- return Math.min((int) Math.ceil(getChildCount() / (double) mMaxItemsPerRow), mMaxRows);
+ private void layoutItemsUsingGravity(int numRows, int numItems) {
+ int numBaseItemsPerRow = numItems / numRows;
+ int numLeftoverItems = numItems % numRows;
+ /**
+ * The bottom rows will each get a leftover item. Rows (indexed at 0)
+ * that are >= this get a leftover item. Note: if there are 0 leftover
+ * items, no rows will get them since this value will be greater than
+ * the last row.
+ */
+ int rowsThatGetALeftoverItem = numRows - numLeftoverItems;
+
+ int[] layout = mLayout;
+ for (int i = 0; i < numRows; i++) {
+ layout[i] = numBaseItemsPerRow;
+
+ // Fill the bottom rows with a leftover item each
+ if (i >= rowsThatGetALeftoverItem) {
+ layout[i]++;
+ }
+ }
+
+ mLayoutNumRows = numRows;
+ }
+
+ /**
+ * Checks whether each item's title is fully visible using the current
+ * layout.
+ *
+ * @return True if the items fit (each item's text is fully visible), false
+ * otherwise.
+ */
+ private boolean doItemsFit() {
+ int itemPos = 0;
+
+ int[] layout = mLayout;
+ int numRows = mLayoutNumRows;
+ for (int row = 0; row < numRows; row++) {
+ int numItemsOnRow = layout[row];
+
+ /*
+ * If there is only one item on this row, increasing the
+ * number of rows won't help.
+ */
+ if (numItemsOnRow == 1) {
+ itemPos++;
+ continue;
+ }
+
+ for (int itemsOnRowCounter = numItemsOnRow; itemsOnRowCounter > 0;
+ itemsOnRowCounter--) {
+ View child = getChildAt(itemPos++);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.maxNumItemsOnRow < numItemsOnRow) {
+ return false;
+ }
+ }
+ }
+
+ return true;
}
/**
@@ -166,7 +279,7 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
* @param itemView The item's view to add
*/
private void addItemView(IconMenuItemView itemView) {
- ViewGroup.LayoutParams lp = itemView.getLayoutParams();
+ LayoutParams lp = (LayoutParams) itemView.getLayoutParams();
if (lp == null) {
// Default layout parameters
@@ -182,6 +295,9 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
// This class is the invoker for all its item views
itemView.setItemInvoker(this);
+ // Set the desired width of item
+ lp.desiredWidth = (int) Layout.getDesiredWidth(itemView.getText(), itemView.getPaint());
+
addView(itemView, lp);
}
@@ -228,7 +344,7 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
final ArrayList<MenuItemImpl> itemsToShow = mMenu.getVisibleItems();
final int numItems = itemsToShow.size();
- final int numItemsThatCanFit = mMaxItemsPerRow * mMaxRows;
+ final int numItemsThatCanFit = mMaxItems;
// Minimum of the num that can fit and the num that we have
final int minFitMinus1AndNumItems = Math.min(numItemsThatCanFit - 1, numItems);
@@ -263,30 +379,6 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
}
/**
- * Calculates the number of items that should go on each row of this menu view.
- * @param numRows the total number of rows for the menu view
- * @param numItems the total number of items (across all rows) contained in the menu view
- * @return int[] where index i contains the number of items for row i
- */
- private int[] calculateNumberOfItemsPerRow(final int numRows, final int numItems) {
- // TODO: get from theme? or write a best-fit algorithm? either way, this hard-coding needs
- // to be dropped (946635). Right now, this is according to UI spec.
- final int numItemsForRow[] = new int[numRows];
- if (numRows == 2) {
- if (numItems <= 5) {
- numItemsForRow[0] = 2;
- numItemsForRow[1] = numItems - 2;
- } else {
- numItemsForRow[0] = numItemsForRow[1] = mMaxItemsPerRow;
- }
- } else if (numRows == 1) {
- numItemsForRow[0] = numItems;
- }
-
- return numItemsForRow;
- }
-
- /**
* The positioning algorithm that gets called from onMeasure. It
* just computes positions for each child, and then stores them in the child's layout params.
* @param menuWidth The width of this menu to assume for positioning
@@ -298,10 +390,9 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
if (mVerticalDivider != null) mVerticalDividerRects.clear();
// Get the minimum number of rows needed
- final int numRows = calculateNumberOfRows();
+ final int numRows = mLayoutNumRows;
final int numRowsMinus1 = numRows - 1;
- final int numItems = getChildCount();
- final int numItemsForRow[] = calculateNumberOfItemsPerRow(numRows, numItems);
+ final int numItemsForRow[] = mLayout;
// The item position across all rows
int itemPos = 0;
@@ -382,13 +473,17 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
updateChildren(false);
}
+ int measuredWidth = resolveSize(Integer.MAX_VALUE, widthMeasureSpec);
+ calculateItemFittingMetadata(measuredWidth);
+ layoutItems(measuredWidth);
+
// Get the desired height of the icon menu view (last row of items does
// not have a divider below)
- final int desiredHeight = (mRowHeight + mHorizontalDividerHeight) * calculateNumberOfRows()
- - mHorizontalDividerHeight;
+ final int desiredHeight = (mRowHeight + mHorizontalDividerHeight) *
+ mLayoutNumRows - mHorizontalDividerHeight;
// Maximum possible width and desired height
- setMeasuredDimension(resolveSize(Integer.MAX_VALUE, widthMeasureSpec),
+ setMeasuredDimension(measuredWidth,
resolveSize(desiredHeight, heightMeasureSpec));
// Position the children
@@ -472,6 +567,32 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
return mAnimations;
}
+ /**
+ * Returns the number of items per row.
+ * <p>
+ * This should only be used for testing.
+ *
+ * @return The length of the array is the number of rows. A value at a
+ * position is the number of items in that row.
+ * @hide
+ */
+ public int[] getLayout() {
+ return mLayout;
+ }
+
+ /**
+ * Returns the number of rows in the layout.
+ * <p>
+ * This should only be used for testing.
+ *
+ * @return The length of the array is the number of rows. A value at a
+ * position is the number of items in that row.
+ * @hide
+ */
+ public int getLayoutNumRows() {
+ return mLayoutNumRows;
+ }
+
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
@@ -499,6 +620,13 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ requestFocus();
+ }
+
+ @Override
protected void onDetachedFromWindow() {
setCycleShortcutCaptionMode(false);
super.onDetachedFromWindow();
@@ -580,6 +708,31 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
((IconMenuItemView) getChildAt(i)).setCaptionMode(shortcut);
}
}
+
+ /**
+ * For each item, calculates the most dense row that fully shows the item's
+ * title.
+ *
+ * @param width The available width of the icon menu.
+ */
+ private void calculateItemFittingMetadata(int width) {
+ int maxNumItemsPerRow = mMaxItemsPerRow;
+ int numItems = getChildCount();
+ for (int i = 0; i < numItems; i++) {
+ LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
+ // Start with 1, since that case does not get covered in the loop below
+ lp.maxNumItemsOnRow = 1;
+ for (int curNumItemsPerRow = maxNumItemsPerRow; curNumItemsPerRow > 0;
+ curNumItemsPerRow--) {
+ // Check whether this item can fit into a row containing curNumItemsPerRow
+ if (lp.desiredWidth < width / curNumItemsPerRow) {
+ // It can, mark this value as the most dense row it can fit into
+ lp.maxNumItemsOnRow = curNumItemsPerRow;
+ break;
+ }
+ }
+ }
+ }
@Override
protected Parcelable onSaveInstanceState() {
@@ -655,6 +808,8 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
public static class LayoutParams extends ViewGroup.MarginLayoutParams
{
int left, top, right, bottom;
+ int desiredWidth;
+ int maxNumItemsOnRow;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 835e4a9..2987602 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -432,8 +432,9 @@ public class MenuBuilder implements Menu {
rintent.setComponent(new ComponentName(
ri.activityInfo.applicationInfo.packageName,
ri.activityInfo.name));
- final MenuItem item = add(group, id, categoryOrder, ri.loadLabel(pm));
- item.setIntent(rintent);
+ final MenuItem item = add(group, id, categoryOrder, ri.loadLabel(pm))
+ .setIcon(ri.loadIcon(pm))
+ .setIntent(rintent);
if (outSpecificItems != null && ri.specificIndex >= 0) {
outSpecificItems[ri.specificIndex] = item;
}
@@ -624,7 +625,8 @@ public class MenuBuilder implements Menu {
return mItems.size();
}
- public MenuItem get(int index) {
+ /** {@inheritDoc} */
+ public MenuItem getItem(int index) {
return mItems.get(index);
}
@@ -773,7 +775,8 @@ public class MenuBuilder implements Menu {
(shortcutAlphaChar != 0) &&
(shortcutAlphaChar == possibleChars.meta[0]
|| shortcutAlphaChar == possibleChars.meta[2]
- || (shortcutAlphaChar == '\b' && keyCode == KeyEvent.KEYCODE_DEL))) {
+ || (shortcutAlphaChar == '\b' && keyCode == KeyEvent.KEYCODE_DEL)) &&
+ item.isEnabled()) {
return item;
}
} else {
@@ -781,7 +784,8 @@ public class MenuBuilder implements Menu {
if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) &&
(shortcutNumericChar != 0) &&
(shortcutNumericChar == possibleChars.meta[0]
- || shortcutNumericChar == possibleChars.meta[2])) {
+ || shortcutNumericChar == possibleChars.meta[2]) &&
+ item.isEnabled()) {
return item;
}
}
@@ -829,13 +833,18 @@ public class MenuBuilder implements Menu {
* sub menu is about to be shown, <var>allMenusAreClosing</var>
* is false.
*/
- public final void close(boolean allMenusAreClosing) {
+ final void close(boolean allMenusAreClosing) {
Callback callback = getCallback();
if (callback != null) {
callback.onCloseMenu(this, allMenusAreClosing);
}
}
+ /** {@inheritDoc} */
+ public void close() {
+ close(true);
+ }
+
/**
* Called when an item is added or removed.
*
diff --git a/core/java/com/android/internal/view/menu/MenuDialogHelper.java b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
index 6dfc7a2..bc51cf3 100644
--- a/core/java/com/android/internal/view/menu/MenuDialogHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
@@ -72,10 +72,11 @@ public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogIn
mDialog = builder.create();
WindowManager.LayoutParams lp = mDialog.getWindow().getAttributes();
- lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+ lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
if (windowToken != null) {
lp.token = windowToken;
}
+ lp.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
mDialog.show();
}
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index c89f2e9..43dba6f 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -24,6 +24,7 @@ import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
+import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
@@ -183,6 +184,7 @@ public final class MenuItemImpl implements MenuItem {
return mGroup;
}
+ @ViewDebug.CapturedViewProperty
public int getItemId() {
return mId;
}
@@ -353,6 +355,7 @@ public final class MenuItemImpl implements MenuItem {
subMenu.setHeaderTitle(getTitle());
}
+ @ViewDebug.CapturedViewProperty
public CharSequence getTitle() {
return mTitle;
}
diff --git a/core/java/com/android/internal/view/package.html b/core/java/com/android/internal/view/package.html
new file mode 100644
index 0000000..783d0a1
--- /dev/null
+++ b/core/java/com/android/internal/view/package.html
@@ -0,0 +1,3 @@
+<body>
+{@hide}
+</body>
diff --git a/core/java/com/android/internal/widget/DialogTitle.java b/core/java/com/android/internal/widget/DialogTitle.java
new file mode 100644
index 0000000..2eef0b6
--- /dev/null
+++ b/core/java/com/android/internal/widget/DialogTitle.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2008 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.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.TextView;
+
+/**
+ * Used by dialogs to change the font size and number of lines to try to fit
+ * the text to the available space.
+ */
+public class DialogTitle extends TextView {
+
+ public DialogTitle(Context context, AttributeSet attrs,
+ int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public DialogTitle(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public DialogTitle(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ final Layout layout = getLayout();
+ if (layout != null) {
+ final int lineCount = layout.getLineCount();
+ if (lineCount > 0) {
+ final int ellipsisCount = layout.getEllipsisCount(lineCount - 1);
+ if (ellipsisCount > 0) {
+ setSingleLine(false);
+
+ TypedArray a = mContext.obtainStyledAttributes(
+ android.R.style.TextAppearance_Medium,
+ android.R.styleable.TextAppearance);
+ final int textSize = a.getDimensionPixelSize(
+ android.R.styleable.TextAppearance_textSize, 20);
+
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize);
+ setMaxLines(2);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
new file mode 100644
index 0000000..efe15f3
--- /dev/null
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -0,0 +1,338 @@
+/*
+ * 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 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 final Handler mUiHandler;
+
+ private Object[] mDefaultComposingSpans;
+
+ public EditableInputConnection(TextView textview) {
+ super(textview);
+ mTextView = textview;
+ mUiHandler = textview.getHandler();
+ }
+
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ if (DEBUG) Log.v(TAG, "setComposingText " + text);
+ replaceText(text, newCursorPosition, true);
+ 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.onCommitCompletion(text);
+ 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 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 ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+ if (mTextView != null) {
+ ExtractedText et = new ExtractedText();
+ if (mTextView.extractText(request, et)) {
+ if ((flags&EXTRACTED_TEXT_MONITOR) != 0) {
+ mTextView.setExtracting(request);
+ }
+ return et;
+ }
+ }
+ 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 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;
+ }
+
+ public static void setComposingSpans(Spannable text) {
+ final Object[] sps = text.getSpans(0, text.length(), Object.class);
+ if (sps != null) {
+ for (int i=sps.length-1; i>=0; i--) {
+ final Object o = sps[i];
+ if (o == COMPOSING) {
+ text.removeSpan(o);
+ continue;
+ }
+ final int fl = text.getSpanFlags(o);
+ if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK))
+ != (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) {
+ text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o),
+ (fl&Spanned.SPAN_POINT_MARK_MASK)
+ | Spanned.SPAN_COMPOSING
+ | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+ }
+
+ text.setSpan(COMPOSING, 0, text.length(),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
+ }
+
+ public static final void removeComposingSpans(Spannable text) {
+ text.removeSpan(COMPOSING);
+ Object[] sps = text.getSpans(0, text.length(), Object.class);
+ if (sps != null) {
+ for (int i=sps.length-1; i>=0; i--) {
+ Object o = sps[i];
+ if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) {
+ text.removeSpan(o);
+ }
+ }
+ }
+ }
+
+ private void replaceText(CharSequence text, int newCursorPosition,
+ boolean composing) {
+ final Editable content = getEditable();
+
+ // 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;
+ }
+
+ 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) {
+ 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);
+ }
+
+ // 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, " ");
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 1c75daa..ed1cd58 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -256,6 +256,20 @@ public class LockPatternUtils {
}
/**
+ * @return Whether tactile feedback for the pattern is enabled.
+ */
+ public boolean isTactileFeedbackEnabled() {
+ return getBoolean(Settings.System.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED);
+ }
+
+ /**
+ * Set whether tactile feedback for the pattern is enabled.
+ */
+ public void setTactileFeedbackEnabled(boolean enabled) {
+ setBoolean(Settings.System.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED, enabled);
+ }
+
+ /**
* 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.
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index bf00eff..7f99ac8 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -32,6 +32,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.os.Debug;
+import android.os.Vibrator;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@@ -48,6 +49,9 @@ import java.util.List;
* "correct" states.
*/
public class LockPatternView extends View {
+ // Vibrator pattern for creating a tactile bump
+ private static final long[] VIBE_PATTERN = {0, 1, 40, 41};
+
private static final boolean PROFILE_DRAWING = false;
private boolean mDrawingProfilingStarted = false;
@@ -88,6 +92,7 @@ public class LockPatternView extends View {
private DisplayMode mPatternDisplayMode = DisplayMode.Correct;
private boolean mInputEnabled = true;
private boolean mInStealthMode = false;
+ private boolean mTactileFeedbackEnabled = true;
private boolean mPatternInProgress = false;
private float mDiameterFactor = 0.5f;
@@ -112,6 +117,8 @@ public class LockPatternView extends View {
private int mBitmapHeight;
+ private Vibrator vibe; // Vibrator for creating tactile feedback
+
/**
* Represents a cell in the 3 X 3 matrix of the unlock pattern view.
*/
@@ -219,6 +226,7 @@ public class LockPatternView extends View {
public LockPatternView(Context context, AttributeSet attrs) {
super(context, attrs);
+ vibe = new Vibrator();
setClickable(true);
@@ -257,6 +265,13 @@ public class LockPatternView extends View {
}
/**
+ * @return Whether the view has tactile feedback enabled.
+ */
+ public boolean isTactileFeedbackEnabled() {
+ return mTactileFeedbackEnabled;
+ }
+
+ /**
* Set whether the view is in stealth mode. If true, there will be no
* visible feedback as the user enters the pattern.
*
@@ -267,6 +282,16 @@ public class LockPatternView extends View {
}
/**
+ * Set whether the view will use tactile feedback. If true, there will be
+ * tactile feedback as the user enters the pattern.
+ *
+ * @param tactileFeedbackEnabled Whether tactile feedback is enabled
+ */
+ public void setTactileFeedbackEnabled(boolean tactileFeedbackEnabled) {
+ mTactileFeedbackEnabled = tactileFeedbackEnabled;
+ }
+
+ /**
* Set the call back for pattern detection.
* @param onPatternListener The call back.
*/
@@ -420,6 +445,9 @@ public class LockPatternView extends View {
addCellToPattern(fillInGapCell);
}
addCellToPattern(cell);
+ if (mTactileFeedbackEnabled){
+ vibe.vibrate(VIBE_PATTERN, -1); // Generate tactile feedback
+ }
return cell;
}
return null;
@@ -900,7 +928,7 @@ public class LockPatternView extends View {
return new SavedState(superState,
LockPatternUtils.patternToString(mPattern),
mPatternDisplayMode.ordinal(),
- mInputEnabled, mInStealthMode);
+ mInputEnabled, mInStealthMode, mTactileFeedbackEnabled);
}
@Override
@@ -913,6 +941,7 @@ public class LockPatternView extends View {
mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()];
mInputEnabled = ss.isInputEnabled();
mInStealthMode = ss.isInStealthMode();
+ mTactileFeedbackEnabled = ss.isTactileFeedbackEnabled();
}
/**
@@ -924,17 +953,19 @@ public class LockPatternView extends View {
private final int mDisplayMode;
private final boolean mInputEnabled;
private final boolean mInStealthMode;
+ private final boolean mTactileFeedbackEnabled;
/**
* Constructor called from {@link LockPatternView#onSaveInstanceState()}
*/
private SavedState(Parcelable superState, String serializedPattern, int displayMode,
- boolean inputEnabled, boolean inStealthMode) {
+ boolean inputEnabled, boolean inStealthMode, boolean tactileFeedbackEnabled) {
super(superState);
mSerializedPattern = serializedPattern;
mDisplayMode = displayMode;
mInputEnabled = inputEnabled;
mInStealthMode = inStealthMode;
+ mTactileFeedbackEnabled = tactileFeedbackEnabled;
}
/**
@@ -946,6 +977,7 @@ public class LockPatternView extends View {
mDisplayMode = in.readInt();
mInputEnabled = (Boolean) in.readValue(null);
mInStealthMode = (Boolean) in.readValue(null);
+ mTactileFeedbackEnabled = (Boolean) in.readValue(null);
}
public String getSerializedPattern() {
@@ -964,6 +996,10 @@ public class LockPatternView extends View {
return mInStealthMode;
}
+ public boolean isTactileFeedbackEnabled(){
+ return mTactileFeedbackEnabled;
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
@@ -971,6 +1007,7 @@ public class LockPatternView extends View {
dest.writeInt(mDisplayMode);
dest.writeValue(mInputEnabled);
dest.writeValue(mInStealthMode);
+ dest.writeValue(mTactileFeedbackEnabled);
}
public static final Parcelable.Creator<SavedState> CREATOR =
diff --git a/core/java/com/android/internal/widget/NumberPicker.java b/core/java/com/android/internal/widget/NumberPicker.java
index 5a7ddcb..5590f1a 100644
--- a/core/java/com/android/internal/widget/NumberPicker.java
+++ b/core/java/com/android/internal/widget/NumberPicker.java
@@ -19,6 +19,7 @@ package com.android.internal.widget;
import android.content.Context;
import android.os.Handler;
import android.text.InputFilter;
+import android.text.InputType;
import android.text.Spanned;
import android.text.method.NumberKeyListener;
import android.util.AttributeSet;
@@ -359,6 +360,12 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
private class NumberRangeKeyListener extends NumberKeyListener {
+ // XXX This doesn't allow for range limits when controlled by a
+ // soft input method!
+ public int getInputType() {
+ return InputType.TYPE_CLASS_NUMBER;
+ }
+
@Override
protected char[] getAcceptedChars() {
return DIGIT_CHARACTERS;
@@ -421,4 +428,10 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
return mStart;
}
-}
+ /**
+ * @return the current value.
+ */
+ public int getCurrent() {
+ return mCurrent;
+ }
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/widget/SlidingDrawer.java b/core/java/com/android/internal/widget/SlidingDrawer.java
index 90a548a..a4045d5 100644
--- a/core/java/com/android/internal/widget/SlidingDrawer.java
+++ b/core/java/com/android/internal/widget/SlidingDrawer.java
@@ -74,6 +74,7 @@ import com.android.internal.R;
* @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
*/
public class SlidingDrawer extends ViewGroup {
@@ -124,6 +125,7 @@ public class SlidingDrawer extends ViewGroup {
private long mCurrentAnimationTime;
private int mTouchDelta;
private boolean mAnimating;
+ private boolean mAllowSingleTap;
private boolean mAnimateOnClick;
/**
@@ -186,6 +188,7 @@ public class SlidingDrawer extends ViewGroup {
mVertical = orientation == ORIENTATION_VERTICAL;
mBottomOffset = (int) a.getDimension(R.styleable.SlidingDrawer_bottomOffset, 0.0f);
mTopOffset = (int) a.getDimension(R.styleable.SlidingDrawer_topOffset, 0.0f);
+ mAllowSingleTap = a.getBoolean(R.styleable.SlidingDrawer_allowSingleTap, true);
mAnimateOnClick = a.getBoolean(R.styleable.SlidingDrawer_animateOnClick, true);
int handleId = a.getResourceId(R.styleable.SlidingDrawer_handle, 0);
@@ -241,11 +244,11 @@ public class SlidingDrawer extends ViewGroup {
measureChild(handle, widthMeasureSpec, heightMeasureSpec);
if (mVertical) {
- int height = heightSpecSize - handle.getMeasuredHeight();
+ int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset;
mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
} else {
- int width = widthSpecSize - handle.getMeasuredWidth();
+ int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;
mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY));
}
@@ -269,6 +272,12 @@ public class SlidingDrawer extends ViewGroup {
} else {
canvas.drawBitmap(cache, handle.getRight(), 0, null);
}
+ } else {
+ canvas.save();
+ canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset,
+ isVertical ? handle.getTop() - mTopOffset : 0);
+ drawChild(canvas, mContent, drawingTime);
+ canvas.restore();
}
} else if (mExpanded) {
drawChild(canvas, mContent, drawingTime);
@@ -415,12 +424,14 @@ public class SlidingDrawer extends ViewGroup {
(!mExpanded && left > mBottomOffset + mRight - mLeft -
mHandleWidth - TAP_THRESHOLD)) {
- playSoundEffect(SoundEffectConstants.CLICK);
+ if (mAllowSingleTap) {
+ playSoundEffect(SoundEffectConstants.CLICK);
- if (mExpanded) {
- animateClose(vertical ? top : left);
- } else {
- animateOpen(vertical ? top : left);
+ if (mExpanded) {
+ animateClose(vertical ? top : left);
+ } else {
+ animateOpen(vertical ? top : left);
+ }
}
} else {
@@ -889,6 +900,9 @@ public class SlidingDrawer extends ViewGroup {
if (mLocked) {
return;
}
+ // mAllowSingleTap isn't relevant here; you're *always*
+ // allowed to open/close the drawer by clicking with the
+ // trackball.
if (mAnimateOnClick) {
animateToggle();
diff --git a/core/java/com/google/android/mms/pdu/PduComposer.java b/core/java/com/google/android/mms/pdu/PduComposer.java
index acece47..094e992 100644
--- a/core/java/com/google/android/mms/pdu/PduComposer.java
+++ b/core/java/com/google/android/mms/pdu/PduComposer.java
@@ -24,7 +24,6 @@ import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.HashMap;
@@ -461,7 +460,7 @@ public class PduComposer {
int version = mPduHeader.getOctet(field);
if (0 == version) {
- appendShortInteger(PduHeaders.MMS_VERSION_1_3);
+ appendShortInteger(PduHeaders.CURRENT_MMS_VERSION);
} else {
appendShortInteger(version);
}
@@ -952,7 +951,7 @@ public class PduComposer {
appendQuotedString("<" + new String(contentId) + ">");
}
}
-
+
// content-location
byte[] contentLocation = part.getContentLocation();
if (null != contentLocation) {
diff --git a/core/java/com/google/android/mms/pdu/PduHeaders.java b/core/java/com/google/android/mms/pdu/PduHeaders.java
index 3769349..4313815 100644
--- a/core/java/com/google/android/mms/pdu/PduHeaders.java
+++ b/core/java/com/google/android/mms/pdu/PduHeaders.java
@@ -150,13 +150,14 @@ public class PduHeaders {
/**
* X-Mms-MMS-Version field types.
*/
- // Current version is 1.3.
public static final int MMS_VERSION_1_3 = ((1 << 4) | 3);
-
public static final int MMS_VERSION_1_2 = ((1 << 4) | 2);
public static final int MMS_VERSION_1_1 = ((1 << 4) | 1);
public static final int MMS_VERSION_1_0 = ((1 << 4) | 0);
+ // Current version is 1.2.
+ public static final int CURRENT_MMS_VERSION = MMS_VERSION_1_2;
+
/**
* From field type components.
*/
@@ -475,7 +476,7 @@ public class PduHeaders {
break;
case MMS_VERSION:
if ((value < MMS_VERSION_1_0)|| (value > MMS_VERSION_1_3)) {
- value = MMS_VERSION_1_3; //1.3 is the default version.
+ value = CURRENT_MMS_VERSION; // Current version is the default value.
}
break;
case MESSAGE_TYPE:
diff --git a/core/java/com/google/android/mms/pdu/PduPersister.java b/core/java/com/google/android/mms/pdu/PduPersister.java
index 4089c58..d2a41f1 100644
--- a/core/java/com/google/android/mms/pdu/PduPersister.java
+++ b/core/java/com/google/android/mms/pdu/PduPersister.java
@@ -729,36 +729,13 @@ public class PduPersister {
}
is = mContentResolver.openInputStream(dataUri);
- boolean fakeRawAmr = contentType.equals("audio/amr");
-
if (LOCAL_LOGV) {
Log.v(TAG, "Saving data to: " + uri);
}
byte[] buffer = new byte[256];
for (int len = 0; (len = is.read(buffer)) != -1; ) {
- if (fakeRawAmr && len > 32) {
- // This is a Gross Hack. We can only record audio to amr format in a 3gpp container.
- // Millions of handsets out there only support what is essentially raw AMR.
- // We work around this issue by extracting the AMR data out of the 3gpp container
- // (in a really stupid and non-portable way), prepending a little header, and then
- // using that as the attachment.
- // This also requires some cooperation from the SoundRecorder, which ends up saving
- // a "recording.amr" file with mime type audio/amr, even though it's a 3gpp file.
- if (buffer[4] == 0x66 && // f
- buffer[5] == 0x74 && // t
- buffer[6] == 0x79 && // y
- buffer[7] == 0x70) { // p
- byte [] amrHeader = new byte [] { 0x23, 0x21, 0x41, 0x4d, 0x52, 0x0a };
- os.write(amrHeader);
- os.write(buffer, 32, len - 32);
- } else {
- os.write(buffer, 0, len);
- }
- fakeRawAmr = false;
- } else {
- os.write(buffer, 0, len);
- }
+ os.write(buffer, 0, len);
}
} else {
if (LOCAL_LOGV) {
diff --git a/core/java/com/google/android/mms/pdu/SendReq.java b/core/java/com/google/android/mms/pdu/SendReq.java
index e2f1101..9081b0c 100644
--- a/core/java/com/google/android/mms/pdu/SendReq.java
+++ b/core/java/com/google/android/mms/pdu/SendReq.java
@@ -29,7 +29,7 @@ public class SendReq extends MultimediaMessagePdu {
try {
setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
- setMmsVersion(PduHeaders.MMS_VERSION_1_3);
+ setMmsVersion(PduHeaders.CURRENT_MMS_VERSION);
// FIXME: Content-type must be decided according to whether
// SMIL part present.
setContentType("application/vnd.wap.multipart.related".getBytes());
diff --git a/core/java/com/google/android/net/ParentalControl.java b/core/java/com/google/android/net/ParentalControl.java
index 368b885..71a3958 100644
--- a/core/java/com/google/android/net/ParentalControl.java
+++ b/core/java/com/google/android/net/ParentalControl.java
@@ -23,7 +23,13 @@ import android.os.ServiceManager;
import android.util.Log;
public class ParentalControl {
-
+ /**
+ * Strings to identify your app. To enable parental control checking for
+ * new apps, please add it here, and configure GServices accordingly.
+ */
+ public static final String VENDING = "vending";
+ public static final String YOUTUBE = "youtube";
+
/**
* This interface is supplied to getParentalControlState and is callback upon with
* the state of parental control.
@@ -36,28 +42,29 @@ public class ParentalControl {
*/
void onResult(ParentalControlState state);
}
-
+
private static class RemoteCallback extends IParentalControlCallback.Stub {
private Callback mCallback;
-
+
public RemoteCallback(Callback callback) {
mCallback = callback;
}
-
+
public void onResult(ParentalControlState state) {
if (mCallback != null) {
mCallback.onResult(state);
}
}
};
-
- public static void getParentalControlState(Callback callback) {
+
+ public static void getParentalControlState(Callback callback,
+ String requestingApp) {
ICheckinService service =
ICheckinService.Stub.asInterface(ServiceManager.getService("checkin"));
-
+
RemoteCallback remoteCallback = new RemoteCallback(callback);
try {
- service.getParentalControlState(remoteCallback);
+ service.getParentalControlState(remoteCallback, requestingApp);
} catch (RemoteException e) {
// This should never happen.
Log.e("ParentalControl", "Failed to talk to the checkin service.");
diff --git a/core/java/com/google/android/util/GoogleWebContentHelper.java b/core/java/com/google/android/util/GoogleWebContentHelper.java
index 5709522..7500ec3 100644
--- a/core/java/com/google/android/util/GoogleWebContentHelper.java
+++ b/core/java/com/google/android/util/GoogleWebContentHelper.java
@@ -22,6 +22,7 @@ import android.net.http.SslError;
import android.os.Message;
import android.provider.Settings;
import android.text.TextUtils;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -81,10 +82,26 @@ public class GoogleWebContentHelper {
*/
public GoogleWebContentHelper setUrlsFromGservices(String secureSetting, String prettySetting) {
ContentResolver contentResolver = mContext.getContentResolver();
- mSecureUrl = fillUrl(Settings.Gservices.getString(contentResolver, secureSetting));
- mPrettyUrl = fillUrl(Settings.Gservices.getString(contentResolver, prettySetting));
+ mSecureUrl = fillUrl(Settings.Gservices.getString(contentResolver, secureSetting),
+ mContext);
+ mPrettyUrl = fillUrl(Settings.Gservices.getString(contentResolver, prettySetting),
+ mContext);
return this;
}
+
+ /**
+ * Fetch directly from provided urls.
+ *
+ * @param secureUrl The HTTPS URL.
+ * @param prettyUrl The pretty URL.
+ * @return This {@link GoogleWebContentHelper} so methods can be chained.
+ */
+ public GoogleWebContentHelper setUrls(String secureUrl, String prettyUrl) {
+ mSecureUrl = fillUrl(secureUrl, mContext);
+ mPrettyUrl = fillUrl(prettyUrl, mContext);
+ return this;
+ }
+
/**
* Sets the message that will be shown if we are unable to load the page.
@@ -113,6 +130,22 @@ public class GoogleWebContentHelper {
mWebView.loadUrl(mSecureUrl);
return this;
}
+
+ /**
+ * Helper to handle the back key. Returns true if the back key was handled,
+ * otherwise returns false.
+ * @param event the key event sent to {@link Activity#dispatchKeyEvent()}
+ */
+ public boolean handleKey(KeyEvent event) {
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
+ && event.getAction() == KeyEvent.ACTION_DOWN) {
+ if (mWebView.canGoBack()) {
+ mWebView.goBack();
+ return true;
+ }
+ }
+ return false;
+ }
/**
* Returns the layout containing the web view, progress bar, and text view.
@@ -138,12 +171,23 @@ public class GoogleWebContentHelper {
* @param url The URL in Formatter style for the extra info to be filled in.
* @return The filled URL.
*/
- private static String fillUrl(String url) {
+ private static String fillUrl(String url, Context context) {
if (TextUtils.isEmpty(url)) {
return "";
}
-
+
+ /* We add another layer of indirection here to allow mcc's to fill
+ * in Locales for TOS. TODO - REMOVE when needed locales supported
+ * natively (when not shipping devices to country X without support
+ * for their locale).
+ */
+ String localeReplacement = context.
+ getString(com.android.internal.R.string.locale_replacement);
+ if (localeReplacement != null && localeReplacement.length() != 0) {
+ url = String.format(url, localeReplacement);
+ }
+
Locale locale = Locale.getDefault();
String tmp = locale.getLanguage() + "_" + locale.getCountry().toLowerCase();
return String.format(url, tmp);
diff --git a/core/java/jarjar-rules.txt b/core/java/jarjar-rules.txt
new file mode 100644
index 0000000..5fdb022
--- /dev/null
+++ b/core/java/jarjar-rules.txt
@@ -0,0 +1,2 @@
+rule org.apache.commons com.android.internal.apache.commons
+