summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/accounts/AccountAuthenticatorActivity.java1
-rw-r--r--core/java/android/accounts/AccountManagerFuture.java3
-rw-r--r--core/java/android/accounts/GrantCredentialsPermissionActivity.java2
-rw-r--r--core/java/android/animation/AnimatorInflater.java2
-rw-r--r--core/java/android/animation/ArgbEvaluator.java13
-rw-r--r--core/java/android/animation/FloatArrayEvaluator.java77
-rw-r--r--core/java/android/animation/IntArrayEvaluator.java75
-rw-r--r--core/java/android/animation/ObjectAnimator.java348
-rw-r--r--core/java/android/animation/PointFEvaluator.java83
-rw-r--r--core/java/android/animation/PropertyValuesHolder.java659
-rw-r--r--core/java/android/animation/RectEvaluator.java47
-rw-r--r--core/java/android/animation/TypeConverter.java68
-rw-r--r--core/java/android/animation/ValueAnimator.java18
-rw-r--r--core/java/android/annotation/IntDef.java60
-rw-r--r--core/java/android/annotation/NonNull.java34
-rw-r--r--core/java/android/annotation/Nullable.java41
-rw-r--r--core/java/android/annotation/StringDef.java51
-rw-r--r--core/java/android/app/ActionBar.java35
-rw-r--r--core/java/android/app/Activity.java114
-rw-r--r--core/java/android/app/ActivityOptions.java179
-rw-r--r--core/java/android/app/AlertDialog.java1
-rw-r--r--core/java/android/app/AppOpsManager.java2
-rw-r--r--core/java/android/app/ApplicationErrorReport.java1
-rw-r--r--core/java/android/app/ApplicationPackageManager.java2
-rw-r--r--core/java/android/app/Dialog.java1
-rw-r--r--core/java/android/app/ExpandableListActivity.java1
-rw-r--r--core/java/android/app/Fragment.java9
-rw-r--r--core/java/android/app/FragmentBreadCrumbs.java15
-rw-r--r--core/java/android/app/IWallpaperManager.aidl10
-rw-r--r--core/java/android/app/MediaRouteButton.java11
-rw-r--r--core/java/android/app/Notification.java11
-rw-r--r--core/java/android/app/PendingIntent.java51
-rw-r--r--core/java/android/app/ResultInfo.java4
-rw-r--r--core/java/android/app/SearchManager.java2
-rw-r--r--core/java/android/app/SharedPreferencesImpl.java1
-rw-r--r--core/java/android/app/TaskStackBuilder.java7
-rw-r--r--core/java/android/app/TimePickerDialog.java45
-rw-r--r--core/java/android/app/WallpaperManager.java22
-rw-r--r--core/java/android/app/backup/FullBackup.java3
-rw-r--r--core/java/android/app/backup/SharedPreferencesBackupHelper.java1
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java2
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java1
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java4
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java2
-rw-r--r--core/java/android/bluetooth/BluetoothGatt.java9
-rw-r--r--core/java/android/bluetooth/BluetoothGattCharacteristic.java1
-rw-r--r--core/java/android/bluetooth/BluetoothGattServer.java9
-rw-r--r--core/java/android/bluetooth/BluetoothGattServerCallback.java2
-rw-r--r--core/java/android/bluetooth/BluetoothHealth.java1
-rw-r--r--core/java/android/bluetooth/BluetoothInputDevice.java1
-rw-r--r--core/java/android/bluetooth/BluetoothMap.java1
-rw-r--r--core/java/android/bluetooth/BluetoothPan.java1
-rw-r--r--core/java/android/bluetooth/BluetoothPbap.java1
-rw-r--r--core/java/android/bluetooth/BluetoothServerSocket.java1
-rw-r--r--core/java/android/bluetooth/BluetoothSocket.java5
-rw-r--r--core/java/android/bluetooth/BluetoothTetheringDataTracker.java13
-rw-r--r--core/java/android/content/AsyncTaskLoader.java34
-rw-r--r--core/java/android/content/ClipboardManager.java2
-rw-r--r--core/java/android/content/ContentResolver.java167
-rw-r--r--core/java/android/content/Context.java208
-rw-r--r--core/java/android/content/ContextWrapper.java4
-rw-r--r--core/java/android/content/CursorLoader.java1
-rw-r--r--core/java/android/content/Entity.java3
-rw-r--r--core/java/android/content/IContentService.aidl64
-rw-r--r--core/java/android/content/ISyncServiceAdapter.aidl (renamed from core/java/android/content/IAnonymousSyncAdapter.aidl)2
-rw-r--r--core/java/android/content/Intent.java32
-rw-r--r--core/java/android/content/Loader.java2
-rw-r--r--core/java/android/content/PeriodicSync.java72
-rw-r--r--core/java/android/content/RestrictionEntry.java2
-rw-r--r--core/java/android/content/SyncInfo.java24
-rw-r--r--core/java/android/content/SyncRequest.java201
-rw-r--r--core/java/android/content/SyncService.java211
-rw-r--r--core/java/android/content/pm/ActivityInfo.java27
-rw-r--r--core/java/android/content/pm/FeatureInfo.java1
-rw-r--r--core/java/android/content/pm/PackageManager.java8
-rw-r--r--core/java/android/content/pm/XmlSerializerAndParser.java1
-rw-r--r--core/java/android/content/res/AssetManager.java1
-rw-r--r--core/java/android/content/res/ColorStateList.java13
-rw-r--r--core/java/android/content/res/Resources.java1
-rw-r--r--core/java/android/content/res/TypedArray.java1
-rw-r--r--core/java/android/database/CursorToBulkCursorAdaptor.java1
-rw-r--r--core/java/android/database/sqlite/SQLiteOpenHelper.java1
-rw-r--r--core/java/android/ddm/DdmHandleNativeHeap.java1
-rw-r--r--core/java/android/ddm/DdmHandleProfiling.java1
-rw-r--r--core/java/android/gesture/GestureOverlayView.java13
-rw-r--r--core/java/android/hardware/Camera.java1
-rw-r--r--core/java/android/hardware/SerialManager.java4
-rw-r--r--core/java/android/hardware/SerialPort.java5
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java1
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java2
-rw-r--r--core/java/android/hardware/camera2/CaptureFailure.java2
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java1
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java10
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java47
-rw-r--r--core/java/android/hardware/camera2/package.html2
-rw-r--r--core/java/android/hardware/display/WifiDisplayStatus.java2
-rw-r--r--core/java/android/hardware/location/GeofenceHardwareRequest.java2
-rw-r--r--core/java/android/hardware/usb/UsbAccessory.java2
-rw-r--r--core/java/android/hardware/usb/UsbDevice.java4
-rw-r--r--core/java/android/hardware/usb/UsbEndpoint.java1
-rw-r--r--core/java/android/hardware/usb/UsbInterface.java1
-rw-r--r--core/java/android/inputmethodservice/ExtractButton.java8
-rw-r--r--core/java/android/inputmethodservice/ExtractEditText.java8
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java1
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java15
-rw-r--r--core/java/android/inputmethodservice/KeyboardView.java13
-rw-r--r--core/java/android/net/BaseNetworkStateTracker.java9
-rw-r--r--core/java/android/net/CaptivePortalTracker.java55
-rw-r--r--core/java/android/net/ConnectivityManager.java19
-rw-r--r--core/java/android/net/DhcpInfo.java1
-rw-r--r--core/java/android/net/DhcpResults.java3
-rw-r--r--core/java/android/net/DummyDataStateTracker.java5
-rw-r--r--core/java/android/net/EthernetDataTracker.java5
-rw-r--r--core/java/android/net/IConnectivityManager.aidl2
-rw-r--r--core/java/android/net/INetworkManagementEventObserver.aidl9
-rw-r--r--core/java/android/net/LinkSocketNotifier.java2
-rw-r--r--core/java/android/net/MailTo.java1
-rw-r--r--core/java/android/net/MobileDataStateTracker.java5
-rw-r--r--core/java/android/net/NetworkConfig.java1
-rw-r--r--core/java/android/net/NetworkInfo.java26
-rw-r--r--core/java/android/net/NetworkStateTracker.java5
-rw-r--r--core/java/android/net/ProxyProperties.java1
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java6
-rw-r--r--core/java/android/net/SSLSessionCache.java38
-rw-r--r--core/java/android/net/SntpClient.java1
-rw-r--r--core/java/android/net/dhcp/DhcpAckPacket.java1
-rw-r--r--core/java/android/net/dhcp/DhcpOfferPacket.java1
-rw-r--r--core/java/android/net/dhcp/DhcpPacket.java3
-rw-r--r--core/java/android/net/dhcp/DhcpStateMachine.java1
-rw-r--r--core/java/android/net/http/AndroidHttpClientConnection.java4
-rw-r--r--core/java/android/net/http/Connection.java1
-rw-r--r--core/java/android/net/http/ConnectionThread.java2
-rw-r--r--core/java/android/net/http/HttpConnection.java2
-rw-r--r--core/java/android/net/http/HttpResponseCache.java4
-rw-r--r--core/java/android/net/http/HttpsConnection.java1
-rw-r--r--core/java/android/net/http/Request.java3
-rw-r--r--core/java/android/net/http/RequestQueue.java4
-rw-r--r--core/java/android/net/nsd/NsdManager.java2
-rw-r--r--core/java/android/nfc/NdefRecord.java1
-rw-r--r--core/java/android/nfc/tech/Ndef.java1
-rw-r--r--core/java/android/nfc/tech/NdefFormatable.java1
-rw-r--r--core/java/android/os/BatteryProperties.java6
-rw-r--r--core/java/android/os/BatteryProperty.aidl19
-rw-r--r--core/java/android/os/BatteryProperty.java70
-rw-r--r--core/java/android/os/BatteryStats.java1
-rw-r--r--core/java/android/os/CommonClock.java9
-rw-r--r--core/java/android/os/CommonTimeConfig.java1
-rw-r--r--core/java/android/os/CountDownTimer.java2
-rw-r--r--core/java/android/os/Debug.java2
-rw-r--r--core/java/android/os/DropBoxManager.java3
-rw-r--r--core/java/android/os/FileObserver.java3
-rw-r--r--core/java/android/os/FileUtils.java2
-rw-r--r--core/java/android/os/IBatteryPropertiesRegistrar.aidl2
-rw-r--r--core/java/android/os/IBinder.java1
-rw-r--r--core/java/android/os/Looper.java1
-rw-r--r--core/java/android/os/MessageQueue.java12
-rw-r--r--core/java/android/os/NullVibrator.java2
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java4
-rw-r--r--core/java/android/os/Process.java13
-rw-r--r--core/java/android/os/Registrant.java1
-rw-r--r--core/java/android/os/RegistrantList.java2
-rw-r--r--core/java/android/os/SystemProperties.java2
-rw-r--r--core/java/android/os/SystemService.java2
-rw-r--r--core/java/android/os/Trace.java2
-rw-r--r--core/java/android/os/UserManager.java1
-rw-r--r--core/java/android/preference/CheckBoxPreference.java15
-rw-r--r--core/java/android/preference/DialogPreference.java24
-rw-r--r--core/java/android/preference/EditTextPreference.java10
-rw-r--r--core/java/android/preference/ListPreference.java22
-rw-r--r--core/java/android/preference/MultiCheckPreference.java21
-rw-r--r--core/java/android/preference/MultiSelectListPreference.java22
-rw-r--r--core/java/android/preference/Preference.java63
-rw-r--r--core/java/android/preference/PreferenceCategory.java13
-rw-r--r--core/java/android/preference/PreferenceFrameLayout.java14
-rw-r--r--core/java/android/preference/PreferenceGroup.java14
-rw-r--r--core/java/android/preference/PreferenceInflater.java3
-rw-r--r--core/java/android/preference/PreferenceScreen.java1
-rw-r--r--core/java/android/preference/RingtonePreference.java14
-rw-r--r--core/java/android/preference/SeekBarDialogPreference.java17
-rw-r--r--core/java/android/preference/SeekBarPreference.java13
-rw-r--r--core/java/android/preference/SwitchPreference.java27
-rw-r--r--core/java/android/preference/TwoStatePreference.java8
-rw-r--r--core/java/android/preference/VolumePreference.java17
-rw-r--r--core/java/android/provider/Contacts.java1
-rw-r--r--core/java/android/provider/ContactsContract.java3
-rw-r--r--core/java/android/provider/Settings.java91
-rw-r--r--core/java/android/service/textservice/SpellCheckerService.java1
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java1
-rw-r--r--core/java/android/speech/srec/Recognizer.java2
-rw-r--r--core/java/android/speech/tts/AbstractEventLogger.java124
-rw-r--r--core/java/android/speech/tts/AbstractSynthesisCallback.java29
-rw-r--r--core/java/android/speech/tts/AudioPlaybackHandler.java2
-rw-r--r--core/java/android/speech/tts/AudioPlaybackQueueItem.java8
-rw-r--r--core/java/android/speech/tts/EventLogTags.logtags3
-rw-r--r--core/java/android/speech/tts/EventLogger.java178
-rw-r--r--core/java/android/speech/tts/EventLoggerV1.java80
-rw-r--r--core/java/android/speech/tts/EventLoggerV2.java73
-rw-r--r--core/java/android/speech/tts/FileSynthesisCallback.java191
-rw-r--r--core/java/android/speech/tts/ITextToSpeechCallback.aidl44
-rw-r--r--core/java/android/speech/tts/ITextToSpeechService.aidl54
-rw-r--r--core/java/android/speech/tts/PlaybackQueueItem.java12
-rw-r--r--core/java/android/speech/tts/PlaybackSynthesisCallback.java152
-rw-r--r--core/java/android/speech/tts/RequestConfig.java213
-rw-r--r--core/java/android/speech/tts/RequestConfigHelper.java170
-rw-r--r--core/java/android/speech/tts/SilencePlaybackQueueItem.java13
-rw-r--r--core/java/android/speech/tts/SynthesisCallback.java79
-rw-r--r--core/java/android/speech/tts/SynthesisPlaybackQueueItem.java26
-rw-r--r--core/java/android/speech/tts/SynthesisRequestV2.aidl20
-rw-r--r--core/java/android/speech/tts/SynthesisRequestV2.java132
-rw-r--r--core/java/android/speech/tts/TextToSpeech.java22
-rw-r--r--core/java/android/speech/tts/TextToSpeechClient.java1054
-rw-r--r--core/java/android/speech/tts/TextToSpeechService.java815
-rw-r--r--core/java/android/speech/tts/VoiceInfo.aidl20
-rw-r--r--core/java/android/speech/tts/VoiceInfo.java325
-rw-r--r--core/java/android/text/Html.java3
-rw-r--r--core/java/android/text/format/DateUtils.java1
-rw-r--r--core/java/android/text/method/HideReturnsTransformationMethod.java7
-rw-r--r--core/java/android/text/method/PasswordTransformationMethod.java1
-rw-r--r--core/java/android/text/method/SingleLineTransformationMethod.java9
-rw-r--r--core/java/android/text/style/DrawableMarginSpan.java1
-rw-r--r--core/java/android/text/style/DynamicDrawableSpan.java3
-rw-r--r--core/java/android/text/style/IconMarginSpan.java1
-rw-r--r--core/java/android/text/style/LineHeightSpan.java2
-rw-r--r--core/java/android/text/style/MetricAffectingSpan.java1
-rw-r--r--core/java/android/text/style/SuggestionSpan.java12
-rw-r--r--core/java/android/transition/Recolor.java8
-rw-r--r--core/java/android/transition/Scene.java11
-rw-r--r--core/java/android/util/EventLogTags.java6
-rw-r--r--core/java/android/util/LocalLog.java1
-rw-r--r--core/java/android/util/LongArray.java160
-rw-r--r--core/java/android/util/LongSparseLongArray.java2
-rw-r--r--core/java/android/util/Slog.java5
-rw-r--r--core/java/android/view/AccessibilityInteractionController.java11
-rw-r--r--core/java/android/view/AccessibilityIterators.java2
-rw-r--r--core/java/android/view/ContextThemeWrapper.java1
-rw-r--r--core/java/android/view/Display.java2
-rw-r--r--core/java/android/view/DisplayInfo.java2
-rw-r--r--core/java/android/view/DisplayList.java33
-rw-r--r--core/java/android/view/GLRenderer.java1610
-rw-r--r--core/java/android/view/HapticFeedbackConstants.java5
-rw-r--r--core/java/android/view/HardwareRenderer.java1674
-rw-r--r--core/java/android/view/InputQueue.java1
-rw-r--r--core/java/android/view/KeyEvent.java1
-rw-r--r--core/java/android/view/Surface.java10
-rw-r--r--core/java/android/view/SurfaceControl.java12
-rw-r--r--core/java/android/view/SurfaceView.java9
-rw-r--r--core/java/android/view/TextureView.java30
-rw-r--r--core/java/android/view/View.java269
-rw-r--r--core/java/android/view/ViewGroup.java30
-rw-r--r--core/java/android/view/ViewRootImpl.java121
-rw-r--r--core/java/android/view/ViewStub.java13
-rw-r--r--core/java/android/view/Window.java18
-rw-r--r--core/java/android/view/WindowManagerPolicy.java27
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java60
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java6
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java36
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java178
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfoCache.java24
-rw-r--r--core/java/android/view/animation/BounceInterpolator.java1
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java6
-rw-r--r--core/java/android/view/inputmethod/ExtractedTextRequest.java1
-rw-r--r--core/java/android/view/inputmethod/InputBinding.java1
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java29
-rw-r--r--core/java/android/view/inputmethod/InputMethodSubtype.java6
-rw-r--r--core/java/android/webkit/CacheManager.java6
-rw-r--r--core/java/android/webkit/DateSorter.java1
-rw-r--r--core/java/android/webkit/Plugin.java1
-rw-r--r--core/java/android/webkit/WebResourceResponse.java2
-rw-r--r--core/java/android/webkit/WebView.java71
-rw-r--r--core/java/android/webkit/WebViewFactory.java2
-rw-r--r--core/java/android/webkit/WebViewFactoryProvider.java5
-rw-r--r--core/java/android/widget/AbsListView.java134
-rw-r--r--core/java/android/widget/AbsSeekBar.java12
-rw-r--r--core/java/android/widget/AbsSpinner.java12
-rw-r--r--core/java/android/widget/AbsoluteLayout.java13
-rw-r--r--core/java/android/widget/ActivityChooserView.java29
-rw-r--r--core/java/android/widget/AdapterView.java12
-rw-r--r--core/java/android/widget/AdapterViewAnimator.java11
-rw-r--r--core/java/android/widget/AdapterViewFlipper.java15
-rw-r--r--core/java/android/widget/AnalogClock.java17
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java14
-rw-r--r--core/java/android/widget/Button.java8
-rw-r--r--core/java/android/widget/CalendarView.java2684
-rw-r--r--core/java/android/widget/CheckBox.java8
-rw-r--r--core/java/android/widget/CheckedTextView.java12
-rw-r--r--core/java/android/widget/Chronometer.java15
-rw-r--r--core/java/android/widget/CompoundButton.java13
-rw-r--r--core/java/android/widget/DatePicker.java1238
-rw-r--r--core/java/android/widget/DateTimeView.java3
-rw-r--r--core/java/android/widget/DialerFilter.java2
-rw-r--r--core/java/android/widget/EditText.java8
-rw-r--r--core/java/android/widget/Editor.java8
-rw-r--r--core/java/android/widget/ExpandableListView.java14
-rw-r--r--core/java/android/widget/FastScroller.java245
-rw-r--r--core/java/android/widget/FrameLayout.java12
-rw-r--r--core/java/android/widget/Gallery.java14
-rw-r--r--core/java/android/widget/GridLayout.java56
-rw-r--r--core/java/android/widget/GridView.java66
-rw-r--r--core/java/android/widget/HorizontalScrollView.java13
-rw-r--r--core/java/android/widget/ImageButton.java13
-rw-r--r--core/java/android/widget/ImageView.java13
-rw-r--r--core/java/android/widget/LegacyTimePickerDelegate.java638
-rw-r--r--core/java/android/widget/LinearLayout.java44
-rw-r--r--core/java/android/widget/ListPopupWindow.java1
-rw-r--r--core/java/android/widget/ListView.java41
-rw-r--r--core/java/android/widget/MultiAutoCompleteTextView.java9
-rw-r--r--core/java/android/widget/NumberPicker.java38
-rw-r--r--core/java/android/widget/OverScroller.java21
-rw-r--r--core/java/android/widget/PopupWindow.java7
-rw-r--r--core/java/android/widget/ProgressBar.java68
-rw-r--r--core/java/android/widget/QuickContactBadge.java9
-rw-r--r--core/java/android/widget/RadialTimePickerView.java1396
-rw-r--r--core/java/android/widget/RadioButton.java10
-rw-r--r--core/java/android/widget/RatingBar.java14
-rw-r--r--core/java/android/widget/RelativeLayout.java23
-rw-r--r--core/java/android/widget/RemoteViewsAdapter.java2
-rw-r--r--core/java/android/widget/ScrollView.java12
-rw-r--r--core/java/android/widget/Scroller.java78
-rw-r--r--core/java/android/widget/SearchView.java25
-rw-r--r--core/java/android/widget/SeekBar.java8
-rw-r--r--core/java/android/widget/SlidingDrawer.java29
-rw-r--r--core/java/android/widget/Space.java12
-rw-r--r--core/java/android/widget/Spinner.java62
-rw-r--r--core/java/android/widget/StackView.java13
-rw-r--r--core/java/android/widget/Switch.java167
-rw-r--r--core/java/android/widget/TabHost.java13
-rw-r--r--core/java/android/widget/TabWidget.java10
-rw-r--r--core/java/android/widget/TextClock.java18
-rw-r--r--core/java/android/widget/TextView.java21
-rw-r--r--core/java/android/widget/TimePicker.java698
-rw-r--r--core/java/android/widget/TimePickerDelegate.java1380
-rw-r--r--core/java/android/widget/Toast.java17
-rw-r--r--core/java/android/widget/ToggleButton.java20
-rw-r--r--core/java/android/widget/TwoLineListItem.java12
-rw-r--r--core/java/android/widget/VideoView.java16
-rw-r--r--core/java/android/widget/ZoomButton.java8
-rw-r--r--core/java/android/widget/ZoomButtonsController.java1
337 files changed, 16122 insertions, 6124 deletions
diff --git a/core/java/android/accounts/AccountAuthenticatorActivity.java b/core/java/android/accounts/AccountAuthenticatorActivity.java
index 6a55ddf..f9284e6 100644
--- a/core/java/android/accounts/AccountAuthenticatorActivity.java
+++ b/core/java/android/accounts/AccountAuthenticatorActivity.java
@@ -17,7 +17,6 @@
package android.accounts;
import android.app.Activity;
-import android.content.Intent;
import android.os.Bundle;
/**
diff --git a/core/java/android/accounts/AccountManagerFuture.java b/core/java/android/accounts/AccountManagerFuture.java
index a1ab00c..af00a08 100644
--- a/core/java/android/accounts/AccountManagerFuture.java
+++ b/core/java/android/accounts/AccountManagerFuture.java
@@ -15,10 +15,7 @@
*/
package android.accounts;
-import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
import java.io.IOException;
/**
diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
index 8b01c6a..12b2b9c 100644
--- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java
+++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
@@ -16,7 +16,6 @@
package android.accounts;
import android.app.Activity;
-import android.content.pm.RegisteredServicesCache;
import android.content.res.Resources;
import android.os.Bundle;
import android.widget.TextView;
@@ -30,7 +29,6 @@ import android.text.TextUtils;
import com.android.internal.R;
import java.io.IOException;
-import java.net.Authenticator;
/**
* @hide
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index d753e32..20236aa 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -215,7 +215,7 @@ public class AnimatorInflater {
(toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
// special case for colors: ignore valueType and get ints
getFloats = false;
- anim.setEvaluator(new ArgbEvaluator());
+ evaluator = ArgbEvaluator.getInstance();
}
if (getFloats) {
diff --git a/core/java/android/animation/ArgbEvaluator.java b/core/java/android/animation/ArgbEvaluator.java
index 717a3d9..ed07195 100644
--- a/core/java/android/animation/ArgbEvaluator.java
+++ b/core/java/android/animation/ArgbEvaluator.java
@@ -21,6 +21,19 @@ package android.animation;
* values that represent ARGB colors.
*/
public class ArgbEvaluator implements TypeEvaluator {
+ private static final ArgbEvaluator sInstance = new ArgbEvaluator();
+
+ /**
+ * Returns an instance of <code>ArgbEvaluator</code> that may be used in
+ * {@link ValueAnimator#setEvaluator(TypeEvaluator)}. The same instance may
+ * be used in multiple <code>Animator</code>s because it holds no state.
+ * @return An instance of <code>ArgbEvalutor</code>.
+ *
+ * @hide
+ */
+ public static ArgbEvaluator getInstance() {
+ return sInstance;
+ }
/**
* This function returns the calculated in-between value for a color
diff --git a/core/java/android/animation/FloatArrayEvaluator.java b/core/java/android/animation/FloatArrayEvaluator.java
new file mode 100644
index 0000000..9ae1197
--- /dev/null
+++ b/core/java/android/animation/FloatArrayEvaluator.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 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.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>float[]</code> values.
+ * Each index into the array is treated as a separate value to interpolate. For example,
+ * evaluating <code>{100, 200}</code> and <code>{300, 400}</code> will interpolate the value at
+ * the first index between 100 and 300 and the value at the second index value between 200 and 400.
+ */
+public class FloatArrayEvaluator implements TypeEvaluator<float[]> {
+
+ private float[] mArray;
+
+ /**
+ * Create a FloatArrayEvaluator that does not reuse the animated value. Care must be taken
+ * when using this option because on every evaluation a new <code>float[]</code> will be
+ * allocated.
+ *
+ * @see #FloatArrayEvaluator(float[])
+ */
+ public FloatArrayEvaluator() {
+ }
+
+ /**
+ * Create a FloatArrayEvaluator that reuses <code>reuseArray</code> for every evaluate() call.
+ * Caution must be taken to ensure that the value returned from
+ * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or
+ * used across threads. The value will be modified on each <code>evaluate()</code> call.
+ *
+ * @param reuseArray The array to modify and return from <code>evaluate</code>.
+ */
+ public FloatArrayEvaluator(float[] reuseArray) {
+ mArray = reuseArray;
+ }
+
+ /**
+ * Interpolates the value at each index by the fraction. If
+ * {@link #FloatArrayEvaluator(float[])} was used to construct this object,
+ * <code>reuseArray</code> will be returned, otherwise a new <code>float[]</code>
+ * will be returned.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value.
+ * @param endValue The end value.
+ * @return A <code>float[]</code> where each element is an interpolation between
+ * the same index in startValue and endValue.
+ */
+ @Override
+ public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
+ float[] array = mArray;
+ if (array == null) {
+ array = new float[startValue.length];
+ }
+
+ for (int i = 0; i < array.length; i++) {
+ float start = startValue[i];
+ float end = endValue[i];
+ array[i] = start + (fraction * (end - start));
+ }
+ return array;
+ }
+}
diff --git a/core/java/android/animation/IntArrayEvaluator.java b/core/java/android/animation/IntArrayEvaluator.java
new file mode 100644
index 0000000..d7f10f3
--- /dev/null
+++ b/core/java/android/animation/IntArrayEvaluator.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2013 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.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>int[]</code> values.
+ * Each index into the array is treated as a separate value to interpolate. For example,
+ * evaluating <code>{100, 200}</code> and <code>{300, 400}</code> will interpolate the value at
+ * the first index between 100 and 300 and the value at the second index value between 200 and 400.
+ */
+public class IntArrayEvaluator implements TypeEvaluator<int[]> {
+
+ private int[] mArray;
+
+ /**
+ * Create an IntArrayEvaluator that does not reuse the animated value. Care must be taken
+ * when using this option because on every evaluation a new <code>int[]</code> will be
+ * allocated.
+ *
+ * @see #IntArrayEvaluator(int[])
+ */
+ public IntArrayEvaluator() {
+ }
+
+ /**
+ * Create an IntArrayEvaluator that reuses <code>reuseArray</code> for every evaluate() call.
+ * Caution must be taken to ensure that the value returned from
+ * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or
+ * used across threads. The value will be modified on each <code>evaluate()</code> call.
+ *
+ * @param reuseArray The array to modify and return from <code>evaluate</code>.
+ */
+ public IntArrayEvaluator(int[] reuseArray) {
+ mArray = reuseArray;
+ }
+
+ /**
+ * Interpolates the value at each index by the fraction. If {@link #IntArrayEvaluator(int[])}
+ * was used to construct this object, <code>reuseArray</code> will be returned, otherwise
+ * a new <code>int[]</code> will be returned.
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start value.
+ * @param endValue The end value.
+ * @return An <code>int[]</code> where each element is an interpolation between
+ * the same index in startValue and endValue.
+ */
+ @Override
+ public int[] evaluate(float fraction, int[] startValue, int[] endValue) {
+ int[] array = mArray;
+ if (array == null) {
+ array = new int[startValue.length];
+ }
+ for (int i = 0; i < array.length; i++) {
+ int start = startValue[i];
+ int end = endValue[i];
+ array[i] = (int) (start + (fraction * (end - start)));
+ }
+ return array;
+ }
+}
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 9c88ccf..c0ce795 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -16,6 +16,8 @@
package android.animation;
+import android.graphics.Path;
+import android.graphics.PointF;
import android.util.Log;
import android.util.Property;
@@ -191,7 +193,7 @@ public final class ObjectAnimator extends ValueAnimator {
/**
* Constructs and returns an ObjectAnimator that animates between int values. A single
- * value implies that that value is the one being animated to. Two values imply a starting
+ * value implies that that value is the one being animated to. Two values imply starting
* and ending values. More than two values imply a starting value, values to animate through
* along the way, and an ending value (these values will be distributed evenly across
* the duration of the animation).
@@ -210,8 +212,33 @@ public final class ObjectAnimator extends ValueAnimator {
}
/**
+ * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+ * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+ * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are integers that are set to separate properties designated by
+ * <code>xPropertyName</code> and <code>yPropertyName</code>.
+ *
+ * @param target The object whose properties are to be animated. This object should
+ * have public methods on it called <code>setNameX()</code> and
+ * <code>setNameY</code>, where <code>nameX</code> and <code>nameY</code>
+ * are the value of <code>xPropertyName</code> and <code>yPropertyName</code>
+ * parameters, respectively.
+ * @param xPropertyName The name of the property for the x coordinate being animated.
+ * @param yPropertyName The name of the property for the y coordinate being animated.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static ObjectAnimator ofInt(Object target, String xPropertyName, String yPropertyName,
+ Path path) {
+ Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, true);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xPropertyName, keyframes[0]);
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yPropertyName, keyframes[1]);
+ return ofPropertyValuesHolder(target, x, y);
+ }
+
+ /**
* Constructs and returns an ObjectAnimator that animates between int values. A single
- * value implies that that value is the one being animated to. Two values imply a starting
+ * value implies that that value is the one being animated to. Two values imply starting
* and ending values. More than two values imply a starting value, values to animate through
* along the way, and an ending value (these values will be distributed evenly across
* the duration of the animation).
@@ -228,8 +255,135 @@ public final class ObjectAnimator extends ValueAnimator {
}
/**
+ * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+ * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+ * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are integers that are set to separate properties, <code>xProperty</code> and
+ * <code>yProperty</code>.
+ *
+ * @param target The object whose properties are to be animated.
+ * @param xProperty The property for the x coordinate being animated.
+ * @param yProperty The property for the y coordinate being animated.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> xProperty,
+ Property<T, Integer> yProperty, Path path) {
+ Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, true);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xProperty, keyframes[0]);
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yProperty, keyframes[1]);
+ return ofPropertyValuesHolder(target, x, y);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates over int values for a multiple
+ * parameters setter. Only public methods that take only int parameters are supported.
+ * Each <code>int[]</code> contains a complete set of parameters to the setter method.
+ * At least two <code>int[]</code> values must be provided, a start and end. More than two
+ * values imply a starting value, values to animate through along the way, and an ending
+ * value (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofMultiInt(Object target, String propertyName, int[][] values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt(propertyName, values);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates the target using a multi-int setter
+ * along the given <code>Path</code>. A <code>Path</code></> animation moves in two dimensions,
+ * animating coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are integer x and y coordinates used in the first and second parameter of the
+ * setter, respectively.
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static ObjectAnimator ofMultiInt(Object target, String propertyName, Path path) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt(propertyName, path);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates over values for a multiple int
+ * parameters setter. Only public methods that take only int parameters are supported.
+ * <p>At least two values must be provided, a start and end. More than two
+ * values imply a starting value, values to animate through along the way, and an ending
+ * value (these values will be distributed evenly across the duration of the animation).</p>
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param converter Converts T objects into int parameters for the multi-value setter.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static <T> ObjectAnimator ofMultiInt(Object target, String propertyName,
+ TypeConverter<T, int[]> converter, TypeEvaluator<T> evaluator, T... values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiInt(propertyName, converter,
+ evaluator, values);
+ return ObjectAnimator.ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between color values. A single
+ * value implies that that value is the one being animated to. Two values imply starting
+ * and ending values. More than two values imply a starting value, values to animate through
+ * along the way, and an ending value (these values will be distributed evenly across
+ * the duration of the animation).
+ *
+ * @param target The object whose property is to be animated. This object should
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofArgb(Object target, String propertyName, int... values) {
+ ObjectAnimator animator = ofInt(target, propertyName, values);
+ animator.setEvaluator(ArgbEvaluator.getInstance());
+ return animator;
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates between color values. A single
+ * value implies that that value is the one being animated to. Two values imply starting
+ * and ending values. More than two values imply a starting value, values to animate through
+ * along the way, and an ending value (these values will be distributed evenly across
+ * the duration of the animation).
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static <T> ObjectAnimator ofArgb(T target, Property<T, Integer> property,
+ int... values) {
+ ObjectAnimator animator = ofInt(target, property, values);
+ animator.setEvaluator(ArgbEvaluator.getInstance());
+ return animator;
+ }
+
+ /**
* Constructs and returns an ObjectAnimator that animates between float values. A single
- * value implies that that value is the one being animated to. Two values imply a starting
+ * value implies that that value is the one being animated to. Two values imply starting
* and ending values. More than two values imply a starting value, values to animate through
* along the way, and an ending value (these values will be distributed evenly across
* the duration of the animation).
@@ -248,8 +402,33 @@ public final class ObjectAnimator extends ValueAnimator {
}
/**
+ * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+ * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+ * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are floats that are set to separate properties designated by
+ * <code>xPropertyName</code> and <code>yPropertyName</code>.
+ *
+ * @param target The object whose properties are to be animated. This object should
+ * have public methods on it called <code>setNameX()</code> and
+ * <code>setNameY</code>, where <code>nameX</code> and <code>nameY</code>
+ * are the value of the <code>xPropertyName</code> and <code>yPropertyName</code>
+ * parameters, respectively.
+ * @param xPropertyName The name of the property for the x coordinate being animated.
+ * @param yPropertyName The name of the property for the y coordinate being animated.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static ObjectAnimator ofFloat(Object target, String xPropertyName, String yPropertyName,
+ Path path) {
+ Keyframe[][] keyframes = PropertyValuesHolder.createKeyframes(path, false);
+ PropertyValuesHolder x = PropertyValuesHolder.ofKeyframe(xPropertyName, keyframes[0]);
+ PropertyValuesHolder y = PropertyValuesHolder.ofKeyframe(yPropertyName, keyframes[1]);
+ return ofPropertyValuesHolder(target, x, y);
+ }
+
+ /**
* Constructs and returns an ObjectAnimator that animates between float values. A single
- * value implies that that value is the one being animated to. Two values imply a starting
+ * value implies that that value is the one being animated to. Two values imply starting
* and ending values. More than two values imply a starting value, values to animate through
* along the way, and an ending value (these values will be distributed evenly across
* the duration of the animation).
@@ -267,8 +446,94 @@ public final class ObjectAnimator extends ValueAnimator {
}
/**
+ * Constructs and returns an ObjectAnimator that animates coordinates along a <code>Path</code>
+ * using two properties. A <code>Path</code></> animation moves in two dimensions, animating
+ * coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are floats that are set to separate properties, <code>xProperty</code> and
+ * <code>yProperty</code>.
+ *
+ * @param target The object whose properties are to be animated.
+ * @param xProperty The property for the x coordinate being animated.
+ * @param yProperty The property for the y coordinate being animated.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> xProperty,
+ Property<T, Float> yProperty, Path path) {
+ return ofFloat(target, xProperty.getName(), yProperty.getName(), path);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates over float values for a multiple
+ * parameters setter. Only public methods that take only float parameters are supported.
+ * Each <code>float[]</code> contains a complete set of parameters to the setter method.
+ * At least two <code>float[]</code> values must be provided, a start and end. More than two
+ * values imply a starting value, values to animate through along the way, and an ending
+ * value (these values will be distributed evenly across the duration of the animation).
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static ObjectAnimator ofMultiFloat(Object target, String propertyName,
+ float[][] values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat(propertyName, values);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates the target using a multi-float setter
+ * along the given <code>Path</code>. A <code>Path</code></> animation moves in two dimensions,
+ * animating coordinates <code>(x, y)</code> together to follow the line. In this variation, the
+ * coordinates are float x and y coordinates used in the first and second parameter of the
+ * setter, respectively.
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static ObjectAnimator ofMultiFloat(Object target, String propertyName, Path path) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat(propertyName, path);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates over values for a multiple float
+ * parameters setter. Only public methods that take only float parameters are supported.
+ * <p>At least two values must be provided, a start and end. More than two
+ * values imply a starting value, values to animate through along the way, and an ending
+ * value (these values will be distributed evenly across the duration of the animation).</p>
+ *
+ * @param target The object whose property is to be animated. This object may
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter. <code>propertyName</code> may also
+ * be the case-sensitive complete name of the public setter method.
+ * @param propertyName The name of the property being animated or the name of the setter method.
+ * @param converter Converts T objects into float parameters for the multi-value setter.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static <T> ObjectAnimator ofMultiFloat(Object target, String propertyName,
+ TypeConverter<T, float[]> converter, TypeEvaluator<T> evaluator, T... values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofMultiFloat(propertyName, converter,
+ evaluator, values);
+ return ObjectAnimator.ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
* Constructs and returns an ObjectAnimator that animates between Object values. A single
- * value implies that that value is the one being animated to. Two values imply a starting
+ * value implies that that value is the one being animated to. Two values imply starting
* and ending values. More than two values imply a starting value, values to animate through
* along the way, and an ending value (these values will be distributed evenly across
* the duration of the animation).
@@ -292,8 +557,32 @@ public final class ObjectAnimator extends ValueAnimator {
}
/**
+ * Constructs and returns an ObjectAnimator that animates a property along a <code>Path</code>.
+ * A <code>Path</code></> animation moves in two dimensions, animating coordinates
+ * <code>(x, y)</code> together to follow the line. This variant animates the coordinates
+ * in a <code>PointF</code> to follow the <code>Path</code>. If the <code>Property</code>
+ * associated with <code>propertyName</code> uses a type other than <code>PointF</code>,
+ * <code>converter</code> can be used to change from <code>PointF</code> to the type
+ * associated with the <code>Property</code>.
+ *
+ * @param target The object whose property is to be animated. This object should
+ * have a public method on it called <code>setName()</code>, where <code>name</code> is
+ * the value of the <code>propertyName</code> parameter.
+ * @param propertyName The name of the property being animated.
+ * @param converter Converts a PointF to the type associated with the setter. May be
+ * null if conversion is unnecessary.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static ObjectAnimator ofObject(Object target, String propertyName,
+ TypeConverter<PointF, ?> converter, Path path) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofObject(propertyName, converter, path);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
* Constructs and returns an ObjectAnimator that animates between Object values. A single
- * value implies that that value is the one being animated to. Two values imply a starting
+ * value implies that that value is the one being animated to. Two values imply starting
* and ending values. More than two values imply a starting value, values to animate through
* along the way, and an ending value (these values will be distributed evenly across
* the duration of the animation).
@@ -315,6 +604,53 @@ public final class ObjectAnimator extends ValueAnimator {
}
/**
+ * Constructs and returns an ObjectAnimator that animates between Object values. A single
+ * value implies that that value is the one being animated to. Two values imply starting
+ * and ending values. More than two values imply a starting value, values to animate through
+ * along the way, and an ending value (these values will be distributed evenly across
+ * the duration of the animation). This variant supplies a <code>TypeConverter</code> to
+ * convert from the animated values to the type of the property. If only one value is
+ * supplied, the <code>TypeConverter</code> must implement
+ * {@link TypeConverter#convertBack(Object)} to retrieve the current value.
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated.
+ * @param converter Converts the animated object to the Property type.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values A set of values that the animation will animate between over time.
+ * @return An ObjectAnimator object that is set up to animate between the given values.
+ */
+ public static <T, V, P> ObjectAnimator ofObject(T target, Property<T, P> property,
+ TypeConverter<V, P> converter, TypeEvaluator<V> evaluator, V... values) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofObject(property, converter, evaluator,
+ values);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
+ * Constructs and returns an ObjectAnimator that animates a property along a <code>Path</code>.
+ * A <code>Path</code></> animation moves in two dimensions, animating coordinates
+ * <code>(x, y)</code> together to follow the line. This variant animates the coordinates
+ * in a <code>PointF</code> to follow the <code>Path</code>. If <code>property</code>
+ * uses a type other than <code>PointF</code>, <code>converter</code> can be used to change
+ * from <code>PointF</code> to the type associated with the <code>Property</code>.
+ *
+ * @param target The object whose property is to be animated.
+ * @param property The property being animated. Should not be null.
+ * @param converter Converts a PointF to the type associated with the setter. May be
+ * null if conversion is unnecessary.
+ * @param path The <code>Path</code> to animate values along.
+ * @return An ObjectAnimator object that is set up to animate along <code>path</code>.
+ */
+ public static <T, V> ObjectAnimator ofObject(T target, Property<T, V> property,
+ TypeConverter<PointF, V> converter, Path path) {
+ PropertyValuesHolder pvh = PropertyValuesHolder.ofObject(property, converter, path);
+ return ofPropertyValuesHolder(target, pvh);
+ }
+
+ /**
* Constructs and returns an ObjectAnimator that animates between the sets of values specified
* in <code>PropertyValueHolder</code> objects. This variant should be used when animating
* several properties at once with the same ObjectAnimator, since PropertyValuesHolder allows
diff --git a/core/java/android/animation/PointFEvaluator.java b/core/java/android/animation/PointFEvaluator.java
new file mode 100644
index 0000000..91d501f
--- /dev/null
+++ b/core/java/android/animation/PointFEvaluator.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2013 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.animation;
+
+import android.graphics.PointF;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>PointF</code> values.
+ */
+public class PointFEvaluator implements TypeEvaluator<PointF> {
+
+ /**
+ * When null, a new PointF is returned on every evaluate call. When non-null,
+ * mPoint will be modified and returned on every evaluate.
+ */
+ private PointF mPoint;
+
+ /**
+ * Construct a PointFEvaluator that returns a new PointF on every evaluate call.
+ * To avoid creating an object for each evaluate call,
+ * {@link PointFEvaluator#PointFEvaluator(android.graphics.PointF)} should be used
+ * whenever possible.
+ */
+ public PointFEvaluator() {
+ }
+
+ /**
+ * Constructs a PointFEvaluator that modifies and returns <code>reuse</code>
+ * in {@link #evaluate(float, android.graphics.PointF, android.graphics.PointF)} calls.
+ * The value returned from
+ * {@link #evaluate(float, android.graphics.PointF, android.graphics.PointF)} should
+ * not be cached because it will change over time as the object is reused on each
+ * call.
+ *
+ * @param reuse A PointF to be modified and returned by evaluate.
+ */
+ public PointFEvaluator(PointF reuse) {
+ mPoint = reuse;
+ }
+
+ /**
+ * This function returns the result of linearly interpolating the start and
+ * end PointF values, with <code>fraction</code> representing the proportion
+ * between the start and end values. The calculation is a simple parametric
+ * calculation on each of the separate components in the PointF objects
+ * (x, y).
+ *
+ * <p>If {@link #PointFEvaluator(android.graphics.PointF)} was used to construct
+ * this PointFEvaluator, the object returned will be the <code>reuse</code>
+ * passed into the constructor.</p>
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start PointF
+ * @param endValue The end PointF
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ @Override
+ public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
+ float x = startValue.x + (fraction * (endValue.x - startValue.x));
+ float y = startValue.y + (fraction * (endValue.y - startValue.y));
+
+ if (mPoint != null) {
+ mPoint.set(x, y);
+ return mPoint;
+ } else {
+ return new PointF(x, y);
+ }
+ }
+}
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index 43014ad..1b028e0 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -16,6 +16,9 @@
package android.animation;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.util.FloatMath;
import android.util.FloatProperty;
import android.util.IntProperty;
import android.util.Log;
@@ -124,6 +127,11 @@ public class PropertyValuesHolder implements Cloneable {
private Object mAnimatedValue;
/**
+ * Converts from the source Object type to the setter Object type.
+ */
+ private TypeConverter mConverter;
+
+ /**
* Internal utility constructor, used by the factory methods to set the property name.
* @param propertyName The name of the property for this holder.
*/
@@ -166,6 +174,104 @@ public class PropertyValuesHolder implements Cloneable {
/**
* Constructs and returns a PropertyValuesHolder with a given property name and
+ * set of <code>int[]</code> values. At least two <code>int[]</code> values must be supplied,
+ * a start and end value. If more values are supplied, the values will be animated from the
+ * start, through all intermediate values to the end value. When used with ObjectAnimator,
+ * the elements of the array represent the parameters of the setter function.
+ *
+ * @param propertyName The name of the property being animated. Can also be the
+ * case-sensitive name of the entire setter method. Should not be null.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see IntArrayEvaluator#IntArrayEvaluator(int[])
+ * @see ObjectAnimator#ofMultiInt(Object, String, TypeConverter, TypeEvaluator, Object[])
+ */
+ public static PropertyValuesHolder ofMultiInt(String propertyName, int[][] values) {
+ if (values.length < 2) {
+ throw new IllegalArgumentException("At least 2 values must be supplied");
+ }
+ int numParameters = 0;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] == null) {
+ throw new IllegalArgumentException("values must not be null");
+ }
+ int length = values[i].length;
+ if (i == 0) {
+ numParameters = length;
+ } else if (length != numParameters) {
+ throw new IllegalArgumentException("Values must all have the same length");
+ }
+ }
+ IntArrayEvaluator evaluator = new IntArrayEvaluator(new int[numParameters]);
+ return new MultiIntValuesHolder(propertyName, null, evaluator, (Object[]) values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name to use
+ * as a multi-int setter. The values are animated along the path, with the first
+ * parameter of the setter set to the x coordinate and the second set to the y coordinate.
+ *
+ * @param propertyName The name of the property being animated. Can also be the
+ * case-sensitive name of the entire setter method. Should not be null.
+ * The setter must take exactly two <code>int</code> parameters.
+ * @param path The Path along which the values should be animated.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
+ */
+ public static PropertyValuesHolder ofMultiInt(String propertyName, Path path) {
+ Keyframe[] keyframes = createKeyframes(path);
+ KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(keyframes);
+ TypeEvaluator<PointF> evaluator = new PointFEvaluator(new PointF());
+ PointFToIntArray converter = new PointFToIntArray();
+ return new MultiIntValuesHolder(propertyName, converter, evaluator, keyframeSet);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of Object values for use with ObjectAnimator multi-value setters. The Object
+ * values are converted to <code>int[]</code> using the converter.
+ *
+ * @param propertyName The property being animated or complete name of the setter.
+ * Should not be null.
+ * @param converter Used to convert the animated value to setter parameters.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see ObjectAnimator#ofMultiInt(Object, String, TypeConverter, TypeEvaluator, Object[])
+ * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
+ */
+ public static <V> PropertyValuesHolder ofMultiInt(String propertyName,
+ TypeConverter<V, int[]> converter, TypeEvaluator<V> evaluator, V... values) {
+ return new MultiIntValuesHolder(propertyName, converter, evaluator, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder object with the specified property name or
+ * setter name for use in a multi-int setter function using ObjectAnimator. The values can be
+ * of any type, but the type should be consistent so that the supplied
+ * {@link android.animation.TypeEvaluator} can be used to to evaluate the animated value. The
+ * <code>converter</code> converts the values to parameters in the setter function.
+ *
+ * <p>At least two values must be supplied, a start and an end value.</p>
+ *
+ * @param propertyName The name of the property to associate with the set of values. This
+ * may also be the complete name of a setter function.
+ * @param converter Converts <code>values</code> into int parameters for the setter.
+ * Can be null if the Keyframes have int[] values.
+ * @param evaluator Used to interpolate between values.
+ * @param values The values at specific fractional times to evaluate between
+ * @return A PropertyValuesHolder for a multi-int parameter setter.
+ */
+ public static <T> PropertyValuesHolder ofMultiInt(String propertyName,
+ TypeConverter<T, int[]> converter, TypeEvaluator<T> evaluator, Keyframe... values) {
+ KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
+ return new MultiIntValuesHolder(propertyName, converter, evaluator, keyframeSet);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
* set of float values.
* @param propertyName The name of the property being animated.
* @param values The values that the named property will animate between.
@@ -188,6 +294,103 @@ public class PropertyValuesHolder implements Cloneable {
/**
* Constructs and returns a PropertyValuesHolder with a given property name and
+ * set of <code>float[]</code> values. At least two <code>float[]</code> values must be supplied,
+ * a start and end value. If more values are supplied, the values will be animated from the
+ * start, through all intermediate values to the end value. When used with ObjectAnimator,
+ * the elements of the array represent the parameters of the setter function.
+ *
+ * @param propertyName The name of the property being animated. Can also be the
+ * case-sensitive name of the entire setter method. Should not be null.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see FloatArrayEvaluator#FloatArrayEvaluator(float[])
+ * @see ObjectAnimator#ofMultiFloat(Object, String, TypeConverter, TypeEvaluator, Object[])
+ */
+ public static PropertyValuesHolder ofMultiFloat(String propertyName, float[][] values) {
+ if (values.length < 2) {
+ throw new IllegalArgumentException("At least 2 values must be supplied");
+ }
+ int numParameters = 0;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] == null) {
+ throw new IllegalArgumentException("values must not be null");
+ }
+ int length = values[i].length;
+ if (i == 0) {
+ numParameters = length;
+ } else if (length != numParameters) {
+ throw new IllegalArgumentException("Values must all have the same length");
+ }
+ }
+ FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[numParameters]);
+ return new MultiFloatValuesHolder(propertyName, null, evaluator, (Object[]) values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name to use
+ * as a multi-float setter. The values are animated along the path, with the first
+ * parameter of the setter set to the x coordinate and the second set to the y coordinate.
+ *
+ * @param propertyName The name of the property being animated. Can also be the
+ * case-sensitive name of the entire setter method. Should not be null.
+ * The setter must take exactly two <code>float</code> parameters.
+ * @param path The Path along which the values should be animated.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see ObjectAnimator#ofPropertyValuesHolder(Object, PropertyValuesHolder...)
+ */
+ public static PropertyValuesHolder ofMultiFloat(String propertyName, Path path) {
+ Keyframe[] keyframes = createKeyframes(path);
+ KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(keyframes);
+ TypeEvaluator<PointF> evaluator = new PointFEvaluator(new PointF());
+ PointFToFloatArray converter = new PointFToFloatArray();
+ return new MultiFloatValuesHolder(propertyName, converter, evaluator, keyframeSet);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of Object values for use with ObjectAnimator multi-value setters. The Object
+ * values are converted to <code>float[]</code> using the converter.
+ *
+ * @param propertyName The property being animated or complete name of the setter.
+ * Should not be null.
+ * @param converter Used to convert the animated value to setter parameters.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see ObjectAnimator#ofMultiFloat(Object, String, TypeConverter, TypeEvaluator, Object[])
+ */
+ public static <V> PropertyValuesHolder ofMultiFloat(String propertyName,
+ TypeConverter<V, float[]> converter, TypeEvaluator<V> evaluator, V... values) {
+ return new MultiFloatValuesHolder(propertyName, converter, evaluator, values);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder object with the specified property name or
+ * setter name for use in a multi-float setter function using ObjectAnimator. The values can be
+ * of any type, but the type should be consistent so that the supplied
+ * {@link android.animation.TypeEvaluator} can be used to to evaluate the animated value. The
+ * <code>converter</code> converts the values to parameters in the setter function.
+ *
+ * <p>At least two values must be supplied, a start and an end value.</p>
+ *
+ * @param propertyName The name of the property to associate with the set of values. This
+ * may also be the complete name of a setter function.
+ * @param converter Converts <code>values</code> into float parameters for the setter.
+ * Can be null if the Keyframes have float[] values.
+ * @param evaluator Used to interpolate between values.
+ * @param values The values at specific fractional times to evaluate between
+ * @return A PropertyValuesHolder for a multi-float parameter setter.
+ */
+ public static <T> PropertyValuesHolder ofMultiFloat(String propertyName,
+ TypeConverter<T, float[]> converter, TypeEvaluator<T> evaluator, Keyframe... values) {
+ KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
+ return new MultiFloatValuesHolder(propertyName, converter, evaluator, keyframeSet);
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
* set of Object values. This variant also takes a TypeEvaluator because the system
* cannot automatically interpolate between objects of unknown type.
*
@@ -207,6 +410,27 @@ public class PropertyValuesHolder implements Cloneable {
}
/**
+ * Constructs and returns a PropertyValuesHolder with a given property name and
+ * a Path along which the values should be animated. This variant supports a
+ * <code>TypeConverter</code> to convert from <code>PointF</code> to the target
+ * type.
+ *
+ * @param propertyName The name of the property being animated.
+ * @param converter Converts a PointF to the type associated with the setter. May be
+ * null if conversion is unnecessary.
+ * @param path The Path along which the values should be animated.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static PropertyValuesHolder ofObject(String propertyName,
+ TypeConverter<PointF, ?> converter, Path path) {
+ Keyframe[] keyframes = createKeyframes(path);
+ PropertyValuesHolder pvh = ofKeyframe(propertyName, keyframes);
+ pvh.setEvaluator(new PointFEvaluator(new PointF()));
+ pvh.setConverter(converter);
+ return pvh;
+ }
+
+ /**
* Constructs and returns a PropertyValuesHolder with a given property and
* set of Object values. This variant also takes a TypeEvaluator because the system
* cannot automatically interpolate between objects of unknown type.
@@ -227,6 +451,55 @@ public class PropertyValuesHolder implements Cloneable {
}
/**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * set of Object values. This variant also takes a TypeEvaluator because the system
+ * cannot automatically interpolate between objects of unknown type. This variant also
+ * takes a <code>TypeConverter</code> to convert from animated values to the type
+ * of the property. If only one value is supplied, the <code>TypeConverter</code>
+ * must implement {@link TypeConverter#convertBack(Object)} to retrieve the current
+ * value.
+ *
+ * @param property The property being animated. Should not be null.
+ * @param converter Converts the animated object to the Property type.
+ * @param evaluator A TypeEvaluator that will be called on each animation frame to
+ * provide the necessary interpolation between the Object values to derive the animated
+ * value.
+ * @param values The values that the property will animate between.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ * @see #setConverter(TypeConverter)
+ * @see TypeConverter
+ */
+ public static <T, V> PropertyValuesHolder ofObject(Property<?, V> property,
+ TypeConverter<T, V> converter, TypeEvaluator<T> evaluator, T... values) {
+ PropertyValuesHolder pvh = new PropertyValuesHolder(property);
+ pvh.setConverter(converter);
+ pvh.setObjectValues(values);
+ pvh.setEvaluator(evaluator);
+ return pvh;
+ }
+
+ /**
+ * Constructs and returns a PropertyValuesHolder with a given property and
+ * a Path along which the values should be animated. This variant supports a
+ * <code>TypeConverter</code> to convert from <code>PointF</code> to the target
+ * type.
+ *
+ * @param property The property being animated. Should not be null.
+ * @param converter Converts a PointF to the type associated with the setter. May be
+ * null if conversion is unnecessary.
+ * @param path The Path along which the values should be animated.
+ * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+ */
+ public static <V> PropertyValuesHolder ofObject(Property<?, V> property,
+ TypeConverter<PointF, V> converter, Path path) {
+ Keyframe[] keyframes = createKeyframes(path);
+ PropertyValuesHolder pvh = ofKeyframe(property, keyframes);
+ pvh.setEvaluator(new PointFEvaluator(new PointF()));
+ pvh.setConverter(converter);
+ return pvh;
+ }
+
+ /**
* Constructs and returns a PropertyValuesHolder object with the specified property name and set
* of values. These values can be of any type, but the type should be consistent so that
* an appropriate {@link android.animation.TypeEvaluator} can be found that matches
@@ -361,6 +634,14 @@ public class PropertyValuesHolder implements Cloneable {
}
/**
+ * Sets the converter to convert from the values type to the setter's parameter type.
+ * @param converter The converter to use to convert values.
+ */
+ public void setConverter(TypeConverter converter) {
+ mConverter = converter;
+ }
+
+ /**
* Determine the setter or getter function using the JavaBeans convention of setFoo or
* getFoo for a property named 'foo'. This function figures out what the name of the
* function should be and uses reflection to find the Method with that name on the
@@ -389,22 +670,24 @@ public class PropertyValuesHolder implements Cloneable {
} else {
args = new Class[1];
Class typeVariants[];
- if (mValueType.equals(Float.class)) {
+ if (valueType.equals(Float.class)) {
typeVariants = FLOAT_VARIANTS;
- } else if (mValueType.equals(Integer.class)) {
+ } else if (valueType.equals(Integer.class)) {
typeVariants = INTEGER_VARIANTS;
- } else if (mValueType.equals(Double.class)) {
+ } else if (valueType.equals(Double.class)) {
typeVariants = DOUBLE_VARIANTS;
} else {
typeVariants = new Class[1];
- typeVariants[0] = mValueType;
+ typeVariants[0] = valueType;
}
for (Class typeVariant : typeVariants) {
args[0] = typeVariant;
try {
returnVal = targetClass.getMethod(methodName, args);
- // change the value type to suit
- mValueType = typeVariant;
+ if (mConverter == null) {
+ // change the value type to suit
+ mValueType = typeVariant;
+ }
return returnVal;
} catch (NoSuchMethodException e) {
// Swallow the error and keep trying other variants
@@ -415,7 +698,7 @@ public class PropertyValuesHolder implements Cloneable {
if (returnVal == null) {
Log.w("PropertyValuesHolder", "Method " +
- getMethodName(prefix, mPropertyName) + "() with type " + mValueType +
+ getMethodName(prefix, mPropertyName) + "() with type " + valueType +
" not found on target class " + targetClass);
}
@@ -465,7 +748,8 @@ public class PropertyValuesHolder implements Cloneable {
* @param targetClass The Class on which the requested method should exist.
*/
void setupSetter(Class targetClass) {
- mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType);
+ Class<?> propertyType = mConverter == null ? mValueType : mConverter.getTargetType();
+ mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType);
}
/**
@@ -489,10 +773,13 @@ public class PropertyValuesHolder implements Cloneable {
if (mProperty != null) {
// check to make sure that mProperty is on the class of target
try {
- Object testValue = mProperty.get(target);
+ Object testValue = null;
for (Keyframe kf : mKeyframeSet.mKeyframes) {
if (!kf.hasValue()) {
- kf.setValue(mProperty.get(target));
+ if (testValue == null) {
+ testValue = convertBack(mProperty.get(target));
+ }
+ kf.setValue(testValue);
}
}
return;
@@ -516,7 +803,8 @@ public class PropertyValuesHolder implements Cloneable {
}
}
try {
- kf.setValue(mGetter.invoke(target));
+ Object value = convertBack(mGetter.invoke(target));
+ kf.setValue(value);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
@@ -526,6 +814,18 @@ public class PropertyValuesHolder implements Cloneable {
}
}
+ private Object convertBack(Object value) {
+ if (mConverter != null) {
+ value = mConverter.convertBack(value);
+ if (value == null) {
+ throw new IllegalArgumentException("Converter "
+ + mConverter.getClass().getName()
+ + " must implement convertBack and not return null.");
+ }
+ }
+ return value;
+ }
+
/**
* Utility function to set the value stored in a particular Keyframe. The value used is
* whatever the value is for the property name specified in the keyframe on the target object.
@@ -535,7 +835,8 @@ public class PropertyValuesHolder implements Cloneable {
*/
private void setupValue(Object target, Keyframe kf) {
if (mProperty != null) {
- kf.setValue(mProperty.get(target));
+ Object value = convertBack(mProperty.get(target));
+ kf.setValue(value);
}
try {
if (mGetter == null) {
@@ -546,7 +847,8 @@ public class PropertyValuesHolder implements Cloneable {
return;
}
}
- kf.setValue(mGetter.invoke(target));
+ Object value = convertBack(mGetter.invoke(target));
+ kf.setValue(value);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
@@ -657,7 +959,8 @@ public class PropertyValuesHolder implements Cloneable {
* @param fraction The elapsed, interpolated fraction of the animation.
*/
void calculateValue(float fraction) {
- mAnimatedValue = mKeyframeSet.getValue(fraction);
+ Object value = mKeyframeSet.getValue(fraction);
+ mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}
/**
@@ -1015,8 +1318,334 @@ public class PropertyValuesHolder implements Cloneable {
}
+ static class MultiFloatValuesHolder extends PropertyValuesHolder {
+ private int mJniSetter;
+ private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap =
+ new HashMap<Class, HashMap<String, Integer>>();
+
+ public MultiFloatValuesHolder(String propertyName, TypeConverter converter,
+ TypeEvaluator evaluator, Object... values) {
+ super(propertyName);
+ setConverter(converter);
+ setObjectValues(values);
+ setEvaluator(evaluator);
+ }
+
+ public MultiFloatValuesHolder(String propertyName, TypeConverter converter,
+ TypeEvaluator evaluator, KeyframeSet keyframeSet) {
+ super(propertyName);
+ setConverter(converter);
+ mKeyframeSet = keyframeSet;
+ setEvaluator(evaluator);
+ }
+
+ /**
+ * Internal function to set the value on the target object, using the setter set up
+ * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+ * to handle turning the value calculated by ValueAnimator into a value set on the object
+ * according to the name of the property.
+ *
+ * @param target The target object on which the value is set
+ */
+ @Override
+ void setAnimatedValue(Object target) {
+ float[] values = (float[]) getAnimatedValue();
+ int numParameters = values.length;
+ if (mJniSetter != 0) {
+ switch (numParameters) {
+ case 1:
+ nCallFloatMethod(target, mJniSetter, values[0]);
+ break;
+ case 2:
+ nCallTwoFloatMethod(target, mJniSetter, values[0], values[1]);
+ break;
+ case 4:
+ nCallFourFloatMethod(target, mJniSetter, values[0], values[1],
+ values[2], values[3]);
+ break;
+ default: {
+ nCallMultipleFloatMethod(target, mJniSetter, values);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Internal function (called from ObjectAnimator) to set up the setter and getter
+ * prior to running the animation. No getter can be used for multiple parameters.
+ *
+ * @param target The object on which the setter exists.
+ */
+ @Override
+ void setupSetterAndGetter(Object target) {
+ setupSetter(target.getClass());
+ }
+
+ @Override
+ void setupSetter(Class targetClass) {
+ if (mJniSetter != 0) {
+ return;
+ }
+ try {
+ mPropertyMapLock.writeLock().lock();
+ HashMap<String, Integer> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ if (propertyMap != null) {
+ Integer jniSetterInteger = propertyMap.get(mPropertyName);
+ if (jniSetterInteger != null) {
+ mJniSetter = jniSetterInteger;
+ }
+ }
+ if (mJniSetter == 0) {
+ String methodName = getMethodName("set", mPropertyName);
+ calculateValue(0f);
+ float[] values = (float[]) getAnimatedValue();
+ int numParams = values.length;
+ try {
+ mJniSetter = nGetMultipleFloatMethod(targetClass, methodName, numParams);
+ } catch (NoSuchMethodError e) {
+ // try without the 'set' prefix
+ mJniSetter = nGetMultipleFloatMethod(targetClass, mPropertyName, numParams);
+ }
+ if (mJniSetter != 0) {
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Integer>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mJniSetter);
+ }
+ }
+ } finally {
+ mPropertyMapLock.writeLock().unlock();
+ }
+ }
+ }
+
+ static class MultiIntValuesHolder extends PropertyValuesHolder {
+ private int mJniSetter;
+ private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap =
+ new HashMap<Class, HashMap<String, Integer>>();
+
+ public MultiIntValuesHolder(String propertyName, TypeConverter converter,
+ TypeEvaluator evaluator, Object... values) {
+ super(propertyName);
+ setConverter(converter);
+ setObjectValues(values);
+ setEvaluator(evaluator);
+ }
+
+ public MultiIntValuesHolder(String propertyName, TypeConverter converter,
+ TypeEvaluator evaluator, KeyframeSet keyframeSet) {
+ super(propertyName);
+ setConverter(converter);
+ mKeyframeSet = keyframeSet;
+ setEvaluator(evaluator);
+ }
+
+ /**
+ * Internal function to set the value on the target object, using the setter set up
+ * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+ * to handle turning the value calculated by ValueAnimator into a value set on the object
+ * according to the name of the property.
+ *
+ * @param target The target object on which the value is set
+ */
+ @Override
+ void setAnimatedValue(Object target) {
+ int[] values = (int[]) getAnimatedValue();
+ int numParameters = values.length;
+ if (mJniSetter != 0) {
+ switch (numParameters) {
+ case 1:
+ nCallIntMethod(target, mJniSetter, values[0]);
+ break;
+ case 2:
+ nCallTwoIntMethod(target, mJniSetter, values[0], values[1]);
+ break;
+ case 4:
+ nCallFourIntMethod(target, mJniSetter, values[0], values[1],
+ values[2], values[3]);
+ break;
+ default: {
+ nCallMultipleIntMethod(target, mJniSetter, values);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Internal function (called from ObjectAnimator) to set up the setter and getter
+ * prior to running the animation. No getter can be used for multiple parameters.
+ *
+ * @param target The object on which the setter exists.
+ */
+ @Override
+ void setupSetterAndGetter(Object target) {
+ setupSetter(target.getClass());
+ }
+
+ @Override
+ void setupSetter(Class targetClass) {
+ if (mJniSetter != 0) {
+ return;
+ }
+ try {
+ mPropertyMapLock.writeLock().lock();
+ HashMap<String, Integer> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ if (propertyMap != null) {
+ Integer jniSetterInteger = propertyMap.get(mPropertyName);
+ if (jniSetterInteger != null) {
+ mJniSetter = jniSetterInteger;
+ }
+ }
+ if (mJniSetter == 0) {
+ String methodName = getMethodName("set", mPropertyName);
+ calculateValue(0f);
+ int[] values = (int[]) getAnimatedValue();
+ int numParams = values.length;
+ try {
+ mJniSetter = nGetMultipleIntMethod(targetClass, methodName, numParams);
+ } catch (NoSuchMethodError e) {
+ // try without the 'set' prefix
+ mJniSetter = nGetMultipleIntMethod(targetClass, mPropertyName, numParams);
+ }
+ if (mJniSetter != 0) {
+ if (propertyMap == null) {
+ propertyMap = new HashMap<String, Integer>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mJniSetter);
+ }
+ }
+ } finally {
+ mPropertyMapLock.writeLock().unlock();
+ }
+ }
+ }
+
+ /* Path interpolation relies on approximating the Path as a series of line segments.
+ The line segments are recursively divided until there is less than 1/2 pixel error
+ between the lines and the curve. Each point of the line segment is converted
+ to a Keyframe and a linear interpolation between Keyframes creates a good approximation
+ of the curve.
+
+ The fraction for each Keyframe is the length along the Path to the point, divided by
+ the total Path length. Two points may have the same fraction in the case of a move
+ command causing a disjoint Path.
+
+ The value for each Keyframe is either the point as a PointF or one of the x or y
+ coordinates as an int or float. In the latter case, two Keyframes are generated for
+ each point that have the same fraction. */
+
+ /**
+ * Returns separate Keyframes arrays for the x and y coordinates along a Path. If
+ * isInt is true, the Keyframes will be IntKeyframes, otherwise they will be FloatKeyframes.
+ * The element at index 0 are the x coordinate Keyframes and element at index 1 are the
+ * y coordinate Keyframes. The returned values can be linearly interpolated and get less
+ * than 1/2 pixel error.
+ */
+ static Keyframe[][] createKeyframes(Path path, boolean isInt) {
+ if (path == null || path.isEmpty()) {
+ throw new IllegalArgumentException("The path must not be null or empty");
+ }
+ float[] pointComponents = path.approximate(0.5f);
+
+ int numPoints = pointComponents.length / 3;
+
+ Keyframe[][] keyframes = new Keyframe[2][];
+ keyframes[0] = new Keyframe[numPoints];
+ keyframes[1] = new Keyframe[numPoints];
+ int componentIndex = 0;
+ for (int i = 0; i < numPoints; i++) {
+ float fraction = pointComponents[componentIndex++];
+ float x = pointComponents[componentIndex++];
+ float y = pointComponents[componentIndex++];
+ if (isInt) {
+ keyframes[0][i] = Keyframe.ofInt(fraction, Math.round(x));
+ keyframes[1][i] = Keyframe.ofInt(fraction, Math.round(y));
+ } else {
+ keyframes[0][i] = Keyframe.ofFloat(fraction, x);
+ keyframes[1][i] = Keyframe.ofFloat(fraction, y);
+ }
+ }
+ return keyframes;
+ }
+
+ /**
+ * Returns PointF Keyframes for a Path. The resulting points can be linearly interpolated
+ * with less than 1/2 pixel in error.
+ */
+ private static Keyframe[] createKeyframes(Path path) {
+ if (path == null || path.isEmpty()) {
+ throw new IllegalArgumentException("The path must not be null or empty");
+ }
+ float[] pointComponents = path.approximate(0.5f);
+
+ int numPoints = pointComponents.length / 3;
+
+ Keyframe[] keyframes = new Keyframe[numPoints];
+ int componentIndex = 0;
+ for (int i = 0; i < numPoints; i++) {
+ float fraction = pointComponents[componentIndex++];
+ float x = pointComponents[componentIndex++];
+ float y = pointComponents[componentIndex++];
+ keyframes[i] = Keyframe.ofObject(fraction, new PointF(x, y));
+ }
+ return keyframes;
+ }
+
+ /**
+ * Convert from PointF to float[] for multi-float setters along a Path.
+ */
+ private static class PointFToFloatArray extends TypeConverter<PointF, float[]> {
+ private float[] mCoordinates = new float[2];
+
+ public PointFToFloatArray() {
+ super(PointF.class, float[].class);
+ }
+
+ @Override
+ public float[] convert(PointF value) {
+ mCoordinates[0] = value.x;
+ mCoordinates[1] = value.y;
+ return mCoordinates;
+ }
+ };
+
+ /**
+ * Convert from PointF to int[] for multi-int setters along a Path.
+ */
+ private static class PointFToIntArray extends TypeConverter<PointF, int[]> {
+ private int[] mCoordinates = new int[2];
+
+ public PointFToIntArray() {
+ super(PointF.class, int[].class);
+ }
+
+ @Override
+ public int[] convert(PointF value) {
+ mCoordinates[0] = Math.round(value.x);
+ mCoordinates[1] = Math.round(value.y);
+ return mCoordinates;
+ }
+ };
+
native static private int nGetIntMethod(Class targetClass, String methodName);
native static private int nGetFloatMethod(Class targetClass, String methodName);
+ native static private int nGetMultipleIntMethod(Class targetClass, String methodName,
+ int numParams);
+ native static private int nGetMultipleFloatMethod(Class targetClass, String methodName,
+ int numParams);
native static private void nCallIntMethod(Object target, int methodID, int arg);
native static private void nCallFloatMethod(Object target, int methodID, float arg);
-} \ No newline at end of file
+ native static private void nCallTwoIntMethod(Object target, int methodID, int arg1, int arg2);
+ native static private void nCallFourIntMethod(Object target, int methodID, int arg1, int arg2,
+ int arg3, int arg4);
+ native static private void nCallMultipleIntMethod(Object target, int methodID, int[] args);
+ native static private void nCallTwoFloatMethod(Object target, int methodID, float arg1,
+ float arg2);
+ native static private void nCallFourFloatMethod(Object target, int methodID, float arg1,
+ float arg2, float arg3, float arg4);
+ native static private void nCallMultipleFloatMethod(Object target, int methodID, float[] args);
+}
diff --git a/core/java/android/animation/RectEvaluator.java b/core/java/android/animation/RectEvaluator.java
index 28d496b..23eb766 100644
--- a/core/java/android/animation/RectEvaluator.java
+++ b/core/java/android/animation/RectEvaluator.java
@@ -23,12 +23,45 @@ import android.graphics.Rect;
public class RectEvaluator implements TypeEvaluator<Rect> {
/**
+ * When null, a new Rect is returned on every evaluate call. When non-null,
+ * mRect will be modified and returned on every evaluate.
+ */
+ private Rect mRect;
+
+ /**
+ * Construct a RectEvaluator that returns a new Rect on every evaluate call.
+ * To avoid creating an object for each evaluate call,
+ * {@link RectEvaluator#RectEvaluator(android.graphics.Rect)} should be used
+ * whenever possible.
+ */
+ public RectEvaluator() {
+ }
+
+ /**
+ * Constructs a RectEvaluator that modifies and returns <code>reuseRect</code>
+ * in {@link #evaluate(float, android.graphics.Rect, android.graphics.Rect)} calls.
+ * The value returned from
+ * {@link #evaluate(float, android.graphics.Rect, android.graphics.Rect)} should
+ * not be cached because it will change over time as the object is reused on each
+ * call.
+ *
+ * @param reuseRect A Rect to be modified and returned by evaluate.
+ */
+ public RectEvaluator(Rect reuseRect) {
+ mRect = reuseRect;
+ }
+
+ /**
* This function returns the result of linearly interpolating the start and
* end Rect values, with <code>fraction</code> representing the proportion
* between the start and end values. The calculation is a simple parametric
* calculation on each of the separate components in the Rect objects
* (left, top, right, and bottom).
*
+ * <p>If {@link #RectEvaluator(android.graphics.Rect)} was used to construct
+ * this RectEvaluator, the object returned will be the <code>reuseRect</code>
+ * passed into the constructor.</p>
+ *
* @param fraction The fraction from the starting to the ending values
* @param startValue The start Rect
* @param endValue The end Rect
@@ -37,9 +70,15 @@ public class RectEvaluator implements TypeEvaluator<Rect> {
*/
@Override
public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
- return new Rect(startValue.left + (int)((endValue.left - startValue.left) * fraction),
- startValue.top + (int)((endValue.top - startValue.top) * fraction),
- startValue.right + (int)((endValue.right - startValue.right) * fraction),
- startValue.bottom + (int)((endValue.bottom - startValue.bottom) * fraction));
+ int left = startValue.left + (int) ((endValue.left - startValue.left) * fraction);
+ int top = startValue.top + (int) ((endValue.top - startValue.top) * fraction);
+ int right = startValue.right + (int) ((endValue.right - startValue.right) * fraction);
+ int bottom = startValue.bottom + (int) ((endValue.bottom - startValue.bottom) * fraction);
+ if (mRect == null) {
+ return new Rect(left, top, right, bottom);
+ } else {
+ mRect.set(left, top, right, bottom);
+ return mRect;
+ }
}
}
diff --git a/core/java/android/animation/TypeConverter.java b/core/java/android/animation/TypeConverter.java
new file mode 100644
index 0000000..03b3eb5
--- /dev/null
+++ b/core/java/android/animation/TypeConverter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 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.animation;
+
+/**
+ * Abstract base class used convert type T to another type V. This
+ * is necessary when the value types of in animation are different
+ * from the property type.
+ * @see PropertyValuesHolder#setConverter(TypeConverter)
+ */
+public abstract class TypeConverter<T, V> {
+ private Class<T> mFromClass;
+ private Class<V> mToClass;
+
+ public TypeConverter(Class<T> fromClass, Class<V> toClass) {
+ mFromClass = fromClass;
+ mToClass = toClass;
+ }
+
+ /**
+ * Returns the target converted type. Used by the animation system to determine
+ * the proper setter function to call.
+ * @return The Class to convert the input to.
+ */
+ Class<V> getTargetType() {
+ return mToClass;
+ }
+
+ /**
+ * Returns the source conversion type.
+ */
+ Class<T> getSourceType() {
+ return mFromClass;
+ }
+
+ /**
+ * Converts a value from one type to another.
+ * @param value The Object to convert.
+ * @return A value of type V, converted from <code>value</code>.
+ */
+ public abstract V convert(T value);
+
+ /**
+ * Does a conversion from the target type back to the source type. The subclass
+ * must implement this when a TypeConverter is used in animations and current
+ * values will need to be read for an animation. By default, this will return null,
+ * indicating that back-conversion is not supported.
+ * @param value The Object to convert.
+ * @return A value of type T, converted from <code>value</code>.
+ */
+ public T convertBack(V value) {
+ return null;
+ }
+}
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 86da673..7880f39 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -280,6 +280,24 @@ public class ValueAnimator extends Animator {
}
/**
+ * Constructs and returns a ValueAnimator that animates between color values. A single
+ * value implies that that value is the one being animated to. However, this is not typically
+ * useful in a ValueAnimator object because there is no way for the object to determine the
+ * starting value for the animation (unlike ObjectAnimator, which can derive that value
+ * from the target object and property being animated). Therefore, there should typically
+ * be two or more values.
+ *
+ * @param values A set of values that the animation will animate between over time.
+ * @return A ValueAnimator object that is set up to animate between the given values.
+ */
+ public static ValueAnimator ofArgb(int... values) {
+ ValueAnimator anim = new ValueAnimator();
+ anim.setIntValues(values);
+ anim.setEvaluator(ArgbEvaluator.getInstance());
+ return anim;
+ }
+
+ /**
* Constructs and returns a ValueAnimator that animates between float values. A single
* value implies that that value is the one being animated to. However, this is not typically
* useful in a ValueAnimator object because there is no way for the object to determine the
diff --git a/core/java/android/annotation/IntDef.java b/core/java/android/annotation/IntDef.java
new file mode 100644
index 0000000..3cae9c5
--- /dev/null
+++ b/core/java/android/annotation/IntDef.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+/**
+ * Denotes that the annotated element of integer type, represents
+ * a logical type and that its value should be one of the explicitly
+ * named constants. If the {@link #flag()} attribute is set to true,
+ * multiple constants can be combined.
+ * <p>
+ * Example:
+ * <pre>{@code
+ * &#64;Retention(CLASS)
+ * &#64;IntDef(&#123;NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS&#125;)
+ * public &#64;interface NavigationMode &#123;&#125;
+ * public static final int NAVIGATION_MODE_STANDARD = 0;
+ * public static final int NAVIGATION_MODE_LIST = 1;
+ * public static final int NAVIGATION_MODE_TABS = 2;
+ * ...
+ * public abstract void setNavigationMode(&#64;NavigationMode int mode);
+ * &#64;NavigationMode
+ * public abstract int getNavigationMode();
+ * }</pre>
+ * For a flag, set the flag attribute:
+ * <pre>{@code
+ * &#64;IntDef(
+ * flag = true
+ * value = &#123;NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS&#125;)
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(CLASS)
+@Target({ANNOTATION_TYPE})
+public @interface IntDef {
+ /** Defines the allowed constants for this element */
+ long[] value() default {};
+
+ /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
+ boolean flag() default false;
+}
diff --git a/core/java/android/annotation/NonNull.java b/core/java/android/annotation/NonNull.java
new file mode 100644
index 0000000..8e604f0
--- /dev/null
+++ b/core/java/android/annotation/NonNull.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that a parameter, field or method return value can never be null.
+ * <p>
+ * This is a marker annotation and it has no specific attributes.
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface NonNull {
+}
diff --git a/core/java/android/annotation/Nullable.java b/core/java/android/annotation/Nullable.java
new file mode 100644
index 0000000..cdba2f6
--- /dev/null
+++ b/core/java/android/annotation/Nullable.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that a parameter, field or method return value can be null.
+ * <p>
+ * When decorating a method call parameter, this denotes that the parameter can
+ * legitimately be null and the method will gracefully deal with it. Typically
+ * used on optional parameters.
+ * <p>
+ * When decorating a method, this denotes the method might legitimately return
+ * null.
+ * <p>
+ * This is a marker annotation and it has no specific attributes.
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface Nullable {
+}
diff --git a/core/java/android/annotation/StringDef.java b/core/java/android/annotation/StringDef.java
new file mode 100644
index 0000000..5f7f380
--- /dev/null
+++ b/core/java/android/annotation/StringDef.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2013 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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+/**
+ * Denotes that the annotated String element, represents a logical
+ * type and that its value should be one of the explicitly named constants.
+ * <p>
+ * Example:
+ * <pre>{@code
+ * &#64;Retention(SOURCE)
+ * &#64;StringDef(&#123;
+ * POWER_SERVICE,
+ * WINDOW_SERVICE,
+ * LAYOUT_INFLATER_SERVICE
+ * &#125;)
+ * public &#64;interface ServiceName &#123;&#125;
+ * public static final String POWER_SERVICE = "power";
+ * public static final String WINDOW_SERVICE = "window";
+ * public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
+ * ...
+ * public abstract Object getSystemService(&#64;ServiceName String name);
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(CLASS)
+@Target({ANNOTATION_TYPE})
+public @interface StringDef {
+ /** Defines the allowed constants for this element */
+ String[] value() default {};
+}
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index c4ddf1f..fbe8987 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -16,6 +16,9 @@
package android.app;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
@@ -28,6 +31,9 @@ import android.view.ViewGroup.MarginLayoutParams;
import android.view.Window;
import android.widget.SpinnerAdapter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A window feature at the top of the activity that may display the activity title, navigation
* modes, and other interactive items.
@@ -57,6 +63,11 @@ import android.widget.SpinnerAdapter;
* </div>
*/
public abstract class ActionBar {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ public @interface NavigationMode {}
+
/**
* Standard navigation mode. Consists of either a logo or icon
* and title text with an optional subtitle. Clicking any of these elements
@@ -78,6 +89,19 @@ public abstract class ActionBar {
*/
public static final int NAVIGATION_MODE_TABS = 2;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ value = {
+ DISPLAY_USE_LOGO,
+ DISPLAY_SHOW_HOME,
+ DISPLAY_HOME_AS_UP,
+ DISPLAY_SHOW_TITLE,
+ DISPLAY_SHOW_CUSTOM,
+ DISPLAY_TITLE_MULTIPLE_LINES
+ })
+ public @interface DisplayOptions {}
+
/**
* Use logo instead of icon if available. This flag will cause appropriate
* navigation modes to use a wider logo in place of the standard icon.
@@ -341,7 +365,7 @@ public abstract class ActionBar {
* @param options A combination of the bits defined by the DISPLAY_ constants
* defined in ActionBar.
*/
- public abstract void setDisplayOptions(int options);
+ public abstract void setDisplayOptions(@DisplayOptions int options);
/**
* Set selected display options. Only the options specified by mask will be changed.
@@ -356,7 +380,7 @@ public abstract class ActionBar {
* defined in ActionBar.
* @param mask A bit mask declaring which display options should be changed.
*/
- public abstract void setDisplayOptions(int options, int mask);
+ public abstract void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask);
/**
* Set whether to display the activity logo rather than the activity icon.
@@ -431,7 +455,7 @@ public abstract class ActionBar {
* @see #setStackedBackgroundDrawable(Drawable)
* @see #setSplitBackgroundDrawable(Drawable)
*/
- public abstract void setBackgroundDrawable(Drawable d);
+ public abstract void setBackgroundDrawable(@Nullable Drawable d);
/**
* Set the ActionBar's stacked background. This will appear
@@ -484,6 +508,7 @@ public abstract class ActionBar {
*
* @return The current navigation mode.
*/
+ @NavigationMode
public abstract int getNavigationMode();
/**
@@ -494,7 +519,7 @@ public abstract class ActionBar {
* @see #NAVIGATION_MODE_LIST
* @see #NAVIGATION_MODE_TABS
*/
- public abstract void setNavigationMode(int mode);
+ public abstract void setNavigationMode(@NavigationMode int mode);
/**
* @return The current set of display options.
@@ -1024,7 +1049,7 @@ public abstract class ActionBar {
})
public int gravity = Gravity.NO_GRAVITY;
- public LayoutParams(Context c, AttributeSet attrs) {
+ public LayoutParams(@NonNull Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs,
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d6db8c2..d34b05d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -16,11 +16,14 @@
package android.app;
+import android.annotation.NonNull;
import android.util.ArrayMap;
import android.util.SuperNotCalledException;
import com.android.internal.app.ActionBarImpl;
import com.android.internal.policy.PolicyManager;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -84,6 +87,8 @@ import android.widget.AdapterView;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
@@ -852,6 +857,7 @@ public class Activity extends ContextThemeWrapper
* @see #getWindow
* @see android.view.Window#getCurrentFocus
*/
+ @Nullable
public View getCurrentFocus() {
return mWindow != null ? mWindow.getCurrentFocus() : null;
}
@@ -882,7 +888,7 @@ public class Activity extends ContextThemeWrapper
* @see #onRestoreInstanceState
* @see #onPostCreate
*/
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
if (mLastNonConfigurationInstances != null) {
mAllLoaderManagers = mLastNonConfigurationInstances.loaders;
@@ -1010,7 +1016,7 @@ public class Activity extends ContextThemeWrapper
* recently supplied in {@link #onSaveInstanceState}. <b><i>Note: Otherwise it is null.</i></b>
* @see #onCreate
*/
- protected void onPostCreate(Bundle savedInstanceState) {
+ protected void onPostCreate(@Nullable Bundle savedInstanceState) {
if (!isChild()) {
mTitleReady = true;
onTitleChanged(getTitle(), getTitleColor());
@@ -1347,6 +1353,7 @@ public class Activity extends ContextThemeWrapper
* @see #onSaveInstanceState
* @see #onPause
*/
+ @Nullable
public CharSequence onCreateDescription() {
return null;
}
@@ -1551,6 +1558,7 @@ public class Activity extends ContextThemeWrapper
* {@link Fragment#setRetainInstance(boolean)} instead; this is also
* available on older platforms through the Android compatibility package.
*/
+ @Nullable
@Deprecated
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
@@ -1630,6 +1638,7 @@ public class Activity extends ContextThemeWrapper
* @return Returns the object previously returned by
* {@link #onRetainNonConfigurationChildInstances()}
*/
+ @Nullable
HashMap<String, Object> getLastNonConfigurationChildInstances() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.children : null;
@@ -1642,6 +1651,7 @@ public class Activity extends ContextThemeWrapper
* set of child activities, such as ActivityGroup. The same guarantees and restrictions apply
* as for {@link #onRetainNonConfigurationInstance()}. The default implementation returns null.
*/
+ @Nullable
HashMap<String,Object> onRetainNonConfigurationChildInstances() {
return null;
}
@@ -1889,6 +1899,7 @@ public class Activity extends ContextThemeWrapper
*
* @return The Activity's ActionBar, or null if it does not have one.
*/
+ @Nullable
public ActionBar getActionBar() {
initActionBar();
return mActionBar;
@@ -1985,7 +1996,17 @@ public class Activity extends ContextThemeWrapper
public void setFinishOnTouchOutside(boolean finish) {
mWindow.setCloseOnTouchOutside(finish);
}
-
+
+ /** @hide */
+ @IntDef({
+ DEFAULT_KEYS_DISABLE,
+ DEFAULT_KEYS_DIALER,
+ DEFAULT_KEYS_SHORTCUT,
+ DEFAULT_KEYS_SEARCH_LOCAL,
+ DEFAULT_KEYS_SEARCH_GLOBAL})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface DefaultKeyMode {}
+
/**
* Use with {@link #setDefaultKeyMode} to turn off default handling of
* keys.
@@ -2055,7 +2076,7 @@ public class Activity extends ContextThemeWrapper
* @see #DEFAULT_KEYS_SEARCH_GLOBAL
* @see #onKeyDown
*/
- public final void setDefaultKeyMode(int mode) {
+ public final void setDefaultKeyMode(@DefaultKeyMode int mode) {
mDefaultKeyMode = mode;
// Some modes use a SpannableStringBuilder to track & dispatch input events
@@ -2521,6 +2542,7 @@ public class Activity extends ContextThemeWrapper
* simply returns null so that all panel sub-windows will have the default
* menu behavior.
*/
+ @Nullable
public View onCreatePanelView(int featureId) {
return null;
}
@@ -3018,6 +3040,7 @@ public class Activity extends ContextThemeWrapper
* {@link FragmentManager} instead; this is also
* available on older platforms through the Android compatibility package.
*/
+ @Nullable
@Deprecated
protected Dialog onCreateDialog(int id, Bundle args) {
return onCreateDialog(id);
@@ -3105,6 +3128,7 @@ public class Activity extends ContextThemeWrapper
* {@link FragmentManager} instead; this is also
* available on older platforms through the Android compatibility package.
*/
+ @Nullable
@Deprecated
public final boolean showDialog(int id, Bundle args) {
if (mManagedDialogs == null) {
@@ -3226,13 +3250,13 @@ public class Activity extends ContextThemeWrapper
* <p>It is typically called from onSearchRequested(), either directly from
* Activity.onSearchRequested() or from an overridden version in any given
* Activity. If your goal is simply to activate search, it is preferred to call
- * onSearchRequested(), which may have been overriden elsewhere in your Activity. If your goal
+ * onSearchRequested(), which may have been overridden elsewhere in your Activity. If your goal
* is to inject specific data such as context data, it is preferred to <i>override</i>
* onSearchRequested(), so that any callers to it will benefit from the override.
*
* @param initialQuery Any non-null non-empty string will be inserted as
* pre-entered text in the search query box.
- * @param selectInitialQuery If true, the intial query will be preselected, which means that
+ * @param selectInitialQuery If true, the initial query will be preselected, which means that
* any further typing will replace it. This is useful for cases where an entire pre-formed
* query is being inserted. If false, the selection point will be placed at the end of the
* inserted query. This is useful when the inserted query is text that the user entered,
@@ -3250,8 +3274,8 @@ public class Activity extends ContextThemeWrapper
* @see android.app.SearchManager
* @see #onSearchRequested
*/
- public void startSearch(String initialQuery, boolean selectInitialQuery,
- Bundle appSearchData, boolean globalSearch) {
+ public void startSearch(@Nullable String initialQuery, boolean selectInitialQuery,
+ @Nullable Bundle appSearchData, boolean globalSearch) {
ensureSearchManager();
mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
appSearchData, globalSearch);
@@ -3267,7 +3291,7 @@ public class Activity extends ContextThemeWrapper
* searches. This data will be returned with SEARCH intent(s). Null if
* no extra data is required.
*/
- public void triggerSearch(String query, Bundle appSearchData) {
+ public void triggerSearch(String query, @Nullable Bundle appSearchData) {
ensureSearchManager();
mSearchManager.triggerSearch(query, getComponentName(), appSearchData);
}
@@ -3334,6 +3358,7 @@ public class Activity extends ContextThemeWrapper
* Convenience for calling
* {@link android.view.Window#getLayoutInflater}.
*/
+ @NonNull
public LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
@@ -3341,6 +3366,7 @@ public class Activity extends ContextThemeWrapper
/**
* Returns a {@link MenuInflater} with this context.
*/
+ @NonNull
public MenuInflater getMenuInflater() {
// Make sure that action views can get an appropriate theme.
if (mMenuInflater == null) {
@@ -3419,7 +3445,7 @@ public class Activity extends ContextThemeWrapper
*
* @see #startActivity
*/
- public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
+ public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
if (mParent == null) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
@@ -3498,7 +3524,7 @@ public class Activity extends ContextThemeWrapper
* @param extraFlags Always set to 0.
*/
public void startIntentSenderForResult(IntentSender intent, int requestCode,
- Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
throws IntentSender.SendIntentException {
startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
flagsValues, extraFlags, null);
@@ -3530,7 +3556,7 @@ public class Activity extends ContextThemeWrapper
* override any that conflict with those given by the IntentSender.
*/
public void startIntentSenderForResult(IntentSender intent, int requestCode,
- Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
Bundle options) throws IntentSender.SendIntentException {
if (mParent == null) {
startIntentSenderForResultInner(intent, requestCode, fillInIntent,
@@ -3618,7 +3644,7 @@ public class Activity extends ContextThemeWrapper
* @see #startActivityForResult
*/
@Override
- public void startActivity(Intent intent, Bundle options) {
+ public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
@@ -3667,7 +3693,7 @@ public class Activity extends ContextThemeWrapper
* @see #startActivityForResult
*/
@Override
- public void startActivities(Intent[] intents, Bundle options) {
+ public void startActivities(Intent[] intents, @Nullable Bundle options) {
mInstrumentation.execStartActivities(this, mMainThread.getApplicationThread(),
mToken, this, intents, options);
}
@@ -3686,7 +3712,7 @@ public class Activity extends ContextThemeWrapper
* @param extraFlags Always set to 0.
*/
public void startIntentSender(IntentSender intent,
- Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
throws IntentSender.SendIntentException {
startIntentSender(intent, fillInIntent, flagsMask, flagsValues,
extraFlags, null);
@@ -3713,7 +3739,7 @@ public class Activity extends ContextThemeWrapper
* override any that conflict with those given by the IntentSender.
*/
public void startIntentSender(IntentSender intent,
- Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
Bundle options) throws IntentSender.SendIntentException {
if (options != null) {
startIntentSenderForResult(intent, -1, fillInIntent, flagsMask,
@@ -3741,7 +3767,7 @@ public class Activity extends ContextThemeWrapper
* @see #startActivity
* @see #startActivityForResult
*/
- public boolean startActivityIfNeeded(Intent intent, int requestCode) {
+ public boolean startActivityIfNeeded(@NonNull Intent intent, int requestCode) {
return startActivityIfNeeded(intent, requestCode, null);
}
@@ -3775,7 +3801,8 @@ public class Activity extends ContextThemeWrapper
* @see #startActivity
* @see #startActivityForResult
*/
- public boolean startActivityIfNeeded(Intent intent, int requestCode, Bundle options) {
+ public boolean startActivityIfNeeded(@NonNull Intent intent, int requestCode,
+ @Nullable Bundle options) {
if (mParent == null) {
int result = ActivityManager.START_RETURN_INTENT_TO_CALLER;
try {
@@ -3824,7 +3851,7 @@ public class Activity extends ContextThemeWrapper
* wasn't. In general, if true is returned you will then want to call
* finish() on yourself.
*/
- public boolean startNextMatchingActivity(Intent intent) {
+ public boolean startNextMatchingActivity(@NonNull Intent intent) {
return startNextMatchingActivity(intent, null);
}
@@ -3847,7 +3874,7 @@ public class Activity extends ContextThemeWrapper
* wasn't. In general, if true is returned you will then want to call
* finish() on yourself.
*/
- public boolean startNextMatchingActivity(Intent intent, Bundle options) {
+ public boolean startNextMatchingActivity(@NonNull Intent intent, @Nullable Bundle options) {
if (mParent == null) {
try {
intent.migrateExtraStreamToClipData();
@@ -3877,7 +3904,7 @@ public class Activity extends ContextThemeWrapper
* @see #startActivity
* @see #startActivityForResult
*/
- public void startActivityFromChild(Activity child, Intent intent,
+ public void startActivityFromChild(@NonNull Activity child, Intent intent,
int requestCode) {
startActivityFromChild(child, intent, requestCode, null);
}
@@ -3901,8 +3928,8 @@ public class Activity extends ContextThemeWrapper
* @see #startActivity
* @see #startActivityForResult
*/
- public void startActivityFromChild(Activity child, Intent intent,
- int requestCode, Bundle options) {
+ public void startActivityFromChild(@NonNull Activity child, Intent intent,
+ int requestCode, @Nullable Bundle options) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, child,
@@ -3927,7 +3954,7 @@ public class Activity extends ContextThemeWrapper
* @see Fragment#startActivity
* @see Fragment#startActivityForResult
*/
- public void startActivityFromFragment(Fragment fragment, Intent intent,
+ public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent,
int requestCode) {
startActivityFromFragment(fragment, intent, requestCode, null);
}
@@ -3952,8 +3979,8 @@ public class Activity extends ContextThemeWrapper
* @see Fragment#startActivity
* @see Fragment#startActivityForResult
*/
- public void startActivityFromFragment(Fragment fragment, Intent intent,
- int requestCode, Bundle options) {
+ public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent,
+ int requestCode, @Nullable Bundle options) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, fragment,
@@ -3985,7 +4012,7 @@ public class Activity extends ContextThemeWrapper
*/
public void startIntentSenderFromChild(Activity child, IntentSender intent,
int requestCode, Intent fillInIntent, int flagsMask, int flagsValues,
- int extraFlags, Bundle options)
+ int extraFlags, @Nullable Bundle options)
throws IntentSender.SendIntentException {
startIntentSenderForResultInner(intent, requestCode, fillInIntent,
flagsMask, flagsValues, child, options);
@@ -4084,6 +4111,7 @@ public class Activity extends ContextThemeWrapper
* @return The package of the activity that will receive your
* reply, or null if none.
*/
+ @Nullable
public String getCallingPackage() {
try {
return ActivityManagerNative.getDefault().getCallingPackage(mToken);
@@ -4106,6 +4134,7 @@ public class Activity extends ContextThemeWrapper
* @return The ComponentName of the activity that will receive your
* reply, or null if none.
*/
+ @Nullable
public ComponentName getCallingActivity() {
try {
return ActivityManagerNative.getDefault().getCallingActivity(mToken);
@@ -4298,7 +4327,7 @@ public class Activity extends ContextThemeWrapper
* @param requestCode Request code that had been used to start the
* activity.
*/
- public void finishActivityFromChild(Activity child, int requestCode) {
+ public void finishActivityFromChild(@NonNull Activity child, int requestCode) {
try {
ActivityManagerNative.getDefault()
.finishSubActivity(mToken, child.mEmbeddedID, requestCode);
@@ -4359,8 +4388,8 @@ public class Activity extends ContextThemeWrapper
*
* @see PendingIntent
*/
- public PendingIntent createPendingResult(int requestCode, Intent data,
- int flags) {
+ public PendingIntent createPendingResult(int requestCode, @NonNull Intent data,
+ @PendingIntent.Flags int flags) {
String packageName = getPackageName();
try {
data.prepareToLeaveProcess();
@@ -4387,7 +4416,7 @@ public class Activity extends ContextThemeWrapper
* @param requestedOrientation An orientation constant as used in
* {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
*/
- public void setRequestedOrientation(int requestedOrientation) {
+ public void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) {
if (mParent == null) {
try {
ActivityManagerNative.getDefault().setRequestedOrientation(
@@ -4409,6 +4438,7 @@ public class Activity extends ContextThemeWrapper
* @return Returns an orientation constant as used in
* {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
*/
+ @ActivityInfo.ScreenOrientation
public int getRequestedOrientation() {
if (mParent == null) {
try {
@@ -4480,6 +4510,7 @@ public class Activity extends ContextThemeWrapper
*
* @return The local class name.
*/
+ @NonNull
public String getLocalClassName() {
final String pkg = getPackageName();
final String cls = mComponent.getClassName();
@@ -4525,9 +4556,9 @@ public class Activity extends ContextThemeWrapper
mSearchManager = new SearchManager(this, null);
}
-
+
@Override
- public Object getSystemService(String name) {
+ public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
@@ -4567,6 +4598,17 @@ public class Activity extends ContextThemeWrapper
setTitle(getText(titleId));
}
+ /**
+ * Change the color of the title associated with this activity.
+ * <p>
+ * This method is deprecated starting in API Level 11 and replaced by action
+ * bar styles. For information on styling the Action Bar, read the <a
+ * href="{@docRoot} guide/topics/ui/actionbar.html">Action Bar</a> developer
+ * guide.
+ *
+ * @deprecated Use action bar styles instead.
+ */
+ @Deprecated
public void setTitleColor(int textColor) {
mTitleColor = textColor;
onTitleChanged(mTitle, textColor);
@@ -4689,7 +4731,7 @@ public class Activity extends ContextThemeWrapper
/**
* Gets the suggested audio stream whose volume should be changed by the
- * harwdare volume controls.
+ * hardware volume controls.
*
* @return The suggested audio stream type whose volume should be changed by
* the hardware volume controls.
@@ -4725,6 +4767,7 @@ public class Activity extends ContextThemeWrapper
* @see android.view.LayoutInflater#createView
* @see android.view.Window#getLayoutInflater
*/
+ @Nullable
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
@@ -4983,6 +5026,7 @@ public class Activity extends ContextThemeWrapper
*
* @see ActionMode
*/
+ @Nullable
public ActionMode startActionMode(ActionMode.Callback callback) {
return mWindow.getDecorView().startActionMode(callback);
}
@@ -4998,6 +5042,7 @@ public class Activity extends ContextThemeWrapper
* @return The new action mode, or <code>null</code> if the activity does not want to
* provide special handling for this action mode. (It will be handled by the system.)
*/
+ @Nullable
@Override
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
initActionBar();
@@ -5141,6 +5186,7 @@ public class Activity extends ContextThemeWrapper
* @return a new Intent targeting the defined parent of this activity or null if
* there is no valid parent.
*/
+ @Nullable
public Intent getParentActivityIntent() {
final String parentName = mActivityInfo.parentActivityName;
if (TextUtils.isEmpty(parentName)) {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 87b1e24..44f6859 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -90,6 +90,35 @@ public class ActivityOptions {
*/
public static final String KEY_ANIM_START_LISTENER = "android:animStartListener";
+ /**
+ * A string array of names for the destination scene. This defines an API in the same
+ * way that intent action or extra names do and should follow a similar convention:
+ * "com.example.scene.FOO"
+ *
+ * @hide
+ */
+ public static final String KEY_DEST_SCENE_NAMES = "android:destSceneNames";
+
+ /**
+ * A string indicating the destination scene name that was chosen by the target.
+ * Used by {@link OnSceneTransitionStartedListener}.
+ * @hide
+ */
+ public static final String KEY_DEST_SCENE_NAME_CHOSEN = "android:destSceneNameChosen";
+
+ /**
+ * Callback for when scene transition is started.
+ * @hide
+ */
+ public static final String KEY_SCENE_TRANSITION_START_LISTENER =
+ "android:sceneTransitionStartListener";
+
+ /**
+ * Arguments for the scene transition about to begin.
+ * @hide
+ */
+ public static final String KEY_SCENE_TRANSITION_ARGS = "android:sceneTransitionArgs";
+
/** @hide */
public static final int ANIM_NONE = 0;
/** @hide */
@@ -100,6 +129,8 @@ public class ActivityOptions {
public static final int ANIM_THUMBNAIL_SCALE_UP = 3;
/** @hide */
public static final int ANIM_THUMBNAIL_SCALE_DOWN = 4;
+ /** @hide */
+ public static final int ANIM_SCENE_TRANSITION = 5;
private String mPackageName;
private int mAnimationType = ANIM_NONE;
@@ -110,7 +141,10 @@ public class ActivityOptions {
private int mStartY;
private int mStartWidth;
private int mStartHeight;
+ private String[] mDestSceneNames;
+ private Bundle mTransitionArgs;
private IRemoteCallback mAnimationStartedListener;
+ private IRemoteCallback mSceneTransitionStartedListener;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -156,11 +190,12 @@ public class ActivityOptions {
opts.mAnimationType = ANIM_CUSTOM;
opts.mCustomEnterResId = enterResId;
opts.mCustomExitResId = exitResId;
- opts.setListener(handler, listener);
+ opts.setOnAnimationStartedListener(handler, listener);
return opts;
}
- private void setListener(Handler handler, OnAnimationStartedListener listener) {
+ private void setOnAnimationStartedListener(Handler handler,
+ OnAnimationStartedListener listener) {
if (listener != null) {
final Handler h = handler;
final OnAnimationStartedListener finalListener = listener;
@@ -176,6 +211,24 @@ public class ActivityOptions {
}
}
+ private void setOnSceneTransitionStartedListener(Handler handler,
+ OnSceneTransitionStartedListener listener) {
+ if (listener != null) {
+ final Handler h = handler;
+ final OnSceneTransitionStartedListener l = listener;
+ mSceneTransitionStartedListener = new IRemoteCallback.Stub() {
+ @Override public void sendResult(final Bundle data) throws RemoteException {
+ h.post(new Runnable() {
+ public void run() {
+ l.onSceneTransitionStarted(data != null ?
+ data.getString(KEY_DEST_SCENE_NAME_CHOSEN) : null);
+ }
+ });
+ }
+ };
+ }
+ }
+
/**
* Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation}
* to find out when the given animation has started running.
@@ -186,6 +239,15 @@ public class ActivityOptions {
}
/**
+ * Callback for use with {@link ActivityOptions#makeSceneTransitionAnimation}
+ * to find out when a transition is about to begin.
+ * @hide
+ */
+ public interface OnSceneTransitionStartedListener {
+ void onSceneTransitionStarted(String destSceneName);
+ }
+
+ /**
* Create an ActivityOptions specifying an animation where the new
* activity is scaled from a small originating area of the screen to
* its final full representation.
@@ -298,7 +360,23 @@ public class ActivityOptions {
source.getLocationOnScreen(pts);
opts.mStartX = pts[0] + startX;
opts.mStartY = pts[1] + startY;
- opts.setListener(source.getHandler(), listener);
+ opts.setOnAnimationStartedListener(source.getHandler(), listener);
+ return opts;
+ }
+
+ /**
+ * Create an ActivityOptions specifying an animation where an activity window is asked
+ * to perform animations within the window content.
+ *
+ * @hide
+ */
+ public static ActivityOptions makeSceneTransitionAnimation(String[] destSceneNames,
+ Bundle args, OnSceneTransitionStartedListener listener, Handler handler) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mAnimationType = ANIM_SCENE_TRANSITION;
+ opts.mDestSceneNames = destSceneNames;
+ opts.mTransitionArgs = args;
+ opts.setOnSceneTransitionStartedListener(handler, listener);
return opts;
}
@@ -309,23 +387,36 @@ public class ActivityOptions {
public ActivityOptions(Bundle opts) {
mPackageName = opts.getString(KEY_PACKAGE_NAME);
mAnimationType = opts.getInt(KEY_ANIM_TYPE);
- if (mAnimationType == ANIM_CUSTOM) {
- mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0);
- mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0);
- mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
- opts.getIBinder(KEY_ANIM_START_LISTENER));
- } else if (mAnimationType == ANIM_SCALE_UP) {
- mStartX = opts.getInt(KEY_ANIM_START_X, 0);
- mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
- mStartWidth = opts.getInt(KEY_ANIM_START_WIDTH, 0);
- mStartHeight = opts.getInt(KEY_ANIM_START_HEIGHT, 0);
- } else if (mAnimationType == ANIM_THUMBNAIL_SCALE_UP ||
- mAnimationType == ANIM_THUMBNAIL_SCALE_DOWN) {
- mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL);
- mStartX = opts.getInt(KEY_ANIM_START_X, 0);
- mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
- mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
- opts.getIBinder(KEY_ANIM_START_LISTENER));
+ switch (mAnimationType) {
+ case ANIM_CUSTOM:
+ mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0);
+ mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0);
+ mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
+ opts.getBinder(KEY_ANIM_START_LISTENER));
+ break;
+
+ case ANIM_SCALE_UP:
+ mStartX = opts.getInt(KEY_ANIM_START_X, 0);
+ mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
+ mStartWidth = opts.getInt(KEY_ANIM_START_WIDTH, 0);
+ mStartHeight = opts.getInt(KEY_ANIM_START_HEIGHT, 0);
+ break;
+
+ case ANIM_THUMBNAIL_SCALE_UP:
+ case ANIM_THUMBNAIL_SCALE_DOWN:
+ mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL);
+ mStartX = opts.getInt(KEY_ANIM_START_X, 0);
+ mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
+ mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
+ opts.getBinder(KEY_ANIM_START_LISTENER));
+ break;
+
+ case ANIM_SCENE_TRANSITION:
+ mDestSceneNames = opts.getStringArray(KEY_DEST_SCENE_NAMES);
+ mTransitionArgs = opts.getBundle(KEY_SCENE_TRANSITION_ARGS);
+ mSceneTransitionStartedListener = IRemoteCallback.Stub.asInterface(
+ opts.getBinder(KEY_SCENE_TRANSITION_START_LISTENER));
+ break;
}
}
@@ -375,11 +466,26 @@ public class ActivityOptions {
}
/** @hide */
+ public String[] getDestSceneNames() {
+ return mDestSceneNames;
+ }
+
+ /** @hide */
+ public Bundle getSceneTransitionArgs() {
+ return mTransitionArgs;
+ }
+
+ /** @hide */
public IRemoteCallback getOnAnimationStartListener() {
return mAnimationStartedListener;
}
/** @hide */
+ public IRemoteCallback getOnSceneTransitionStartedListener() {
+ return mSceneTransitionStartedListener;
+ }
+
+ /** @hide */
public void abort() {
if (mAnimationStartedListener != null) {
try {
@@ -411,13 +517,16 @@ public class ActivityOptions {
mCustomEnterResId = otherOptions.mCustomEnterResId;
mCustomExitResId = otherOptions.mCustomExitResId;
mThumbnail = null;
- if (otherOptions.mAnimationStartedListener != null) {
+ if (mAnimationStartedListener != null) {
try {
- otherOptions.mAnimationStartedListener.sendResult(null);
+ mAnimationStartedListener.sendResult(null);
} catch (RemoteException e) {
}
}
mAnimationStartedListener = otherOptions.mAnimationStartedListener;
+ mSceneTransitionStartedListener = null;
+ mTransitionArgs = null;
+ mDestSceneNames = null;
break;
case ANIM_SCALE_UP:
mAnimationType = otherOptions.mAnimationType;
@@ -425,13 +534,16 @@ public class ActivityOptions {
mStartY = otherOptions.mStartY;
mStartWidth = otherOptions.mStartWidth;
mStartHeight = otherOptions.mStartHeight;
- if (otherOptions.mAnimationStartedListener != null) {
+ if (mAnimationStartedListener != null) {
try {
- otherOptions.mAnimationStartedListener.sendResult(null);
+ mAnimationStartedListener.sendResult(null);
} catch (RemoteException e) {
}
}
mAnimationStartedListener = null;
+ mSceneTransitionStartedListener = null;
+ mTransitionArgs = null;
+ mDestSceneNames = null;
break;
case ANIM_THUMBNAIL_SCALE_UP:
case ANIM_THUMBNAIL_SCALE_DOWN:
@@ -439,13 +551,28 @@ public class ActivityOptions {
mThumbnail = otherOptions.mThumbnail;
mStartX = otherOptions.mStartX;
mStartY = otherOptions.mStartY;
- if (otherOptions.mAnimationStartedListener != null) {
+ if (mAnimationStartedListener != null) {
try {
- otherOptions.mAnimationStartedListener.sendResult(null);
+ mAnimationStartedListener.sendResult(null);
} catch (RemoteException e) {
}
}
mAnimationStartedListener = otherOptions.mAnimationStartedListener;
+ mSceneTransitionStartedListener = null;
+ mTransitionArgs = null;
+ mDestSceneNames = null;
+ break;
+ case ANIM_SCENE_TRANSITION:
+ mAnimationType = otherOptions.mAnimationType;
+ if (mSceneTransitionStartedListener != null) {
+ try {
+ mSceneTransitionStartedListener.sendResult(null);
+ } catch (RemoteException e) {
+ }
+ }
+ mSceneTransitionStartedListener = otherOptions.mSceneTransitionStartedListener;
+ mDestSceneNames = otherOptions.mDestSceneNames;
+ mAnimationStartedListener = null;
break;
}
}
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index 10d5e25..77526c3 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -27,7 +27,6 @@ import android.os.Message;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index aece462..e71d47d 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -36,7 +36,7 @@ import android.os.RemoteException;
* API for interacting with "application operation" tracking.
*
* <p>This API is not generally intended for third party application developers; most
- * features are only available to system applicatins. Obtain an instance of it through
+ * features are only available to system applications. Obtain an instance of it through
* {@link Context#getSystemService(String) Context.getSystemService} with
* {@link Context#APP_OPS_SERVICE Context.APP_OPS_SERVICE}.</p>
*/
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index c117486..8b132e0 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -24,7 +24,6 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Printer;
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index b505d4f..b910ba5 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -57,8 +57,6 @@ import android.view.Display;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
/*package*/
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index cda2c5f..92f3ffc 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -17,7 +17,6 @@
package android.app;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import com.android.internal.app.ActionBarImpl;
import com.android.internal.policy.PolicyManager;
diff --git a/core/java/android/app/ExpandableListActivity.java b/core/java/android/app/ExpandableListActivity.java
index 9651078..e08f25a 100644
--- a/core/java/android/app/ExpandableListActivity.java
+++ b/core/java/android/app/ExpandableListActivity.java
@@ -27,7 +27,6 @@ import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.SimpleCursorTreeAdapter;
import android.widget.SimpleExpandableListAdapter;
-import android.widget.AdapterView.AdapterContextMenuInfo;
import java.util.Map;
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index d626e5f..c09da87 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -17,6 +17,7 @@
package android.app;
import android.animation.Animator;
+import android.annotation.Nullable;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
@@ -575,7 +576,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
* the given fragment class. This is a runtime exception; it is not
* normally expected to happen.
*/
- public static Fragment instantiate(Context context, String fname, Bundle args) {
+ public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
try {
Class<?> clazz = sClassMap.get(fname);
if (clazz == null) {
@@ -1213,7 +1214,8 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
*
* @return Return the View for the fragment's UI, or null.
*/
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ @Nullable
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
return null;
}
@@ -1228,7 +1230,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
* @param savedInstanceState If non-null, this fragment is being re-constructed
* from a previous saved state as given here.
*/
- public void onViewCreated(View view, Bundle savedInstanceState) {
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
}
/**
@@ -1237,6 +1239,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
*
* @return The fragment's root view, or null if it has no layout.
*/
+ @Nullable
public View getView() {
return mView;
}
diff --git a/core/java/android/app/FragmentBreadCrumbs.java b/core/java/android/app/FragmentBreadCrumbs.java
index b810b89..e4de7af 100644
--- a/core/java/android/app/FragmentBreadCrumbs.java
+++ b/core/java/android/app/FragmentBreadCrumbs.java
@@ -81,14 +81,19 @@ public class FragmentBreadCrumbs extends ViewGroup
}
public FragmentBreadCrumbs(Context context, AttributeSet attrs) {
- this(context, attrs, android.R.style.Widget_FragmentBreadCrumbs);
+ this(context, attrs, com.android.internal.R.attr.fragmentBreadCrumbsStyle);
}
- public FragmentBreadCrumbs(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public FragmentBreadCrumbs(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public FragmentBreadCrumbs(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.FragmentBreadCrumbs, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.FragmentBreadCrumbs, defStyleAttr, defStyleRes);
mGravity = a.getInt(com.android.internal.R.styleable.FragmentBreadCrumbs_gravity,
DEFAULT_GRAVITY);
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 3efd3c0..181eb63 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -71,4 +71,14 @@ interface IWallpaperManager {
* Returns the desired minimum height for the wallpaper.
*/
int getHeightHint();
+
+ /**
+ * Returns the name of the wallpaper. Private API.
+ */
+ String getName();
+
+ /**
+ * Informs the service that wallpaper settings have been restored. Private API.
+ */
+ void settingsRestored();
}
diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java
index a7982f4..fa2813e 100644
--- a/core/java/android/app/MediaRouteButton.java
+++ b/core/java/android/app/MediaRouteButton.java
@@ -73,13 +73,18 @@ public class MediaRouteButton extends View {
}
public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public MediaRouteButton(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mCallback = new MediaRouterCallback();
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, 0);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, defStyleRes);
setRemoteIndicatorDrawable(a.getDrawable(
com.android.internal.R.styleable.MediaRouteButton_externalRouteEnabledDrawable));
mMinWidth = a.getDimensionPixelSize(
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index c63e586..ed3bb92 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -18,6 +18,7 @@ package android.app;
import com.android.internal.R;
+import android.annotation.IntDef;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -37,6 +38,8 @@ import android.view.View;
import android.widget.ProgressBar;
import android.widget.RemoteViews;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.text.NumberFormat;
import java.util.ArrayList;
@@ -350,6 +353,11 @@ public class Notification implements Parcelable
public int flags;
+ /** @hide */
+ @IntDef({PRIORITY_DEFAULT,PRIORITY_LOW,PRIORITY_MIN,PRIORITY_HIGH,PRIORITY_MAX})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Priority {}
+
/**
* Default notification {@link #priority}. If your application does not prioritize its own
* notifications, use this value for all notifications.
@@ -391,6 +399,7 @@ public class Notification implements Parcelable
* system will make a determination about how to interpret this priority when presenting
* the notification.
*/
+ @Priority
public int priority;
/**
@@ -1550,7 +1559,7 @@ public class Notification implements Parcelable
*
* @see Notification#priority
*/
- public Builder setPriority(int pri) {
+ public Builder setPriority(@Priority int pri) {
mPriority = pri;
return this;
}
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index bdd0adb..a03e5b6 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -16,6 +16,9 @@
package android.app;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.IIntentReceiver;
@@ -30,6 +33,9 @@ import android.os.Parcelable;
import android.os.UserHandle;
import android.util.AndroidException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A description of an Intent and target action to perform with it. Instances
* of this class are created with {@link #getActivity}, {@link #getActivities},
@@ -86,6 +92,26 @@ import android.util.AndroidException;
public final class PendingIntent implements Parcelable {
private final IIntentSender mTarget;
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ FLAG_ONE_SHOT,
+ FLAG_NO_CREATE,
+ FLAG_CANCEL_CURRENT,
+ FLAG_UPDATE_CURRENT,
+
+ Intent.FILL_IN_ACTION,
+ Intent.FILL_IN_DATA,
+ Intent.FILL_IN_CATEGORIES,
+ Intent.FILL_IN_COMPONENT,
+ Intent.FILL_IN_PACKAGE,
+ Intent.FILL_IN_SOURCE_BOUNDS,
+ Intent.FILL_IN_SELECTOR,
+ Intent.FILL_IN_CLIP_DATA
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Flags {}
+
/**
* Flag for use with {@link #getActivity}, {@link #getBroadcast}, and
* {@link #getService}: this
@@ -220,7 +246,7 @@ public final class PendingIntent implements Parcelable {
* supplied.
*/
public static PendingIntent getActivity(Context context, int requestCode,
- Intent intent, int flags) {
+ Intent intent, @Flags int flags) {
return getActivity(context, requestCode, intent, flags, null);
}
@@ -253,7 +279,7 @@ public final class PendingIntent implements Parcelable {
* supplied.
*/
public static PendingIntent getActivity(Context context, int requestCode,
- Intent intent, int flags, Bundle options) {
+ @NonNull Intent intent, @Flags int flags, @Nullable Bundle options) {
String packageName = context.getPackageName();
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
@@ -278,7 +304,7 @@ public final class PendingIntent implements Parcelable {
* activity is started, not when the pending intent is created.
*/
public static PendingIntent getActivityAsUser(Context context, int requestCode,
- Intent intent, int flags, Bundle options, UserHandle user) {
+ @NonNull Intent intent, int flags, Bundle options, UserHandle user) {
String packageName = context.getPackageName();
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
@@ -343,7 +369,7 @@ public final class PendingIntent implements Parcelable {
* supplied.
*/
public static PendingIntent getActivities(Context context, int requestCode,
- Intent[] intents, int flags) {
+ @NonNull Intent[] intents, @Flags int flags) {
return getActivities(context, requestCode, intents, flags, null);
}
@@ -393,7 +419,7 @@ public final class PendingIntent implements Parcelable {
* supplied.
*/
public static PendingIntent getActivities(Context context, int requestCode,
- Intent[] intents, int flags, Bundle options) {
+ @NonNull Intent[] intents, @Flags int flags, @Nullable Bundle options) {
String packageName = context.getPackageName();
String[] resolvedTypes = new String[intents.length];
for (int i=0; i<intents.length; i++) {
@@ -419,7 +445,7 @@ public final class PendingIntent implements Parcelable {
* activity is started, not when the pending intent is created.
*/
public static PendingIntent getActivitiesAsUser(Context context, int requestCode,
- Intent[] intents, int flags, Bundle options, UserHandle user) {
+ @NonNull Intent[] intents, int flags, Bundle options, UserHandle user) {
String packageName = context.getPackageName();
String[] resolvedTypes = new String[intents.length];
for (int i=0; i<intents.length; i++) {
@@ -463,7 +489,7 @@ public final class PendingIntent implements Parcelable {
* supplied.
*/
public static PendingIntent getBroadcast(Context context, int requestCode,
- Intent intent, int flags) {
+ Intent intent, @Flags int flags) {
return getBroadcastAsUser(context, requestCode, intent, flags,
new UserHandle(UserHandle.myUserId()));
}
@@ -517,7 +543,7 @@ public final class PendingIntent implements Parcelable {
* supplied.
*/
public static PendingIntent getService(Context context, int requestCode,
- Intent intent, int flags) {
+ @NonNull Intent intent, @Flags int flags) {
String packageName = context.getPackageName();
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
@@ -747,6 +773,7 @@ public final class PendingIntent implements Parcelable {
* @return The package name of the PendingIntent, or null if there is
* none associated with it.
*/
+ @Nullable
public String getCreatorPackage() {
try {
return ActivityManagerNative.getDefault()
@@ -805,6 +832,7 @@ public final class PendingIntent implements Parcelable {
* @return The user handle of the PendingIntent, or null if there is
* none associated with it.
*/
+ @Nullable
public UserHandle getCreatorUserHandle() {
try {
int uid = ActivityManagerNative.getDefault()
@@ -920,8 +948,8 @@ public final class PendingIntent implements Parcelable {
* @param sender The PendingIntent to write, or null.
* @param out Where to write the PendingIntent.
*/
- public static void writePendingIntentOrNullToParcel(PendingIntent sender,
- Parcel out) {
+ public static void writePendingIntentOrNullToParcel(@Nullable PendingIntent sender,
+ @NonNull Parcel out) {
out.writeStrongBinder(sender != null ? sender.mTarget.asBinder()
: null);
}
@@ -936,7 +964,8 @@ public final class PendingIntent implements Parcelable {
* @return Returns the Messenger read from the Parcel, or null if null had
* been written.
*/
- public static PendingIntent readPendingIntentOrNullFromParcel(Parcel in) {
+ @Nullable
+ public static PendingIntent readPendingIntentOrNullFromParcel(@NonNull Parcel in) {
IBinder b = in.readStrongBinder();
return b != null ? new PendingIntent(b) : null;
}
diff --git a/core/java/android/app/ResultInfo.java b/core/java/android/app/ResultInfo.java
index 48a0fc2..5e0867c 100644
--- a/core/java/android/app/ResultInfo.java
+++ b/core/java/android/app/ResultInfo.java
@@ -17,12 +17,8 @@
package android.app;
import android.content.Intent;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.Bundle;
-
-import java.util.Map;
/**
* {@hide}
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index f9c245e..33c3409 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -22,7 +22,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.Rect;
@@ -34,7 +33,6 @@ import android.os.ServiceManager;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
-import android.util.Slog;
import android.view.KeyEvent;
import java.util.List;
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 86fd7b9..dd882ce 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -42,7 +42,6 @@ import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
import libcore.io.ErrnoException;
import libcore.io.IoUtils;
diff --git a/core/java/android/app/TaskStackBuilder.java b/core/java/android/app/TaskStackBuilder.java
index 3e0ac7e..0077db1 100644
--- a/core/java/android/app/TaskStackBuilder.java
+++ b/core/java/android/app/TaskStackBuilder.java
@@ -16,6 +16,7 @@
package android.app;
+import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -244,7 +245,7 @@ public class TaskStackBuilder {
*
* @return The obtained PendingIntent
*/
- public PendingIntent getPendingIntent(int requestCode, int flags) {
+ public PendingIntent getPendingIntent(int requestCode, @PendingIntent.Flags int flags) {
return getPendingIntent(requestCode, flags, null);
}
@@ -263,7 +264,8 @@ public class TaskStackBuilder {
*
* @return The obtained PendingIntent
*/
- public PendingIntent getPendingIntent(int requestCode, int flags, Bundle options) {
+ public PendingIntent getPendingIntent(int requestCode, @PendingIntent.Flags int flags,
+ Bundle options) {
if (mIntents.isEmpty()) {
throw new IllegalStateException(
"No intents added to TaskStackBuilder; cannot getPendingIntent");
@@ -294,6 +296,7 @@ public class TaskStackBuilder {
*
* @return An array containing the intents added to this builder.
*/
+ @NonNull
public Intent[] getIntents() {
Intent[] intents = new Intent[mIntents.size()];
if (intents.length == 0) return intents;
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index 952227f..a85c61f 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -16,17 +16,19 @@
package android.app;
-import com.android.internal.R;
-
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
+import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TimePicker;
import android.widget.TimePicker.OnTimeChangedListener;
+import com.android.internal.R;
+
+
/**
* A dialog that prompts the user for the time of day using a {@link TimePicker}.
*
@@ -38,7 +40,7 @@ public class TimePickerDialog extends AlertDialog
/**
* The callback interface used to indicate the user is done filling in
- * the time (they clicked on the 'Set' button).
+ * the time (they clicked on the 'Done' button).
*/
public interface OnTimeSetListener {
@@ -55,7 +57,7 @@ public class TimePickerDialog extends AlertDialog
private static final String IS_24_HOUR = "is24hour";
private final TimePicker mTimePicker;
- private final OnTimeSetListener mCallback;
+ private final OnTimeSetListener mTimeSetCallback;
int mInitialHourOfDay;
int mInitialMinute;
@@ -74,6 +76,16 @@ public class TimePickerDialog extends AlertDialog
this(context, 0, callBack, hourOfDay, minute, is24HourView);
}
+ static int resolveDialogTheme(Context context, int resid) {
+ if (resid == 0) {
+ TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.timePickerDialogTheme, outValue, true);
+ return outValue.resourceId;
+ } else {
+ return resid;
+ }
+ }
+
/**
* @param context Parent.
* @param theme the theme to apply to this dialog
@@ -86,17 +98,13 @@ public class TimePickerDialog extends AlertDialog
int theme,
OnTimeSetListener callBack,
int hourOfDay, int minute, boolean is24HourView) {
- super(context, theme);
- mCallback = callBack;
+ super(context, resolveDialogTheme(context, theme));
+ mTimeSetCallback = callBack;
mInitialHourOfDay = hourOfDay;
mInitialMinute = minute;
mIs24HourView = is24HourView;
- setIcon(0);
- setTitle(R.string.time_picker_dialog_title);
-
Context themeContext = getContext();
- setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_done), this);
LayoutInflater inflater =
(LayoutInflater) themeContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -104,7 +112,18 @@ public class TimePickerDialog extends AlertDialog
setView(view);
mTimePicker = (TimePicker) view.findViewById(R.id.timePicker);
- // initialize state
+ // Initialize state
+ mTimePicker.setLegacyMode(false /* will show new UI */);
+ mTimePicker.setShowDoneButton(true);
+ mTimePicker.setDismissCallback(new TimePicker.TimePickerDismissCallback() {
+ @Override
+ public void dismiss(TimePicker view, boolean isCancel, int hourOfDay, int minute) {
+ if (!isCancel) {
+ mTimeSetCallback.onTimeSet(view, hourOfDay, minute);
+ }
+ TimePickerDialog.this.dismiss();
+ }
+ });
mTimePicker.setIs24HourView(mIs24HourView);
mTimePicker.setCurrentHour(mInitialHourOfDay);
mTimePicker.setCurrentMinute(mInitialMinute);
@@ -125,9 +144,9 @@ public class TimePickerDialog extends AlertDialog
}
private void tryNotifyTimeSet() {
- if (mCallback != null) {
+ if (mTimeSetCallback != null) {
mTimePicker.clearFocus();
- mCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
+ mTimeSetCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
mTimePicker.getCurrentMinute());
}
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index ced72f8..4435032 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -41,7 +41,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -221,24 +220,9 @@ public class WallpaperManager {
private static final int MSG_CLEAR_WALLPAPER = 1;
- private final Handler mHandler;
-
Globals(Looper looper) {
IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
mService = IWallpaperManager.Stub.asInterface(b);
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_CLEAR_WALLPAPER:
- synchronized (this) {
- mWallpaper = null;
- mDefaultWallpaper = null;
- }
- break;
- }
- }
- };
}
public void onWallpaperChanged() {
@@ -247,7 +231,10 @@ public class WallpaperManager {
* to null so if the user requests the wallpaper again then we'll
* fetch it.
*/
- mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER);
+ synchronized (this) {
+ mWallpaper = null;
+ mDefaultWallpaper = null;
+ }
}
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
@@ -280,7 +267,6 @@ public class WallpaperManager {
synchronized (this) {
mWallpaper = null;
mDefaultWallpaper = null;
- mHandler.removeMessages(MSG_CLEAR_WALLPAPER);
}
}
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index cb0737e..cfd0a65 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -16,9 +16,6 @@
package android.app.backup;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.os.ParcelFileDescriptor;
import android.util.Log;
diff --git a/core/java/android/app/backup/SharedPreferencesBackupHelper.java b/core/java/android/app/backup/SharedPreferencesBackupHelper.java
index 213bd31..939616b 100644
--- a/core/java/android/app/backup/SharedPreferencesBackupHelper.java
+++ b/core/java/android/app/backup/SharedPreferencesBackupHelper.java
@@ -18,7 +18,6 @@ package android.app.backup;
import android.app.QueuedWork;
import android.content.Context;
-import android.content.SharedPreferences;
import android.os.ParcelFileDescriptor;
import android.util.Log;
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index f104d71..84d3835 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -19,7 +19,6 @@ package android.appwidget;
import java.util.ArrayList;
import java.util.HashMap;
-import android.app.ActivityThread;
import android.content.Context;
import android.os.Binder;
import android.os.Handler;
@@ -31,7 +30,6 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.util.TypedValue;
import android.widget.RemoteViews;
import android.widget.RemoteViews.OnClickHandler;
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index d1c7bec..8a89cbc 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -16,7 +16,6 @@
package android.appwidget;
-import android.app.ActivityManagerNative;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index e2bc80a..7ee2313 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -19,9 +19,7 @@ package android.bluetooth;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
-import android.os.Binder;
import android.os.IBinder;
-import android.os.Message;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -34,10 +32,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
-import java.util.Random;
import java.util.Set;
import java.util.UUID;
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index d789a94..1bd698d 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -19,12 +19,10 @@ package android.bluetooth;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ParcelUuid;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.Log;
import java.io.IOException;
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index a2bb78c..b6e1bb3 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -18,18 +18,9 @@ package android.bluetooth;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothProfile.ServiceListener;
-import android.bluetooth.IBluetoothManager;
-import android.bluetooth.IBluetoothStateChangeCallback;
-
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.Log;
import java.util.ArrayList;
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index f0ecbb4..a86677c 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -16,7 +16,6 @@
package android.bluetooth;
import java.util.ArrayList;
-import java.util.IllegalFormatConversionException;
import java.util.List;
import java.util.UUID;
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 58ee54f..09072f9 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -19,18 +19,9 @@ package android.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothProfile.ServiceListener;
-import android.bluetooth.IBluetoothManager;
-import android.bluetooth.IBluetoothStateChangeCallback;
-
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.Log;
import java.util.ArrayList;
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
index f9f1d97..fc3ffe8 100644
--- a/core/java/android/bluetooth/BluetoothGattServerCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -18,8 +18,6 @@ package android.bluetooth;
import android.bluetooth.BluetoothDevice;
-import android.util.Log;
-
/**
* This abstract class is used to implement {@link BluetoothGattServer} callbacks.
*/
diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java
index 2e950fa..daf3bad 100644
--- a/core/java/android/bluetooth/BluetoothHealth.java
+++ b/core/java/android/bluetooth/BluetoothHealth.java
@@ -23,7 +23,6 @@ import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.Log;
import java.util.ArrayList;
diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java
index 844f432..c4ba5b1 100644
--- a/core/java/android/bluetooth/BluetoothInputDevice.java
+++ b/core/java/android/bluetooth/BluetoothInputDevice.java
@@ -24,7 +24,6 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.Log;
import java.util.ArrayList;
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 92a2f1e..5a1b7aa 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -24,7 +24,6 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.RemoteException;
import android.os.IBinder;
-import android.os.ServiceManager;
import android.util.Log;
/**
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index b7a37f4..e72832c 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -24,7 +24,6 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.Log;
import java.util.ArrayList;
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index 7f45652..8522ee0 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -22,7 +22,6 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.RemoteException;
import android.os.IBinder;
-import android.os.ServiceManager;
import android.util.Log;
/**
diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java
index 96be8a2..bc56e55 100644
--- a/core/java/android/bluetooth/BluetoothServerSocket.java
+++ b/core/java/android/bluetooth/BluetoothServerSocket.java
@@ -17,7 +17,6 @@
package android.bluetooth;
import android.os.Handler;
-import android.os.Message;
import android.os.ParcelUuid;
import java.io.Closeable;
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index d10eaea..ddefc70 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -16,21 +16,16 @@
package android.bluetooth;
-import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.Log;
import java.io.Closeable;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.List;
import java.util.Locale;
import java.util.UUID;
import android.net.LocalSocket;
diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
index a9b7176..6dd551e 100644
--- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
+++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
@@ -17,9 +17,6 @@
package android.bluetooth;
import android.net.BaseNetworkStateTracker;
-import android.os.IBinder;
-import android.os.ServiceManager;
-import android.os.INetworkManagementService;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.DhcpResults;
@@ -35,11 +32,6 @@ import android.os.Message;
import android.os.Messenger;
import android.text.TextUtils;
import android.util.Log;
-import java.net.InterfaceAddress;
-import android.net.LinkAddress;
-import android.net.RouteInfo;
-import java.net.Inet4Address;
-import android.os.SystemProperties;
import com.android.internal.util.AsyncChannel;
@@ -143,11 +135,6 @@ public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker {
}
@Override
- public void captivePortalCheckComplete() {
- // not implemented
- }
-
- @Override
public void captivePortalCheckCompleted(boolean isCaptivePortal) {
// not implemented
}
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index eb7426e..7241e0d 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -20,7 +20,7 @@ import android.os.AsyncTask;
import android.os.Handler;
import android.os.OperationCanceledException;
import android.os.SystemClock;
-import android.util.Slog;
+import android.util.Log;
import android.util.TimeUtils;
import java.io.FileDescriptor;
@@ -64,10 +64,10 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
/* Runs on a worker thread */
@Override
protected D doInBackground(Void... params) {
- if (DEBUG) Slog.v(TAG, this + " >>> doInBackground");
+ if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
try {
D data = AsyncTaskLoader.this.onLoadInBackground();
- if (DEBUG) Slog.v(TAG, this + " <<< doInBackground");
+ if (DEBUG) Log.v(TAG, this + " <<< doInBackground");
return data;
} catch (OperationCanceledException ex) {
if (!isCancelled()) {
@@ -79,7 +79,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
// So we treat this case as an unhandled exception.
throw ex;
}
- if (DEBUG) Slog.v(TAG, this + " <<< doInBackground (was canceled)", ex);
+ if (DEBUG) Log.v(TAG, this + " <<< doInBackground (was canceled)", ex);
return null;
}
}
@@ -87,7 +87,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
/* Runs on the UI thread */
@Override
protected void onPostExecute(D data) {
- if (DEBUG) Slog.v(TAG, this + " onPostExecute");
+ if (DEBUG) Log.v(TAG, this + " onPostExecute");
try {
AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
} finally {
@@ -98,7 +98,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
/* Runs on the UI thread */
@Override
protected void onCancelled(D data) {
- if (DEBUG) Slog.v(TAG, this + " onCancelled");
+ if (DEBUG) Log.v(TAG, this + " onCancelled");
try {
AsyncTaskLoader.this.dispatchOnCancelled(this, data);
} finally {
@@ -162,18 +162,18 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
super.onForceLoad();
cancelLoad();
mTask = new LoadTask();
- if (DEBUG) Slog.v(TAG, "Preparing load: mTask=" + mTask);
+ if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
executePendingTask();
}
@Override
protected boolean onCancelLoad() {
- if (DEBUG) Slog.v(TAG, "onCancelLoad: mTask=" + mTask);
+ if (DEBUG) Log.v(TAG, "onCancelLoad: mTask=" + mTask);
if (mTask != null) {
if (mCancellingTask != null) {
// There was a pending task already waiting for a previous
// one being canceled; just drop it.
- if (DEBUG) Slog.v(TAG,
+ if (DEBUG) Log.v(TAG,
"cancelLoad: still waiting for cancelled task; dropping next");
if (mTask.waiting) {
mTask.waiting = false;
@@ -184,14 +184,14 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
} else if (mTask.waiting) {
// There is a task, but it is waiting for the time it should
// execute. We can just toss it.
- if (DEBUG) Slog.v(TAG, "cancelLoad: task is waiting, dropping it");
+ if (DEBUG) Log.v(TAG, "cancelLoad: task is waiting, dropping it");
mTask.waiting = false;
mHandler.removeCallbacks(mTask);
mTask = null;
return false;
} else {
boolean cancelled = mTask.cancel(false);
- if (DEBUG) Slog.v(TAG, "cancelLoad: cancelled=" + cancelled);
+ if (DEBUG) Log.v(TAG, "cancelLoad: cancelled=" + cancelled);
if (cancelled) {
mCancellingTask = mTask;
cancelLoadInBackground();
@@ -223,7 +223,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
long now = SystemClock.uptimeMillis();
if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
// Not yet time to do another load.
- if (DEBUG) Slog.v(TAG, "Waiting until "
+ if (DEBUG) Log.v(TAG, "Waiting until "
+ (mLastLoadCompleteTime+mUpdateThrottle)
+ " to execute: " + mTask);
mTask.waiting = true;
@@ -231,7 +231,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
return;
}
}
- if (DEBUG) Slog.v(TAG, "Executing: " + mTask);
+ if (DEBUG) Log.v(TAG, "Executing: " + mTask);
mTask.executeOnExecutor(mExecutor, (Void[]) null);
}
}
@@ -239,11 +239,11 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
void dispatchOnCancelled(LoadTask task, D data) {
onCanceled(data);
if (mCancellingTask == task) {
- if (DEBUG) Slog.v(TAG, "Cancelled task is now canceled!");
+ if (DEBUG) Log.v(TAG, "Cancelled task is now canceled!");
rollbackContentChanged();
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mCancellingTask = null;
- if (DEBUG) Slog.v(TAG, "Delivering cancellation");
+ if (DEBUG) Log.v(TAG, "Delivering cancellation");
deliverCancellation();
executePendingTask();
}
@@ -251,7 +251,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
void dispatchOnLoadComplete(LoadTask task, D data) {
if (mTask != task) {
- if (DEBUG) Slog.v(TAG, "Load complete of old task, trying to cancel");
+ if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
dispatchOnCancelled(task, data);
} else {
if (isAbandoned()) {
@@ -261,7 +261,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
commitContentChanged();
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mTask = null;
- if (DEBUG) Slog.v(TAG, "Delivering result");
+ if (DEBUG) Log.v(TAG, "Delivering result");
deliverResult(data);
}
}
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 73e6fd0..5653cad 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -22,8 +22,6 @@ import android.os.RemoteException;
import android.os.Handler;
import android.os.IBinder;
import android.os.ServiceManager;
-import android.os.StrictMode;
-import android.util.Log;
import java.util.ArrayList;
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 4e6cc92..018e4c5 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -141,7 +141,7 @@ public abstract class ContentResolver {
public static final String SYNC_EXTRAS_PRIORITY = "sync_priority";
/** {@hide} Flag to allow sync to occur on metered network. */
- public static final String SYNC_EXTRAS_DISALLOW_METERED = "disallow_metered";
+ public static final String SYNC_EXTRAS_DISALLOW_METERED = "allow_metered";
/**
* Set by the SyncManager to request that the SyncAdapter initialize itself for
@@ -368,9 +368,7 @@ public abstract class ContentResolver {
}
/**
- * <p>
* Query the given URI, returning a {@link Cursor} over the result set.
- * </p>
* <p>
* For best performance, the caller should follow these guidelines:
* <ul>
@@ -405,9 +403,8 @@ public abstract class ContentResolver {
}
/**
- * <p>
- * Query the given URI, returning a {@link Cursor} over the result set.
- * </p>
+ * Query the given URI, returning a {@link Cursor} over the result set
+ * with optional support for cancellation.
* <p>
* For best performance, the caller should follow these guidelines:
* <ul>
@@ -1751,7 +1748,7 @@ public abstract class ContentResolver {
new SyncRequest.Builder()
.setSyncAdapter(account, authority)
.setExtras(extras)
- .syncOnce()
+ .syncOnce() // Immediate sync.
.build();
requestSync(request);
}
@@ -1759,9 +1756,6 @@ public abstract class ContentResolver {
/**
* Register a sync with the SyncManager. These requests are built using the
* {@link SyncRequest.Builder}.
- *
- * @param request The immutable SyncRequest object containing the sync parameters. Use
- * {@link SyncRequest.Builder} to construct these.
*/
public static void requestSync(SyncRequest request) {
try {
@@ -1829,12 +1823,25 @@ public abstract class ContentResolver {
*/
public static void cancelSync(Account account, String authority) {
try {
- getContentService().cancelSync(account, authority);
+ getContentService().cancelSync(account, authority, null);
} catch (RemoteException e) {
}
}
/**
+ * Cancel any active or pending syncs that are running on this service.
+ *
+ * @param cname the service for which to cancel all active/pending operations.
+ */
+ public static void cancelSync(ComponentName cname) {
+ try {
+ getContentService().cancelSync(null, null, cname);
+ } catch (RemoteException e) {
+
+ }
+ }
+
+ /**
* Get information about the SyncAdapters that are known to the system.
* @return an array of SyncAdapters that have registered with the system
*/
@@ -1897,12 +1904,13 @@ public abstract class ContentResolver {
* {@link #SYNC_EXTRAS_INITIALIZE}, {@link #SYNC_EXTRAS_FORCE},
* {@link #SYNC_EXTRAS_EXPEDITED}, {@link #SYNC_EXTRAS_MANUAL} set to true.
* If any are supplied then an {@link IllegalArgumentException} will be thrown.
- * <p>As of API level 19 this function introduces a default flexibility of ~4% (up to a maximum
- * of one hour in the day) into the requested period. Use
- * {@link SyncRequest.Builder#syncPeriodic(long, long)} to set this flexibility manually.
*
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
+ * <p>The bundle for a periodic sync can be queried by applications with the correct
+ * permissions using
+ * {@link ContentResolver#getPeriodicSyncs(Account account, String provider)}, so no
+ * sensitive data should be transferred here.
*
* @param account the account to specify in the sync
* @param authority the provider to specify in the sync request
@@ -1932,6 +1940,26 @@ public abstract class ContentResolver {
}
/**
+ * {@hide}
+ * Helper function to throw an <code>IllegalArgumentException</code> if any illegal
+ * extras were set for a periodic sync.
+ *
+ * @param extras bundle to validate.
+ */
+ public static boolean invalidPeriodicExtras(Bundle extras) {
+ if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)
+ || extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)
+ || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)
+ || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
+ || extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
+ || extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false)
+ || extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Remove a periodic sync. Has no affect if account, authority and extras don't match
* an existing periodic sync.
* <p>This method requires the caller to hold the permission
@@ -1951,6 +1979,31 @@ public abstract class ContentResolver {
}
/**
+ * Remove the specified sync. This will cancel any pending or active syncs. If the request is
+ * for a periodic sync, this call will remove any future occurrences.
+ * <p>If a periodic sync is specified, the caller must hold the permission
+ * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}. If this SyncRequest targets a
+ * SyncService adapter,the calling application must be signed with the same certificate as the
+ * adapter.
+ *</p>It is possible to cancel a sync using a SyncRequest object that is not the same object
+ * with which you requested the sync. Do so by building a SyncRequest with the same
+ * service/adapter, frequency, <b>and</b> extras bundle.
+ *
+ * @param request SyncRequest object containing information about sync to cancel.
+ */
+ public static void cancelSync(SyncRequest request) {
+ if (request == null) {
+ throw new IllegalArgumentException("request cannot be null");
+ }
+ try {
+ getContentService().cancelRequest(request);
+ } catch (RemoteException e) {
+ // exception ignored; if this is thrown then it means the runtime is in the midst of
+ // being restarted
+ }
+ }
+
+ /**
* Get the list of information about the periodic syncs for the given account and authority.
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#READ_SYNC_SETTINGS}.
@@ -1961,7 +2014,23 @@ public abstract class ContentResolver {
*/
public static List<PeriodicSync> getPeriodicSyncs(Account account, String authority) {
try {
- return getContentService().getPeriodicSyncs(account, authority);
+ return getContentService().getPeriodicSyncs(account, authority, null);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * Return periodic syncs associated with the provided component.
+ * <p>The calling application must be signed with the same certificate as the target component,
+ * otherwise this call will fail.
+ */
+ public static List<PeriodicSync> getPeriodicSyncs(ComponentName cname) {
+ if (cname == null) {
+ throw new IllegalArgumentException("Component must not be null");
+ }
+ try {
+ return getContentService().getPeriodicSyncs(null, null, cname);
} catch (RemoteException e) {
throw new RuntimeException("the ContentService should always be reachable", e);
}
@@ -1997,6 +2066,38 @@ public abstract class ContentResolver {
}
/**
+ * Set whether the provided {@link SyncService} is available to process work.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
+ * <p>The calling application must be signed with the same certificate as the target component,
+ * otherwise this call will fail.
+ */
+ public static void setServiceActive(ComponentName cname, boolean active) {
+ try {
+ getContentService().setServiceActive(cname, active);
+ } catch (RemoteException e) {
+ // exception ignored; if this is thrown then it means the runtime is in the midst of
+ // being restarted
+ }
+ }
+
+ /**
+ * Query the state of this sync service.
+ * <p>Set with {@link #setServiceActive(ComponentName cname, boolean active)}.
+ * <p>The calling application must be signed with the same certificate as the target component,
+ * otherwise this call will fail.
+ * @param cname ComponentName referring to a {@link SyncService}
+ * @return true if jobs will be run on this service, false otherwise.
+ */
+ public static boolean isServiceActive(ComponentName cname) {
+ try {
+ return getContentService().isServiceActive(cname);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
* Gets the master auto-sync setting that applies to all the providers and accounts.
* If this is false then the per-provider auto-sync setting is ignored.
* <p>This method requires the caller to hold the permission
@@ -2030,8 +2131,8 @@ public abstract class ContentResolver {
}
/**
- * Returns true if there is currently a sync operation for the given
- * account or authority in the pending list, or actively being processed.
+ * Returns true if there is currently a sync operation for the given account or authority
+ * actively being processed.
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#READ_SYNC_STATS}.
* @param account the account whose setting we are querying
@@ -2039,8 +2140,26 @@ public abstract class ContentResolver {
* @return true if a sync is active for the given account or authority.
*/
public static boolean isSyncActive(Account account, String authority) {
+ if (account == null) {
+ throw new IllegalArgumentException("account must not be null");
+ }
+ if (authority == null) {
+ throw new IllegalArgumentException("authority must not be null");
+ }
+
+ try {
+ return getContentService().isSyncActive(account, authority, null);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ public static boolean isSyncActive(ComponentName cname) {
+ if (cname == null) {
+ throw new IllegalArgumentException("component name must not be null");
+ }
try {
- return getContentService().isSyncActive(account, authority);
+ return getContentService().isSyncActive(null, null, cname);
} catch (RemoteException e) {
throw new RuntimeException("the ContentService should always be reachable", e);
}
@@ -2098,7 +2217,7 @@ public abstract class ContentResolver {
*/
public static SyncStatusInfo getSyncStatus(Account account, String authority) {
try {
- return getContentService().getSyncStatus(account, authority);
+ return getContentService().getSyncStatus(account, authority, null);
} catch (RemoteException e) {
throw new RuntimeException("the ContentService should always be reachable", e);
}
@@ -2114,7 +2233,15 @@ public abstract class ContentResolver {
*/
public static boolean isSyncPending(Account account, String authority) {
try {
- return getContentService().isSyncPending(account, authority);
+ return getContentService().isSyncPending(account, authority, null);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ public static boolean isSyncPending(ComponentName cname) {
+ try {
+ return getContentService().isSyncPending(null, null, cname);
} catch (RemoteException e) {
throw new RuntimeException("the ContentService should always be reachable", e);
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2e4e209..32b95c2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -16,6 +16,10 @@
package android.content;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
@@ -47,6 +51,8 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Interface to global information about an application environment. This is
@@ -132,6 +138,20 @@ public abstract class Context {
*/
public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 0x0008;
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ BIND_AUTO_CREATE,
+ BIND_AUTO_CREATE,
+ BIND_DEBUG_UNBIND,
+ BIND_NOT_FOREGROUND,
+ BIND_ABOVE_CLIENT,
+ BIND_ALLOW_OOM_MANAGEMENT,
+ BIND_WAIVE_PRIORITY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BindServiceFlags {}
+
/**
* Flag for {@link #bindService}: automatically create the service as long
* as the binding exists. Note that while this will create the service,
@@ -495,7 +515,7 @@ public abstract class Context {
* and {@link #MODE_WORLD_WRITEABLE} to control permissions. The bit
* {@link #MODE_MULTI_PROCESS} can also be used if multiple processes
* are mutating the same SharedPreferences file. {@link #MODE_MULTI_PROCESS}
- * is always on in apps targetting Gingerbread (Android 2.3) and below, and
+ * is always on in apps targeting Gingerbread (Android 2.3) and below, and
* off by default in later versions.
*
* @return The single {@link SharedPreferences} instance that can be used
@@ -674,7 +694,8 @@ public abstract class Context {
* @see #getFilesDir
* @see android.os.Environment#getExternalStoragePublicDirectory
*/
- public abstract File getExternalFilesDir(String type);
+ @Nullable
+ public abstract File getExternalFilesDir(@Nullable String type);
/**
* Returns absolute paths to application-specific directories on all
@@ -840,6 +861,7 @@ public abstract class Context {
*
* @see #getCacheDir
*/
+ @Nullable
public abstract File getExternalCacheDir();
/**
@@ -960,7 +982,8 @@ public abstract class Context {
* @see #deleteDatabase
*/
public abstract SQLiteDatabase openOrCreateDatabase(String name,
- int mode, CursorFactory factory, DatabaseErrorHandler errorHandler);
+ int mode, CursorFactory factory,
+ @Nullable DatabaseErrorHandler errorHandler);
/**
* Delete an existing private SQLiteDatabase associated with this Context's
@@ -1106,7 +1129,7 @@ public abstract class Context {
* @see #startActivity(Intent)
* @see PackageManager#resolveActivity
*/
- public abstract void startActivity(Intent intent, Bundle options);
+ public abstract void startActivity(Intent intent, @Nullable Bundle options);
/**
* Version of {@link #startActivity(Intent, Bundle)} that allows you to specify the
@@ -1122,7 +1145,7 @@ public abstract class Context {
* @throws ActivityNotFoundException &nbsp;
* @hide
*/
- public void startActivityAsUser(Intent intent, Bundle options, UserHandle userId) {
+ public void startActivityAsUser(Intent intent, @Nullable Bundle options, UserHandle userId) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
@@ -1241,7 +1264,7 @@ public abstract class Context {
* @see #startIntentSender(IntentSender, Intent, int, int, int)
*/
public abstract void startIntentSender(IntentSender intent,
- Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
Bundle options) throws IntentSender.SendIntentException;
/**
@@ -1291,11 +1314,11 @@ public abstract class Context {
* @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
*/
public abstract void sendBroadcast(Intent intent,
- String receiverPermission);
+ @Nullable String receiverPermission);
/**
* Like {@link #sendBroadcast(Intent, String)}, but also allows specification
- * of an assocated app op as per {@link android.app.AppOpsManager}.
+ * of an associated app op as per {@link android.app.AppOpsManager}.
* @hide
*/
public abstract void sendBroadcast(Intent intent,
@@ -1322,7 +1345,7 @@ public abstract class Context {
* @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
*/
public abstract void sendOrderedBroadcast(Intent intent,
- String receiverPermission);
+ @Nullable String receiverPermission);
/**
* Version of {@link #sendBroadcast(Intent)} that allows you to
@@ -1366,15 +1389,15 @@ public abstract class Context {
* @see #registerReceiver
* @see android.app.Activity#RESULT_OK
*/
- public abstract void sendOrderedBroadcast(Intent intent,
- String receiverPermission, BroadcastReceiver resultReceiver,
- Handler scheduler, int initialCode, String initialData,
- Bundle initialExtras);
+ public abstract void sendOrderedBroadcast(@NonNull Intent intent,
+ @Nullable String receiverPermission, BroadcastReceiver resultReceiver,
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras);
/**
* Like {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler,
* int, String, android.os.Bundle)}, but also allows specification
- * of an assocated app op as per {@link android.app.AppOpsManager}.
+ * of an associated app op as per {@link android.app.AppOpsManager}.
* @hide
*/
public abstract void sendOrderedBroadcast(Intent intent,
@@ -1409,7 +1432,7 @@ public abstract class Context {
* @see #sendBroadcast(Intent, String)
*/
public abstract void sendBroadcastAsUser(Intent intent, UserHandle user,
- String receiverPermission);
+ @Nullable String receiverPermission);
/**
* Version of
@@ -1442,8 +1465,9 @@ public abstract class Context {
* @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
*/
public abstract void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
- String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
- int initialCode, String initialData, Bundle initialExtras);
+ @Nullable String receiverPermission, BroadcastReceiver resultReceiver,
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras);
/**
* Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the
@@ -1508,8 +1532,8 @@ public abstract class Context {
*/
public abstract void sendStickyOrderedBroadcast(Intent intent,
BroadcastReceiver resultReceiver,
- Handler scheduler, int initialCode, String initialData,
- Bundle initialExtras);
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras);
/**
* Remove the data previously sent with {@link #sendStickyBroadcast},
@@ -1569,8 +1593,8 @@ public abstract class Context {
*/
public abstract void sendStickyOrderedBroadcastAsUser(Intent intent,
UserHandle user, BroadcastReceiver resultReceiver,
- Handler scheduler, int initialCode, String initialData,
- Bundle initialExtras);
+ @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+ @Nullable Bundle initialExtras);
/**
* Version of {@link #removeStickyBroadcast(Intent)} that allows you to specify the
@@ -1637,7 +1661,8 @@ public abstract class Context {
* @see #sendBroadcast
* @see #unregisterReceiver
*/
- public abstract Intent registerReceiver(BroadcastReceiver receiver,
+ @Nullable
+ public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,
IntentFilter filter);
/**
@@ -1671,8 +1696,10 @@ public abstract class Context {
* @see #sendBroadcast
* @see #unregisterReceiver
*/
+ @Nullable
public abstract Intent registerReceiver(BroadcastReceiver receiver,
- IntentFilter filter, String broadcastPermission, Handler scheduler);
+ IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler);
/**
* @hide
@@ -1698,9 +1725,10 @@ public abstract class Context {
* @see #sendBroadcast
* @see #unregisterReceiver
*/
+ @Nullable
public abstract Intent registerReceiverAsUser(BroadcastReceiver receiver,
- UserHandle user, IntentFilter filter, String broadcastPermission,
- Handler scheduler);
+ UserHandle user, IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler);
/**
* Unregister a previously registered BroadcastReceiver. <em>All</em>
@@ -1759,6 +1787,7 @@ public abstract class Context {
* @see #stopService
* @see #bindService
*/
+ @Nullable
public abstract ComponentName startService(Intent service);
/**
@@ -1846,8 +1875,8 @@ public abstract class Context {
* @see #BIND_DEBUG_UNBIND
* @see #BIND_NOT_FOREGROUND
*/
- public abstract boolean bindService(Intent service, ServiceConnection conn,
- int flags);
+ public abstract boolean bindService(Intent service, @NonNull ServiceConnection conn,
+ @BindServiceFlags int flags);
/**
* Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
@@ -1868,7 +1897,7 @@ public abstract class Context {
*
* @see #bindService
*/
- public abstract void unbindService(ServiceConnection conn);
+ public abstract void unbindService(@NonNull ServiceConnection conn);
/**
* Start executing an {@link android.app.Instrumentation} class. The given
@@ -1893,8 +1922,64 @@ public abstract class Context {
* @return {@code true} if the instrumentation was successfully started,
* else {@code false} if it could not be found.
*/
- public abstract boolean startInstrumentation(ComponentName className,
- String profileFile, Bundle arguments);
+ public abstract boolean startInstrumentation(@NonNull ComponentName className,
+ @Nullable String profileFile, @Nullable Bundle arguments);
+
+ /** @hide */
+ @StringDef({
+ POWER_SERVICE,
+ WINDOW_SERVICE,
+ LAYOUT_INFLATER_SERVICE,
+ ACCOUNT_SERVICE,
+ ACTIVITY_SERVICE,
+ ALARM_SERVICE,
+ NOTIFICATION_SERVICE,
+ ACCESSIBILITY_SERVICE,
+ CAPTIONING_SERVICE,
+ KEYGUARD_SERVICE,
+ LOCATION_SERVICE,
+ //@hide: COUNTRY_DETECTOR,
+ SEARCH_SERVICE,
+ SENSOR_SERVICE,
+ STORAGE_SERVICE,
+ WALLPAPER_SERVICE,
+ VIBRATOR_SERVICE,
+ //@hide: STATUS_BAR_SERVICE,
+ CONNECTIVITY_SERVICE,
+ //@hide: UPDATE_LOCK_SERVICE,
+ //@hide: NETWORKMANAGEMENT_SERVICE,
+ //@hide: NETWORK_STATS_SERVICE,
+ //@hide: NETWORK_POLICY_SERVICE,
+ WIFI_SERVICE,
+ WIFI_P2P_SERVICE,
+ NSD_SERVICE,
+ AUDIO_SERVICE,
+ MEDIA_ROUTER_SERVICE,
+ TELEPHONY_SERVICE,
+ CLIPBOARD_SERVICE,
+ INPUT_METHOD_SERVICE,
+ TEXT_SERVICES_MANAGER_SERVICE,
+ //@hide: APPWIDGET_SERVICE,
+ //@hide: BACKUP_SERVICE,
+ DROPBOX_SERVICE,
+ DEVICE_POLICY_SERVICE,
+ UI_MODE_SERVICE,
+ DOWNLOAD_SERVICE,
+ NFC_SERVICE,
+ BLUETOOTH_SERVICE,
+ //@hide: SIP_SERVICE,
+ USB_SERVICE,
+ //@hide: SERIAL_SERVICE,
+ INPUT_SERVICE,
+ DISPLAY_SERVICE,
+ //@hide: SCHEDULING_POLICY_SERVICE,
+ USER_SERVICE,
+ //@hide: APP_OPS_SERVICE
+ CAMERA_SERVICE,
+ PRINT_SERVICE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ServiceName {}
/**
* Return the handle to a system-level service by name. The class of the
@@ -1995,7 +2080,7 @@ public abstract class Context {
* @see #DOWNLOAD_SERVICE
* @see android.app.DownloadManager
*/
- public abstract Object getSystemService(String name);
+ public abstract Object getSystemService(@ServiceName @NonNull String name);
/**
* Use with {@link #getSystemService} to retrieve a
@@ -2431,7 +2516,6 @@ public abstract class Context {
*
* @see #getSystemService
* @see android.hardware.camera2.CameraManager
- * @hide
*/
public static final String CAMERA_SERVICE = "camera";
@@ -2470,7 +2554,8 @@ public abstract class Context {
* @see PackageManager#checkPermission(String, String)
* @see #checkCallingPermission
*/
- public abstract int checkPermission(String permission, int pid, int uid);
+ @PackageManager.PermissionResult
+ public abstract int checkPermission(@NonNull String permission, int pid, int uid);
/**
* Determine whether the calling process of an IPC you are handling has been
@@ -2493,7 +2578,8 @@ public abstract class Context {
* @see #checkPermission
* @see #checkCallingOrSelfPermission
*/
- public abstract int checkCallingPermission(String permission);
+ @PackageManager.PermissionResult
+ public abstract int checkCallingPermission(@NonNull String permission);
/**
* Determine whether the calling process of an IPC <em>or you</em> have been
@@ -2511,7 +2597,8 @@ public abstract class Context {
* @see #checkPermission
* @see #checkCallingPermission
*/
- public abstract int checkCallingOrSelfPermission(String permission);
+ @PackageManager.PermissionResult
+ public abstract int checkCallingOrSelfPermission(@NonNull String permission);
/**
* If the given permission is not allowed for a particular process
@@ -2526,7 +2613,7 @@ public abstract class Context {
* @see #checkPermission(String, int, int)
*/
public abstract void enforcePermission(
- String permission, int pid, int uid, String message);
+ @NonNull String permission, int pid, int uid, @Nullable String message);
/**
* If the calling process of an IPC you are handling has not been
@@ -2547,7 +2634,7 @@ public abstract class Context {
* @see #checkCallingPermission(String)
*/
public abstract void enforceCallingPermission(
- String permission, String message);
+ @NonNull String permission, @Nullable String message);
/**
* If neither you nor the calling process of an IPC you are
@@ -2563,7 +2650,7 @@ public abstract class Context {
* @see #checkCallingOrSelfPermission(String)
*/
public abstract void enforceCallingOrSelfPermission(
- String permission, String message);
+ @NonNull String permission, @Nullable String message);
/**
* Grant permission to access a specific Uri to another package, regardless
@@ -2599,7 +2686,7 @@ public abstract class Context {
* @see #revokeUriPermission
*/
public abstract void grantUriPermission(String toPackage, Uri uri,
- int modeFlags);
+ @Intent.GrantUriMode int modeFlags);
/**
* Remove all permissions to access a particular content provider Uri
@@ -2618,7 +2705,7 @@ public abstract class Context {
*
* @see #grantUriPermission
*/
- public abstract void revokeUriPermission(Uri uri, int modeFlags);
+ public abstract void revokeUriPermission(Uri uri, @Intent.GrantUriMode int modeFlags);
/**
* Determine whether a particular process and user ID has been granted
@@ -2641,7 +2728,8 @@ public abstract class Context {
*
* @see #checkCallingUriPermission
*/
- public abstract int checkUriPermission(Uri uri, int pid, int uid, int modeFlags);
+ public abstract int checkUriPermission(Uri uri, int pid, int uid,
+ @Intent.GrantUriMode int modeFlags);
/**
* Determine whether the calling process and user ID has been
@@ -2664,7 +2752,7 @@ public abstract class Context {
*
* @see #checkUriPermission(Uri, int, int, int)
*/
- public abstract int checkCallingUriPermission(Uri uri, int modeFlags);
+ public abstract int checkCallingUriPermission(Uri uri, @Intent.GrantUriMode int modeFlags);
/**
* Determine whether the calling process of an IPC <em>or you</em> has been granted
@@ -2683,7 +2771,8 @@ public abstract class Context {
*
* @see #checkCallingUriPermission
*/
- public abstract int checkCallingOrSelfUriPermission(Uri uri, int modeFlags);
+ public abstract int checkCallingOrSelfUriPermission(Uri uri,
+ @Intent.GrantUriMode int modeFlags);
/**
* Check both a Uri and normal permission. This allows you to perform
@@ -2695,7 +2784,7 @@ public abstract class Context {
* @param readPermission The permission that provides overall read access,
* or null to not do this check.
* @param writePermission The permission that provides overall write
- * acess, or null to not do this check.
+ * access, or null to not do this check.
* @param pid The process ID being checked against. Must be &gt; 0.
* @param uid The user ID being checked against. A uid of 0 is the root
* user, which will pass every permission check.
@@ -2707,8 +2796,9 @@ public abstract class Context {
* is allowed to access that uri or holds one of the given permissions, or
* {@link PackageManager#PERMISSION_DENIED} if it is not.
*/
- public abstract int checkUriPermission(Uri uri, String readPermission,
- String writePermission, int pid, int uid, int modeFlags);
+ public abstract int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission,
+ @Nullable String writePermission, int pid, int uid,
+ @Intent.GrantUriMode int modeFlags);
/**
* If a particular process and user ID has not been granted
@@ -2730,7 +2820,7 @@ public abstract class Context {
* @see #checkUriPermission(Uri, int, int, int)
*/
public abstract void enforceUriPermission(
- Uri uri, int pid, int uid, int modeFlags, String message);
+ Uri uri, int pid, int uid, @Intent.GrantUriMode int modeFlags, String message);
/**
* If the calling process and user ID has not been granted
@@ -2752,7 +2842,7 @@ public abstract class Context {
* @see #checkCallingUriPermission(Uri, int)
*/
public abstract void enforceCallingUriPermission(
- Uri uri, int modeFlags, String message);
+ Uri uri, @Intent.GrantUriMode int modeFlags, String message);
/**
* If the calling process of an IPC <em>or you</em> has not been
@@ -2771,7 +2861,7 @@ public abstract class Context {
* @see #checkCallingOrSelfUriPermission(Uri, int)
*/
public abstract void enforceCallingOrSelfUriPermission(
- Uri uri, int modeFlags, String message);
+ Uri uri, @Intent.GrantUriMode int modeFlags, String message);
/**
* Enforce both a Uri and normal permission. This allows you to perform
@@ -2783,7 +2873,7 @@ public abstract class Context {
* @param readPermission The permission that provides overall read access,
* or null to not do this check.
* @param writePermission The permission that provides overall write
- * acess, or null to not do this check.
+ * access, or null to not do this check.
* @param pid The process ID being checked against. Must be &gt; 0.
* @param uid The user ID being checked against. A uid of 0 is the root
* user, which will pass every permission check.
@@ -2795,8 +2885,15 @@ public abstract class Context {
* @see #checkUriPermission(Uri, String, String, int, int, int)
*/
public abstract void enforceUriPermission(
- Uri uri, String readPermission, String writePermission,
- int pid, int uid, int modeFlags, String message);
+ @Nullable Uri uri, @Nullable String readPermission,
+ @Nullable String writePermission, int pid, int uid, @Intent.GrantUriMode int modeFlags,
+ @Nullable String message);
+
+ /** @hide */
+ @IntDef(flag = true,
+ value = {CONTEXT_INCLUDE_CODE, CONTEXT_IGNORE_SECURITY, CONTEXT_RESTRICTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CreatePackageOptions {}
/**
* Flag for use with {@link #createPackageContext}: include the application
@@ -2854,7 +2951,7 @@ public abstract class Context {
* the given package name.
*/
public abstract Context createPackageContext(String packageName,
- int flags) throws PackageManager.NameNotFoundException;
+ @CreatePackageOptions int flags) throws PackageManager.NameNotFoundException;
/**
* Similar to {@link #createPackageContext(String, int)}, but with a
@@ -2890,7 +2987,8 @@ public abstract class Context {
*
* @return A {@link Context} with the given configuration override.
*/
- public abstract Context createConfigurationContext(Configuration overrideConfiguration);
+ public abstract Context createConfigurationContext(
+ @NonNull Configuration overrideConfiguration);
/**
* Return a new Context object for the current Context but whose resources
@@ -2910,7 +3008,7 @@ public abstract class Context {
*
* @return A {@link Context} for the display.
*/
- public abstract Context createDisplayContext(Display display);
+ public abstract Context createDisplayContext(@NonNull Display display);
/**
* Gets the display adjustments holder for this context. This information
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index a708dad..93f6cdf 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -16,9 +16,6 @@
package android.content;
-import android.app.Activity;
-import android.app.ActivityManagerNative;
-import android.app.LoadedApk;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
@@ -33,7 +30,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.view.DisplayAdjustments;
import android.view.Display;
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 5d7d677..c78871c 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -16,7 +16,6 @@
package android.content;
-import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.CancellationSignal;
diff --git a/core/java/android/content/Entity.java b/core/java/android/content/Entity.java
index 7842de0..607cb3f 100644
--- a/core/java/android/content/Entity.java
+++ b/core/java/android/content/Entity.java
@@ -16,10 +16,7 @@
package android.content;
-import android.os.Parcelable;
-import android.os.Parcel;
import android.net.Uri;
-import android.util.Log;
import java.util.ArrayList;
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index 9ad5a19..73a76e8 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -17,6 +17,7 @@
package android.content;
import android.accounts.Account;
+import android.content.ComponentName;
import android.content.SyncInfo;
import android.content.ISyncStatusObserver;
import android.content.SyncAdapterType;
@@ -55,8 +56,14 @@ interface IContentService {
int userHandle);
void requestSync(in Account account, String authority, in Bundle extras);
+ /**
+ * Start a sync given a request.
+ */
void sync(in SyncRequest request);
- void cancelSync(in Account account, String authority);
+ void cancelSync(in Account account, String authority, in ComponentName cname);
+
+ /** Cancel a sync, providing information about the sync to be cancelled. */
+ void cancelRequest(in SyncRequest request);
/**
* Check if the provider should be synced when a network tickle is received
@@ -74,12 +81,14 @@ interface IContentService {
void setSyncAutomatically(in Account account, String providerName, boolean sync);
/**
- * Get the frequency of the periodic poll, if any.
- * @param providerName the provider whose setting we are querying
- * @return the frequency of the periodic sync in seconds. If 0 then no periodic syncs
- * will take place.
+ * Get a list of periodic operations for a specified authority, or service.
+ * @param account account for authority, must be null if cname is non-null.
+ * @param providerName name of provider, must be null if cname is non-null.
+ * @param cname component to identify sync service, must be null if account/providerName are
+ * non-null.
*/
- List<PeriodicSync> getPeriodicSyncs(in Account account, String providerName);
+ List<PeriodicSync> getPeriodicSyncs(in Account account, String providerName,
+ in ComponentName cname);
/**
* Set whether or not the provider is to be synced on a periodic basis.
@@ -112,15 +121,22 @@ interface IContentService {
*/
void setIsSyncable(in Account account, String providerName, int syncable);
- void setMasterSyncAutomatically(boolean flag);
-
- boolean getMasterSyncAutomatically();
+ /**
+ * Corresponds roughly to setIsSyncable(String account, String provider) for syncs that bind
+ * to a SyncService.
+ */
+ void setServiceActive(in ComponentName cname, boolean active);
/**
- * Returns true if there is currently a sync operation for the given
- * account or authority in the pending list, or actively being processed.
+ * Corresponds roughly to getIsSyncable(String account, String provider) for syncs that bind
+ * to a SyncService.
+ * @return 0 if this SyncService is not enabled, 1 if enabled, <0 if unknown.
*/
- boolean isSyncActive(in Account account, String authority);
+ boolean isServiceActive(in ComponentName cname);
+
+ void setMasterSyncAutomatically(boolean flag);
+
+ boolean getMasterSyncAutomatically();
List<SyncInfo> getCurrentSyncs();
@@ -131,17 +147,33 @@ interface IContentService {
SyncAdapterType[] getSyncAdapterTypes();
/**
+ * Returns true if there is currently a operation for the given account/authority or service
+ * actively being processed.
+ * @param account account for authority, must be null if cname is non-null.
+ * @param providerName name of provider, must be null if cname is non-null.
+ * @param cname component to identify sync service, must be null if account/providerName are
+ * non-null.
+ */
+ boolean isSyncActive(in Account account, String authority, in ComponentName cname);
+
+ /**
* Returns the status that matches the authority. If there are multiples accounts for
* the authority, the one with the latest "lastSuccessTime" status is returned.
- * @param authority the authority whose row should be selected
- * @return the SyncStatusInfo for the authority, or null if none exists
+ * @param account account for authority, must be null if cname is non-null.
+ * @param providerName name of provider, must be null if cname is non-null.
+ * @param cname component to identify sync service, must be null if account/providerName are
+ * non-null.
*/
- SyncStatusInfo getSyncStatus(in Account account, String authority);
+ SyncStatusInfo getSyncStatus(in Account account, String authority, in ComponentName cname);
/**
* Return true if the pending status is true of any matching authorities.
+ * @param account account for authority, must be null if cname is non-null.
+ * @param providerName name of provider, must be null if cname is non-null.
+ * @param cname component to identify sync service, must be null if account/providerName are
+ * non-null.
*/
- boolean isSyncPending(in Account account, String authority);
+ boolean isSyncPending(in Account account, String authority, in ComponentName cname);
void addStatusChangeListener(int mask, ISyncStatusObserver callback);
diff --git a/core/java/android/content/IAnonymousSyncAdapter.aidl b/core/java/android/content/ISyncServiceAdapter.aidl
index a80cea3..d419307 100644
--- a/core/java/android/content/IAnonymousSyncAdapter.aidl
+++ b/core/java/android/content/ISyncServiceAdapter.aidl
@@ -24,7 +24,7 @@ import android.content.ISyncContext;
* Provider specified). See {@link android.content.AbstractThreadedSyncAdapter}.
* {@hide}
*/
-oneway interface IAnonymousSyncAdapter {
+oneway interface ISyncServiceAdapter {
/**
* Initiate a sync. SyncAdapter-specific parameters may be specified in
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index a289649..85b3141 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -21,6 +21,7 @@ import android.util.ArraySet;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.IntDef;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.pm.ActivityInfo;
@@ -45,6 +46,8 @@ import com.android.internal.util.XmlUtils;
import java.io.IOException;
import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
@@ -3335,6 +3338,12 @@ public class Intent implements Parcelable, Cloneable {
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
+ /** @hide */
+ @IntDef(flag = true,
+ value = {FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface GrantUriMode {}
+
/**
* If set, the recipient of this Intent will be granted permission to
* perform read operations on the URI in the Intent's data and any URIs
@@ -6374,6 +6383,21 @@ public class Intent implements Parcelable, Cloneable {
}
}
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ FILL_IN_ACTION,
+ FILL_IN_DATA,
+ FILL_IN_CATEGORIES,
+ FILL_IN_COMPONENT,
+ FILL_IN_PACKAGE,
+ FILL_IN_SOURCE_BOUNDS,
+ FILL_IN_SELECTOR,
+ FILL_IN_CLIP_DATA
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FillInFlags {}
+
/**
* Use with {@link #fillIn} to allow the current action value to be
* overwritten, even if it is already set.
@@ -6467,10 +6491,12 @@ public class Intent implements Parcelable, Cloneable {
*
* @return Returns a bit mask of {@link #FILL_IN_ACTION},
* {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE},
- * {@link #FILL_IN_COMPONENT}, {@link #FILL_IN_SOURCE_BOUNDS}, and
- * {@link #FILL_IN_SELECTOR} indicating which fields were changed.
+ * {@link #FILL_IN_COMPONENT}, {@link #FILL_IN_SOURCE_BOUNDS},
+ * {@link #FILL_IN_SELECTOR} and {@link #FILL_IN_CLIP_DATA indicating which fields were
+ * changed.
*/
- public int fillIn(Intent other, int flags) {
+ @FillInFlags
+ public int fillIn(Intent other, @FillInFlags int flags) {
int changes = 0;
if (other.mAction != null
&& (mAction == null || (flags&FILL_IN_ACTION) != 0)) {
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index 911e49c..f3828b0 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -413,7 +413,7 @@ public class Loader<D> {
* {@link #onReset()} happens. You can retrieve the current abandoned
* state with {@link #isAbandoned}.
*/
- protected void onAbandon() {
+ protected void onAbandon() {
}
/**
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
index b586eec..836c6f8 100644
--- a/core/java/android/content/PeriodicSync.java
+++ b/core/java/android/content/PeriodicSync.java
@@ -29,13 +29,17 @@ public class PeriodicSync implements Parcelable {
public final Account account;
/** The authority of the sync. Can be null. */
public final String authority;
+ /** The service for syncing, if this is an anonymous sync. Can be null.*/
+ public final ComponentName service;
/** Any extras that parameters that are to be passed to the sync adapter. */
public final Bundle extras;
/** How frequently the sync should be scheduled, in seconds. Kept around for API purposes. */
public final long period;
+ /** Whether this periodic sync runs on a {@link SyncService}. */
+ public final boolean isService;
/**
- * {@hide}
* How much flexibility can be taken in scheduling the sync, in seconds.
+ * {@hide}
*/
public final long flexTime;
@@ -48,44 +52,74 @@ public class PeriodicSync implements Parcelable {
public PeriodicSync(Account account, String authority, Bundle extras, long periodInSeconds) {
this.account = account;
this.authority = authority;
+ this.service = null;
+ this.isService = false;
if (extras == null) {
this.extras = new Bundle();
} else {
this.extras = new Bundle(extras);
}
this.period = periodInSeconds;
- // Initialise to a sane value.
+ // Old API uses default flex time. No-one should be using this ctor anyway.
this.flexTime = 0L;
}
/**
- * {@hide}
* Create a copy of a periodic sync.
+ * {@hide}
*/
public PeriodicSync(PeriodicSync other) {
this.account = other.account;
this.authority = other.authority;
+ this.service = other.service;
+ this.isService = other.isService;
this.extras = new Bundle(other.extras);
this.period = other.period;
this.flexTime = other.flexTime;
}
/**
- * {@hide}
* A PeriodicSync for a sync with a specified provider.
+ * {@hide}
*/
public PeriodicSync(Account account, String authority, Bundle extras,
long period, long flexTime) {
this.account = account;
this.authority = authority;
+ this.service = null;
+ this.isService = false;
+ this.extras = new Bundle(extras);
+ this.period = period;
+ this.flexTime = flexTime;
+ }
+
+ /**
+ * A PeriodicSync for a sync with a specified SyncService.
+ * {@hide}
+ */
+ public PeriodicSync(ComponentName service, Bundle extras,
+ long period,
+ long flexTime) {
+ this.account = null;
+ this.authority = null;
+ this.service = service;
+ this.isService = true;
this.extras = new Bundle(extras);
this.period = period;
this.flexTime = flexTime;
}
private PeriodicSync(Parcel in) {
- this.account = in.readParcelable(null);
- this.authority = in.readString();
+ this.isService = (in.readInt() != 0);
+ if (this.isService) {
+ this.service = in.readParcelable(null);
+ this.account = null;
+ this.authority = null;
+ } else {
+ this.account = in.readParcelable(null);
+ this.authority = in.readString();
+ this.service = null;
+ }
this.extras = in.readBundle();
this.period = in.readLong();
this.flexTime = in.readLong();
@@ -98,8 +132,13 @@ public class PeriodicSync implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(account, flags);
- dest.writeString(authority);
+ dest.writeInt(isService ? 1 : 0);
+ if (account == null && authority == null) {
+ dest.writeParcelable(service, flags);
+ } else {
+ dest.writeParcelable(account, flags);
+ dest.writeString(authority);
+ }
dest.writeBundle(extras);
dest.writeLong(period);
dest.writeLong(flexTime);
@@ -126,14 +165,24 @@ public class PeriodicSync implements Parcelable {
return false;
}
final PeriodicSync other = (PeriodicSync) o;
- return account.equals(other.account)
- && authority.equals(other.authority)
+ if (this.isService != other.isService) {
+ return false;
+ }
+ boolean equal = false;
+ if (this.isService) {
+ equal = service.equals(other.service);
+ } else {
+ equal = account.equals(other.account)
+ && authority.equals(other.authority);
+ }
+ return equal
&& period == other.period
&& syncExtrasEquals(extras, other.extras);
}
/**
- * Periodic sync extra comparison function.
+ * Periodic sync extra comparison function. Duplicated from
+ * {@link com.android.server.content.SyncManager#syncExtrasEquals(Bundle b1, Bundle b2)}
* {@hide}
*/
public static boolean syncExtrasEquals(Bundle b1, Bundle b2) {
@@ -158,6 +207,7 @@ public class PeriodicSync implements Parcelable {
public String toString() {
return "account: " + account +
", authority: " + authority +
+ ", service: " + service +
". period: " + period + "s " +
", flex: " + flexTime;
}
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
index 283a097..3ff53bf 100644
--- a/core/java/android/content/RestrictionEntry.java
+++ b/core/java/android/content/RestrictionEntry.java
@@ -19,8 +19,6 @@ package android.content;
import android.os.Parcel;
import android.os.Parcelable;
-import java.lang.annotation.Inherited;
-
/**
* Applications can expose restrictions for a restricted user on a
* multiuser device. The administrator can configure these restrictions that will then be
diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java
index cffc653..146dd99 100644
--- a/core/java/android/content/SyncInfo.java
+++ b/core/java/android/content/SyncInfo.java
@@ -19,7 +19,6 @@ package android.content;
import android.accounts.Account;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.Parcelable.Creator;
/**
* Information about the sync operation that is currently underway.
@@ -29,16 +28,24 @@ public class SyncInfo implements Parcelable {
public final int authorityId;
/**
- * The {@link Account} that is currently being synced.
+ * The {@link Account} that is currently being synced. Will be null if this sync is running via
+ * a {@link SyncService}.
*/
public final Account account;
/**
- * The authority of the provider that is currently being synced.
+ * The authority of the provider that is currently being synced. Will be null if this sync
+ * is running via a {@link SyncService}.
*/
public final String authority;
/**
+ * The {@link SyncService} that is targeted by this operation. Null if this sync is running via
+ * a {@link AbstractThreadedSyncAdapter}.
+ */
+ public final ComponentName service;
+
+ /**
* The start time of the current sync operation in milliseconds since boot.
* This is represented in elapsed real time.
* See {@link android.os.SystemClock#elapsedRealtime()}.
@@ -46,12 +53,13 @@ public class SyncInfo implements Parcelable {
public final long startTime;
/** @hide */
- public SyncInfo(int authorityId, Account account, String authority,
+ public SyncInfo(int authorityId, Account account, String authority, ComponentName service,
long startTime) {
this.authorityId = authorityId;
this.account = account;
this.authority = authority;
this.startTime = startTime;
+ this.service = service;
}
/** @hide */
@@ -60,6 +68,7 @@ public class SyncInfo implements Parcelable {
this.account = new Account(other.account.name, other.account.type);
this.authority = other.authority;
this.startTime = other.startTime;
+ this.service = other.service;
}
/** @hide */
@@ -70,17 +79,20 @@ public class SyncInfo implements Parcelable {
/** @hide */
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(authorityId);
- account.writeToParcel(parcel, 0);
+ parcel.writeParcelable(account, flags);
parcel.writeString(authority);
parcel.writeLong(startTime);
+ parcel.writeParcelable(service, flags);
+
}
/** @hide */
SyncInfo(Parcel parcel) {
authorityId = parcel.readInt();
- account = new Account(parcel);
+ account = parcel.readParcelable(Account.class.getClassLoader());
authority = parcel.readString();
startTime = parcel.readLong();
+ service = parcel.readParcelable(ComponentName.class.getClassLoader());
}
/** @hide */
diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java
index 6ca283d..a9a62a7 100644
--- a/core/java/android/content/SyncRequest.java
+++ b/core/java/android/content/SyncRequest.java
@@ -23,15 +23,15 @@ import android.os.Parcelable;
public class SyncRequest implements Parcelable {
private static final String TAG = "SyncRequest";
- /** Account to pass to the sync adapter. May be null. */
+ /** Account to pass to the sync adapter. Can be null. */
private final Account mAccountToSync;
/** Authority string that corresponds to a ContentProvider. */
private final String mAuthority;
- /** Sync service identifier. May be null.*/
+ /** {@link SyncService} identifier. */
private final ComponentName mComponentInfo;
/** Bundle containing user info as well as sync settings. */
private final Bundle mExtras;
- /** Disallow this sync request on metered networks. */
+ /** Don't allow this sync request on metered networks. */
private final boolean mDisallowMetered;
/**
* Anticipated upload size in bytes.
@@ -69,18 +69,14 @@ public class SyncRequest implements Parcelable {
return mIsPeriodic;
}
- /**
- * {@hide}
- * @return whether this is an expedited sync.
- */
public boolean isExpedited() {
return mIsExpedited;
}
/**
* {@hide}
- * @return true if this sync uses an account/authority pair, or false if this sync is bound to
- * a Sync Service.
+ * @return true if this sync uses an account/authority pair, or false if
+ * this is an anonymous sync bound to an @link AnonymousSyncService.
*/
public boolean hasAuthority() {
return mIsAuthority;
@@ -88,34 +84,51 @@ public class SyncRequest implements Parcelable {
/**
* {@hide}
+ *
* @return account object for this sync.
- * @throws IllegalArgumentException if this function is called for a request that does not
- * specify an account/provider authority.
+ * @throws IllegalArgumentException if this function is called for a request that targets a
+ * sync service.
*/
public Account getAccount() {
if (!hasAuthority()) {
- throw new IllegalArgumentException("Cannot getAccount() for a sync that does not"
- + "specify an authority.");
+ throw new IllegalArgumentException("Cannot getAccount() for a sync that targets a sync"
+ + "service.");
}
return mAccountToSync;
}
/**
* {@hide}
+ *
* @return provider for this sync.
- * @throws IllegalArgumentException if this function is called for a request that does not
- * specify an account/provider authority.
+ * @throws IllegalArgumentException if this function is called for a request that targets a
+ * sync service.
*/
public String getProvider() {
if (!hasAuthority()) {
- throw new IllegalArgumentException("Cannot getProvider() for a sync that does not"
- + "specify a provider.");
+ throw new IllegalArgumentException("Cannot getProvider() for a sync that targets a"
+ + "sync service.");
}
return mAuthority;
}
/**
* {@hide}
+ * Throws a runtime IllegalArgumentException if this function is called for a
+ * SyncRequest that is bound to an account/provider.
+ *
+ * @return ComponentName for the service that this sync will bind to.
+ */
+ public ComponentName getService() {
+ if (hasAuthority()) {
+ throw new IllegalArgumentException(
+ "Cannot getAnonymousService() for a sync that has specified a provider.");
+ }
+ return mComponentInfo;
+ }
+
+ /**
+ * {@hide}
* Retrieve bundle for this SyncRequest. Will not be null.
*/
public Bundle getBundle() {
@@ -129,7 +142,6 @@ public class SyncRequest implements Parcelable {
public long getSyncFlexTime() {
return mSyncFlexTimeSecs;
}
-
/**
* {@hide}
* @return the last point in time at which this sync must scheduled.
@@ -216,7 +228,7 @@ public class SyncRequest implements Parcelable {
}
/**
- * Builder class for a {@link SyncRequest}. As you build your SyncRequest this class will also
+ * Builder class for a @link SyncRequest. As you build your SyncRequest this class will also
* perform validation.
*/
public static class Builder {
@@ -232,9 +244,12 @@ public class SyncRequest implements Parcelable {
private static final int SYNC_TARGET_SERVICE = 1;
/** Specify that this is a sync with a provider. */
private static final int SYNC_TARGET_ADAPTER = 2;
- /** Earliest point of displacement into the future at which this sync can occur. */
+ /**
+ * Earliest point of displacement into the future at which this sync can
+ * occur.
+ */
private long mSyncFlexTimeSecs;
- /** Latest point of displacement into the future at which this sync must occur. */
+ /** Displacement into the future at which this sync must occur. */
private long mSyncRunTimeSecs;
/**
* Sync configuration information - custom user data explicitly provided by the developer.
@@ -283,8 +298,9 @@ public class SyncRequest implements Parcelable {
private boolean mExpedited;
/**
- * The sync component that contains the sync logic if this is a provider-less sync,
- * otherwise null.
+ * The {@link SyncService} component that
+ * contains the sync logic if this is a provider-less sync, otherwise
+ * null.
*/
private ComponentName mComponentName;
/**
@@ -320,11 +336,15 @@ public class SyncRequest implements Parcelable {
/**
* Build a periodic sync. Either this or syncOnce() <b>must</b> be called for this builder.
- * Syncs are identified by target {@link android.provider}/{@link android.accounts.Account}
- * and by the contents of the extras bundle.
- * You cannot reuse the same builder for one-time syncs (by calling this function) after
- * having specified a periodic sync. If you do, an <code>IllegalArgumentException</code>
+ * Syncs are identified by target {@link SyncService}/{@link android.provider} and by the
+ * contents of the extras bundle.
+ * You cannot reuse the same builder for one-time syncs after having specified a periodic
+ * sync (by calling this function). If you do, an <code>IllegalArgumentException</code>
* will be thrown.
+ * <p>The bundle for a periodic sync can be queried by applications with the correct
+ * permissions using
+ * {@link ContentResolver#getPeriodicSyncs(Account account, String provider)}, so no
+ * sensitive data should be transferred here.
*
* Example usage.
*
@@ -375,7 +395,6 @@ public class SyncRequest implements Parcelable {
}
/**
- * {@hide}
* Developer can provide insight into their payload size; optional. -1 specifies unknown,
* so that you are not restricted to defining both fields.
*
@@ -389,20 +408,28 @@ public class SyncRequest implements Parcelable {
}
/**
- * @see android.net.ConnectivityManager#isActiveNetworkMetered()
- * @param disallow true to enforce that this transfer not occur on metered networks.
- * Default false.
+ * Will throw an <code>IllegalArgumentException</code> if called and
+ * {@link #setIgnoreSettings(boolean ignoreSettings)} has already been called.
+ * @param disallow true to allow this transfer on metered networks. Default false.
+ *
*/
public Builder setDisallowMetered(boolean disallow) {
+ if (mIgnoreSettings && disallow) {
+ throw new IllegalArgumentException("setDisallowMetered(true) after having"
+ + "specified that settings are ignored.");
+ }
mDisallowMetered = disallow;
return this;
}
/**
- * Specify an authority and account for this transfer.
+ * Specify an authority and account for this transfer. Cannot be used with
+ * {@link #setSyncAdapter(ComponentName cname)}.
*
- * @param authority String identifying which content provider to sync.
- * @param account Account to sync. Can be null unless this is a periodic sync.
+ * @param authority
+ * @param account Account to sync. Can be null unless this is a periodic
+ * sync, for which verification by the ContentResolver will
+ * fail. If a sync is performed without an account, the
*/
public Builder setSyncAdapter(Account account, String authority) {
if (mSyncTarget != SYNC_TARGET_UNKNOWN) {
@@ -419,10 +446,26 @@ public class SyncRequest implements Parcelable {
}
/**
- * Optional developer-provided extras handed back in
- * {@link AbstractThreadedSyncAdapter#onPerformSync(Account, Bundle, String,
- * ContentProviderClient, SyncResult)} occurs. This bundle is copied into the SyncRequest
- * returned by {@link #build()}.
+ * Specify the {@link SyncService} component for this sync. This is not validated until
+ * sync time so providing an incorrect component name here will not fail. Cannot be used
+ * with {@link #setSyncAdapter(Account account, String authority)}.
+ *
+ * @param cname ComponentName to identify your Anonymous service
+ */
+ public Builder setSyncAdapter(ComponentName cname) {
+ if (mSyncTarget != SYNC_TARGET_UNKNOWN) {
+ throw new IllegalArgumentException("Sync target has already been defined.");
+ }
+ mSyncTarget = SYNC_TARGET_SERVICE;
+ mComponentName = cname;
+ mAccount = null;
+ mAuthority = null;
+ return this;
+ }
+
+ /**
+ * Developer-provided extras handed back when sync actually occurs. This bundle is copied
+ * into the SyncRequest returned by {@link #build()}.
*
* Example:
* <pre>
@@ -436,7 +479,7 @@ public class SyncRequest implements Parcelable {
* Bundle extras = new Bundle();
* extras.setString("data", syncData);
* builder.setExtras(extras);
- * ContentResolver.sync(builder.build()); // Each sync() request is for a unique sync.
+ * ContentResolver.sync(builder.build()); // Each sync() request creates a unique sync.
* }
* </pre>
* Only values of the following types may be used in the extras bundle:
@@ -477,13 +520,19 @@ public class SyncRequest implements Parcelable {
/**
* Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS}.
*
- * A sync can specify that system sync settings be ignored (user has turned sync off). Not
- * valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
+ * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
* {@link #build()}.
+ * <p>Throws <code>IllegalArgumentException</code> if called and
+ * {@link #setDisallowMetered(boolean)} has been set.
+ *
*
* @param ignoreSettings true to ignore the sync automatically settings. Default false.
*/
public Builder setIgnoreSettings(boolean ignoreSettings) {
+ if (mDisallowMetered && ignoreSettings) {
+ throw new IllegalArgumentException("setIgnoreSettings(true) after having specified"
+ + " sync settings with this builder.");
+ }
mIgnoreSettings = ignoreSettings;
return this;
}
@@ -491,13 +540,13 @@ public class SyncRequest implements Parcelable {
/**
* Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}.
*
- * Force the sync scheduling process to ignore any back-off that was the result of a failed
- * sync, as well as to invalidate any {@link SyncResult#delayUntil} value that may have
- * been set by the adapter. Successive failures will not honor this flag. Not valid for
- * periodic sync and will throw an <code>IllegalArgumentException</code> in
- * {@link #build()}.
+ * Ignoring back-off will force the sync scheduling process to ignore any back-off that was
+ * the result of a failed sync, as well as to invalidate any {@link SyncResult#delayUntil}
+ * value that may have been set by the adapter. Successive failures will not honor this
+ * flag. Not valid for periodic sync and will throw an <code>IllegalArgumentException</code>
+ * in {@link #build()}.
*
- * @param ignoreBackoff ignore back-off settings. Default false.
+ * @param ignoreBackoff ignore back off settings. Default false.
*/
public Builder setIgnoreBackoff(boolean ignoreBackoff) {
mIgnoreBackoff = ignoreBackoff;
@@ -507,9 +556,8 @@ public class SyncRequest implements Parcelable {
/**
* Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_MANUAL}.
*
- * A manual sync is functionally equivalent to calling {@link #setIgnoreBackoff(boolean)}
- * and {@link #setIgnoreSettings(boolean)}. Not valid for periodic sync and will throw an
- * <code>IllegalArgumentException</code> in {@link #build()}.
+ * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
+ * {@link #build()}.
*
* @param isManual User-initiated sync or not. Default false.
*/
@@ -519,7 +567,7 @@ public class SyncRequest implements Parcelable {
}
/**
- * An expedited sync runs immediately and will preempt another non-expedited running sync.
+ * An expedited sync runs immediately and can preempt other non-expedited running syncs.
*
* Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
* {@link #build()}.
@@ -532,7 +580,6 @@ public class SyncRequest implements Parcelable {
}
/**
- * {@hide}
* @param priority the priority of this request among all requests from the calling app.
* Range of [-2,2] similar to how this is done with notifications.
*/
@@ -552,11 +599,11 @@ public class SyncRequest implements Parcelable {
* builder.
*/
public SyncRequest build() {
+ // Validate the extras bundle
+ ContentResolver.validateSyncExtrasBundle(mCustomExtras);
if (mCustomExtras == null) {
mCustomExtras = new Bundle();
}
- // Validate the extras bundle
- ContentResolver.validateSyncExtrasBundle(mCustomExtras);
// Combine builder extra flags into the config bundle.
mSyncConfigExtras = new Bundle();
if (mIgnoreBackoff) {
@@ -575,51 +622,33 @@ public class SyncRequest implements Parcelable {
mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
}
if (mIsManual) {
- mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+ mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+ mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
}
mSyncConfigExtras.putLong(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD, mTxBytes);
mSyncConfigExtras.putLong(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD, mRxBytes);
mSyncConfigExtras.putInt(ContentResolver.SYNC_EXTRAS_PRIORITY, mPriority);
if (mSyncType == SYNC_TYPE_PERIODIC) {
// If this is a periodic sync ensure than invalid extras were not set.
- validatePeriodicExtras(mCustomExtras);
- validatePeriodicExtras(mSyncConfigExtras);
- // Verify that account and provider are not null.
- if (mAccount == null) {
- throw new IllegalArgumentException("Account must not be null for periodic"
- + " sync.");
- }
- if (mAuthority == null) {
- throw new IllegalArgumentException("Authority must not be null for periodic"
- + " sync.");
+ if (ContentResolver.invalidPeriodicExtras(mCustomExtras) ||
+ ContentResolver.invalidPeriodicExtras(mSyncConfigExtras)) {
+ throw new IllegalArgumentException("Illegal extras were set");
}
} else if (mSyncType == SYNC_TYPE_UNKNOWN) {
throw new IllegalArgumentException("Must call either syncOnce() or syncPeriodic()");
}
+ if (mSyncTarget == SYNC_TARGET_SERVICE) {
+ if (mSyncConfigExtras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
+ throw new IllegalArgumentException("Cannot specify an initialisation sync"
+ + " that targets a service.");
+ }
+ }
// Ensure that a target for the sync has been set.
if (mSyncTarget == SYNC_TARGET_UNKNOWN) {
- throw new IllegalArgumentException("Must specify an adapter with "
- + "setSyncAdapter(Account, String");
+ throw new IllegalArgumentException("Must specify an adapter with one of"
+ + "setSyncAdapter(ComponentName) or setSyncAdapter(Account, String");
}
return new SyncRequest(this);
}
-
- /**
- * Helper function to throw an <code>IllegalArgumentException</code> if any illegal
- * extras were set for a periodic sync.
- *
- * @param extras bundle to validate.
- */
- private void validatePeriodicExtras(Bundle extras) {
- if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)
- || extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)
- || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)
- || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
- || extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
- || extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false)
- || extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
- throw new IllegalArgumentException("Illegal extras were set");
- }
- }
- }
+ }
}
diff --git a/core/java/android/content/SyncService.java b/core/java/android/content/SyncService.java
new file mode 100644
index 0000000..4df998c
--- /dev/null
+++ b/core/java/android/content/SyncService.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.app.Service;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.Trace;
+import android.util.SparseArray;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Simplified @link android.content.AbstractThreadedSyncAdapter. Folds that
+ * behaviour into a service to which the system can bind when requesting an
+ * anonymous (providerless/accountless) sync.
+ * <p>
+ * In order to perform an anonymous sync operation you must extend this service, implementing the
+ * abstract methods. This service must be declared in the application's manifest as usual. You
+ * can use this service for other work, however you <b> must not </b> override the onBind() method
+ * unless you know what you're doing, which limits the usefulness of this service for other work.
+ * <p>A {@link SyncService} can either be active or inactive. Different to an
+ * {@link AbstractThreadedSyncAdapter}, there is no
+ * {@link ContentResolver#setSyncAutomatically(android.accounts.Account account, String provider, boolean sync)},
+ * as well as no concept of initialisation (you can handle your own if needed).
+ *
+ * <pre>
+ * &lt;service android:name=".MySyncService"/&gt;
+ * </pre>
+ * Like @link android.content.AbstractThreadedSyncAdapter this service supports
+ * multiple syncs at the same time. Each incoming startSync() with a unique tag
+ * will spawn a thread to do the work of that sync. If startSync() is called
+ * with a tag that already exists, a SyncResult.ALREADY_IN_PROGRESS is returned.
+ * Remember that your service will spawn multiple threads if you schedule multiple syncs
+ * at once, so if you mutate local objects you must ensure synchronization.
+ */
+public abstract class SyncService extends Service {
+ private static final String TAG = "SyncService";
+
+ private final SyncAdapterImpl mSyncAdapter = new SyncAdapterImpl();
+
+ /** Keep track of on-going syncs, keyed by bundle. */
+ @GuardedBy("mSyncThreadLock")
+ private final SparseArray<SyncThread>
+ mSyncThreads = new SparseArray<SyncThread>();
+ /** Lock object for accessing the SyncThreads HashMap. */
+ private final Object mSyncThreadLock = new Object();
+ /**
+ * Default key for if this sync service does not support parallel operations. Currently not
+ * sure if null keys will make it into the ArrayMap for KLP, so keeping our default for now.
+ */
+ private static final int KEY_DEFAULT = 0;
+ /** Identifier for this sync service. */
+ private ComponentName mServiceComponent;
+
+ /** {@hide} */
+ public IBinder onBind(Intent intent) {
+ mServiceComponent = new ComponentName(this, getClass());
+ return mSyncAdapter.asBinder();
+ }
+
+ /** {@hide} */
+ private class SyncAdapterImpl extends ISyncServiceAdapter.Stub {
+ @Override
+ public void startSync(ISyncContext syncContext, Bundle extras) {
+ // Wrap the provided Sync Context because it may go away by the time
+ // we call it.
+ final SyncContext syncContextClient = new SyncContext(syncContext);
+ boolean alreadyInProgress = false;
+ final int extrasAsKey = extrasToKey(extras);
+ synchronized (mSyncThreadLock) {
+ if (mSyncThreads.get(extrasAsKey) == null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "starting sync for : " + mServiceComponent);
+ }
+ // Start sync.
+ SyncThread syncThread = new SyncThread(syncContextClient, extras);
+ mSyncThreads.put(extrasAsKey, syncThread);
+ syncThread.start();
+ } else {
+ // Don't want to call back to SyncManager while still
+ // holding lock.
+ alreadyInProgress = true;
+ }
+ }
+ if (alreadyInProgress) {
+ syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
+ }
+ }
+
+ /**
+ * Used by the SM to cancel a specific sync using the
+ * com.android.server.content.SyncManager.ActiveSyncContext as a handle.
+ */
+ @Override
+ public void cancelSync(ISyncContext syncContext) {
+ SyncThread runningSync = null;
+ synchronized (mSyncThreadLock) {
+ for (int i = 0; i < mSyncThreads.size(); i++) {
+ SyncThread thread = mSyncThreads.valueAt(i);
+ if (thread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
+ runningSync = thread;
+ break;
+ }
+ }
+ }
+ if (runningSync != null) {
+ runningSync.interrupt();
+ }
+ }
+ }
+
+ /**
+ *
+ * @param extras Bundle for which to compute hash
+ * @return an integer hash that is equal to that of another bundle if they both contain the
+ * same key -> value mappings, however, not necessarily in order.
+ * Based on the toString() representation of the value mapped.
+ */
+ private int extrasToKey(Bundle extras) {
+ int hash = KEY_DEFAULT; // Empty bundle, or no parallel operations enabled.
+ if (parallelSyncsEnabled()) {
+ for (String key : extras.keySet()) {
+ String mapping = key + " " + extras.get(key).toString();
+ hash += mapping.hashCode();
+ }
+ }
+ return hash;
+ }
+
+ /**
+ * {@hide}
+ * Similar to {@link android.content.AbstractThreadedSyncAdapter.SyncThread}. However while
+ * the ATSA considers an already in-progress sync to be if the account provided is currently
+ * syncing, this anonymous sync has no notion of account and considers a sync unique if the
+ * provided bundle is different.
+ */
+ private class SyncThread extends Thread {
+ private final SyncContext mSyncContext;
+ private final Bundle mExtras;
+ private final int mThreadsKey;
+
+ public SyncThread(SyncContext syncContext, Bundle extras) {
+ mSyncContext = syncContext;
+ mExtras = extras;
+ mThreadsKey = extrasToKey(extras);
+ }
+
+ @Override
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+ Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, getApplication().getPackageName());
+
+ SyncResult syncResult = new SyncResult();
+ try {
+ if (isCancelled()) return;
+ // Run the sync.
+ SyncService.this.onPerformSync(mExtras, syncResult);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER);
+ if (!isCancelled()) {
+ mSyncContext.onFinished(syncResult);
+ }
+ // Synchronize so that the assignment will be seen by other
+ // threads that also synchronize accesses to mSyncThreads.
+ synchronized (mSyncThreadLock) {
+ mSyncThreads.remove(mThreadsKey);
+ }
+ }
+ }
+
+ private boolean isCancelled() {
+ return Thread.currentThread().isInterrupted();
+ }
+ }
+
+ /**
+ * Initiate an anonymous sync using this service. SyncAdapter-specific
+ * parameters may be specified in extras, which is guaranteed to not be
+ * null.
+ */
+ public abstract void onPerformSync(Bundle extras, SyncResult syncResult);
+
+ /**
+ * Override this function to indicated whether you want to support parallel syncs.
+ * <p>If you override and return true multiple threads will be spawned within your Service to
+ * handle each concurrent sync request.
+ *
+ * @return false to indicate that this service does not support parallel operations by default.
+ */
+ protected boolean parallelSyncsEnabled() {
+ return false;
+ }
+}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index b8ac3bf..40275d8 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -16,11 +16,15 @@
package android.content.pm;
+import android.annotation.IntDef;
import android.content.res.Configuration;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Printer;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Information you can retrieve about a particular application
* activity or receiver. This corresponds to information collected
@@ -212,6 +216,28 @@ public class ActivityInfo extends ComponentInfo
*/
public int flags;
+ /** @hide */
+ @IntDef({
+ SCREEN_ORIENTATION_UNSPECIFIED,
+ SCREEN_ORIENTATION_LANDSCAPE,
+ SCREEN_ORIENTATION_PORTRAIT,
+ SCREEN_ORIENTATION_USER,
+ SCREEN_ORIENTATION_BEHIND,
+ SCREEN_ORIENTATION_SENSOR,
+ SCREEN_ORIENTATION_NOSENSOR,
+ SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
+ SCREEN_ORIENTATION_SENSOR_PORTRAIT,
+ SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
+ SCREEN_ORIENTATION_REVERSE_PORTRAIT,
+ SCREEN_ORIENTATION_FULL_SENSOR,
+ SCREEN_ORIENTATION_USER_LANDSCAPE,
+ SCREEN_ORIENTATION_USER_PORTRAIT,
+ SCREEN_ORIENTATION_FULL_USER,
+ SCREEN_ORIENTATION_LOCKED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScreenOrientation {}
+
/**
* Constant corresponding to <code>unspecified</code> in
* the {@link android.R.attr#screenOrientation} attribute.
@@ -323,6 +349,7 @@ public class ActivityInfo extends ComponentInfo
* {@link #SCREEN_ORIENTATION_FULL_USER},
* {@link #SCREEN_ORIENTATION_LOCKED},
*/
+ @ScreenOrientation
public int screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
/**
diff --git a/core/java/android/content/pm/FeatureInfo.java b/core/java/android/content/pm/FeatureInfo.java
index 89394f9..d919fc3 100644
--- a/core/java/android/content/pm/FeatureInfo.java
+++ b/core/java/android/content/pm/FeatureInfo.java
@@ -18,7 +18,6 @@ package android.content.pm;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.Parcelable.Creator;
/**
* A single feature that can be requested by an application. This corresponds
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c97c2b8..0192a30 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.annotation.IntDef;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ComponentName;
@@ -33,6 +34,8 @@ import android.util.AndroidException;
import android.util.DisplayMetrics;
import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -190,6 +193,11 @@ public abstract class PackageManager {
*/
public static final int MATCH_DEFAULT_ONLY = 0x00010000;
+ /** @hide */
+ @IntDef({PERMISSION_GRANTED, PERMISSION_DENIED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PermissionResult {}
+
/**
* Permission check result: this is returned by {@link #checkPermission}
* if the permission has been granted to the given package.
diff --git a/core/java/android/content/pm/XmlSerializerAndParser.java b/core/java/android/content/pm/XmlSerializerAndParser.java
index 935fc02..20cb61c 100644
--- a/core/java/android/content/pm/XmlSerializerAndParser.java
+++ b/core/java/android/content/pm/XmlSerializerAndParser.java
@@ -19,7 +19,6 @@ package android.content.pm;
import org.xmlpull.v1.XmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import android.os.Parcel;
import java.io.IOException;
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index fc9e486..e53486d 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -17,7 +17,6 @@
package android.content.res;
import android.os.ParcelFileDescriptor;
-import android.os.Trace;
import android.util.Log;
import android.util.TypedValue;
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index bd23db4..431226a 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -16,6 +16,7 @@
package android.content.res;
+import android.graphics.Color;
import com.android.internal.util.ArrayUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -259,7 +260,17 @@ public class ColorStateList implements Parcelable {
public boolean isStateful() {
return mStateSpecs.length > 1;
}
-
+
+ public boolean isOpaque() {
+ final int n = mColors.length;
+ for (int i = 0; i < n; i++) {
+ if (Color.alpha(mColors[i]) != 0xFF) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* Return the color associated with the given set of {@link android.view.View} states.
*
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index cd5b5d2..eb41ee9 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -36,7 +36,6 @@ import android.util.Log;
import android.util.Slog;
import android.util.TypedValue;
import android.util.LongSparseArray;
-import android.view.DisplayAdjustments;
import java.io.IOException;
import java.io.InputStream;
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 83d48aa..4b96800 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -16,7 +16,6 @@
package android.content.res;
-import android.content.pm.ActivityInfo;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
index 82a61d4..7dcfae2 100644
--- a/core/java/android/database/CursorToBulkCursorAdaptor.java
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -20,7 +20,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Log;
/**
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 431eca2..2dd4800 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -18,7 +18,6 @@ package android.database.sqlite;
import android.content.Context;
import android.database.DatabaseErrorHandler;
-import android.database.DefaultDatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.util.Log;
diff --git a/core/java/android/ddm/DdmHandleNativeHeap.java b/core/java/android/ddm/DdmHandleNativeHeap.java
index 6bd65aa..775c570 100644
--- a/core/java/android/ddm/DdmHandleNativeHeap.java
+++ b/core/java/android/ddm/DdmHandleNativeHeap.java
@@ -20,7 +20,6 @@ import org.apache.harmony.dalvik.ddmc.Chunk;
import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
import android.util.Log;
-import java.nio.ByteBuffer;
/**
* Handle thread-related traffic.
diff --git a/core/java/android/ddm/DdmHandleProfiling.java b/core/java/android/ddm/DdmHandleProfiling.java
index 537763d..cce4dd2 100644
--- a/core/java/android/ddm/DdmHandleProfiling.java
+++ b/core/java/android/ddm/DdmHandleProfiling.java
@@ -21,7 +21,6 @@ import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
import android.os.Debug;
import android.util.Log;
-import java.io.IOException;
import java.nio.ByteBuffer;
/**
diff --git a/core/java/android/gesture/GestureOverlayView.java b/core/java/android/gesture/GestureOverlayView.java
index 2d47f28..6e3a00f 100644
--- a/core/java/android/gesture/GestureOverlayView.java
+++ b/core/java/android/gesture/GestureOverlayView.java
@@ -134,11 +134,16 @@ public class GestureOverlayView extends FrameLayout {
this(context, attrs, com.android.internal.R.attr.gestureOverlayViewStyle);
}
- public GestureOverlayView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public GestureOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public GestureOverlayView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.GestureOverlayView, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.GestureOverlayView, defStyleAttr, defStyleRes);
mGestureStrokeWidth = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth,
mGestureStrokeWidth);
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index feb47aa..9913e33 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -46,7 +46,6 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-import java.util.concurrent.locks.ReentrantLock;
/**
* The Camera class is used to set image capture settings, start/stop preview,
diff --git a/core/java/android/hardware/SerialManager.java b/core/java/android/hardware/SerialManager.java
index c5e1c2b..e0680bf 100644
--- a/core/java/android/hardware/SerialManager.java
+++ b/core/java/android/hardware/SerialManager.java
@@ -17,16 +17,12 @@
package android.hardware;
-import android.app.PendingIntent;
import android.content.Context;
-import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
-import android.os.SystemProperties;
import android.util.Log;
import java.io.IOException;
-import java.util.HashMap;
/**
* @hide
diff --git a/core/java/android/hardware/SerialPort.java b/core/java/android/hardware/SerialPort.java
index f50cdef..5d83d9c 100644
--- a/core/java/android/hardware/SerialPort.java
+++ b/core/java/android/hardware/SerialPort.java
@@ -17,14 +17,9 @@
package android.hardware;
import android.os.ParcelFileDescriptor;
-import android.util.Log;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
import java.io.IOException;
-import java.io.OutputStream;
import java.nio.ByteBuffer;
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 65b6c7a..2ac50e4 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -19,7 +19,6 @@ package android.hardware.camera2;
import android.content.Context;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceListener;
-import android.hardware.IProCameraUser;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 1d6ff7d..5d0bb33 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -168,7 +168,7 @@ public abstract class CameraMetadata {
Key lhs = (Key) o;
- return mName.equals(lhs.mName);
+ return mName.equals(lhs.mName) && mType.equals(lhs.mType);
}
/**
diff --git a/core/java/android/hardware/camera2/CaptureFailure.java b/core/java/android/hardware/camera2/CaptureFailure.java
index 3b408cf..35f9af1 100644
--- a/core/java/android/hardware/camera2/CaptureFailure.java
+++ b/core/java/android/hardware/camera2/CaptureFailure.java
@@ -15,8 +15,6 @@
*/
package android.hardware.camera2;
-import android.hardware.camera2.CameraDevice.CaptureListener;
-
/**
* A report of failed capture for a single image capture from the image sensor.
*
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 898f123..00b02fa 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -17,7 +17,6 @@
package android.hardware.camera2;
import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.CameraDevice.CaptureListener;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.Surface;
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 535b963..7224577 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -16,8 +16,6 @@
package android.hardware.camera2;
-import android.graphics.Point;
-import android.graphics.Rect;
import android.hardware.camera2.impl.CameraMetadataNative;
/**
@@ -784,6 +782,8 @@ public final class CaptureResult extends CameraMetadata {
* <p>
* Only available if faceDetectMode == FULL
* </p>
+ *
+ * @hide
*/
public static final Key<int[]> STATISTICS_FACE_IDS =
new Key<int[]>("android.statistics.faceIds", int[].class);
@@ -796,6 +796,8 @@ public final class CaptureResult extends CameraMetadata {
* <p>
* Only available if faceDetectMode == FULL
* </p>
+ *
+ * @hide
*/
public static final Key<int[]> STATISTICS_FACE_LANDMARKS =
new Key<int[]>("android.statistics.faceLandmarks", int[].class);
@@ -808,6 +810,8 @@ public final class CaptureResult extends CameraMetadata {
* <p>
* Only available if faceDetectMode != OFF
* </p>
+ *
+ * @hide
*/
public static final Key<android.graphics.Rect[]> STATISTICS_FACE_RECTANGLES =
new Key<android.graphics.Rect[]>("android.statistics.faceRectangles", android.graphics.Rect[].class);
@@ -821,6 +825,8 @@ public final class CaptureResult extends CameraMetadata {
* Only available if faceDetectMode != OFF. The value should be
* meaningful (for example, setting 100 at all times is illegal).
* </p>
+ *
+ * @hide
*/
public static final Key<byte[]> STATISTICS_FACE_SCORES =
new Key<byte[]>("android.statistics.faceScores", byte[].class);
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 072c5bb..2ddcb14 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -448,7 +448,7 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
} else if (key.equals(CaptureResult.STATISTICS_FACES)) {
return (T) getFaces();
} else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) {
- return (T) fixFaceRectangles();
+ return (T) getFaceRectangles();
}
// For other keys, get() falls back to getBase()
@@ -457,12 +457,15 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
private int[] getAvailableFormats() {
int[] availableFormats = getBase(CameraCharacteristics.SCALER_AVAILABLE_FORMATS);
- for (int i = 0; i < availableFormats.length; i++) {
- // JPEG has different value between native and managed side, need override.
- if (availableFormats[i] == NATIVE_JPEG_FORMAT) {
- availableFormats[i] = ImageFormat.JPEG;
+ if (availableFormats != null) {
+ for (int i = 0; i < availableFormats.length; i++) {
+ // JPEG has different value between native and managed side, need override.
+ if (availableFormats[i] == NATIVE_JPEG_FORMAT) {
+ availableFormats[i] = ImageFormat.JPEG;
+ }
}
}
+
return availableFormats;
}
@@ -550,7 +553,7 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
// (left, top, width, height) at the native level, so the normal Rect
// conversion that does (l, t, w, h) -> (l, t, r, b) is unnecessary. Undo
// that conversion here for just the faces.
- private Rect[] fixFaceRectangles() {
+ private Rect[] getFaceRectangles() {
Rect[] faceRectangles = getBase(CaptureResult.STATISTICS_FACE_RECTANGLES);
if (faceRectangles == null) return null;
@@ -590,6 +593,8 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
private <T> boolean setOverride(Key<T> key, T value) {
if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)) {
return setAvailableFormats((int[]) value);
+ } else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) {
+ return setFaceRectangles((Rect[]) value);
}
// For other keys, set() falls back to setBase().
@@ -615,6 +620,36 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
return true;
}
+ /**
+ * Convert Face Rectangles from managed side to native side as they have different definitions.
+ * <p>
+ * Managed side face rectangles are defined as: left, top, width, height.
+ * Native side face rectangles are defined as: left, top, right, bottom.
+ * The input face rectangle need to be converted to native side definition when set is called.
+ * </p>
+ *
+ * @param faceRects Input face rectangles.
+ * @return true if face rectangles can be set successfully. Otherwise, Let the caller
+ * (setBase) to handle it appropriately.
+ */
+ private boolean setFaceRectangles(Rect[] faceRects) {
+ if (faceRects == null) {
+ return false;
+ }
+
+ Rect[] newFaceRects = new Rect[faceRects.length];
+ for (int i = 0; i < newFaceRects.length; i++) {
+ newFaceRects[i] = new Rect(
+ faceRects[i].left,
+ faceRects[i].top,
+ faceRects[i].right + faceRects[i].left,
+ faceRects[i].bottom + faceRects[i].top);
+ }
+
+ setBase(CaptureResult.STATISTICS_FACE_RECTANGLES, newFaceRects);
+ return true;
+ }
+
private long mMetadataPtr; // native CameraMetadata*
private native long nativeAllocate();
diff --git a/core/java/android/hardware/camera2/package.html b/core/java/android/hardware/camera2/package.html
index c619984..9f6c2a9 100644
--- a/core/java/android/hardware/camera2/package.html
+++ b/core/java/android/hardware/camera2/package.html
@@ -80,7 +80,5 @@ output streams included in the request. These are produced
asynchronously relative to the output CaptureResult, sometimes
substantially later.</p>
-{@hide}
-
</BODY>
</HTML>
diff --git a/core/java/android/hardware/display/WifiDisplayStatus.java b/core/java/android/hardware/display/WifiDisplayStatus.java
index 5216727..b645662 100644
--- a/core/java/android/hardware/display/WifiDisplayStatus.java
+++ b/core/java/android/hardware/display/WifiDisplayStatus.java
@@ -20,8 +20,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.List;
/**
* Describes the current global state of Wifi display connectivity, including the
diff --git a/core/java/android/hardware/location/GeofenceHardwareRequest.java b/core/java/android/hardware/location/GeofenceHardwareRequest.java
index 6e7b592..796d7f8 100644
--- a/core/java/android/hardware/location/GeofenceHardwareRequest.java
+++ b/core/java/android/hardware/location/GeofenceHardwareRequest.java
@@ -16,8 +16,6 @@
package android.hardware.location;
-import android.location.Location;
-
/**
* This class represents the characteristics of the geofence.
*
diff --git a/core/java/android/hardware/usb/UsbAccessory.java b/core/java/android/hardware/usb/UsbAccessory.java
index 5719452..2f9178c 100644
--- a/core/java/android/hardware/usb/UsbAccessory.java
+++ b/core/java/android/hardware/usb/UsbAccessory.java
@@ -16,10 +16,8 @@
package android.hardware.usb;
-import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Log;
/**
* A class representing a USB accessory, which is an external hardware component
diff --git a/core/java/android/hardware/usb/UsbDevice.java b/core/java/android/hardware/usb/UsbDevice.java
index 9bd38f9..ae6118c 100644
--- a/core/java/android/hardware/usb/UsbDevice.java
+++ b/core/java/android/hardware/usb/UsbDevice.java
@@ -16,12 +16,8 @@
package android.hardware.usb;
-import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Log;
-
-import java.io.FileDescriptor;
/**
* This class represents a USB device attached to the android device with the android device
diff --git a/core/java/android/hardware/usb/UsbEndpoint.java b/core/java/android/hardware/usb/UsbEndpoint.java
index 753a447..708d651 100644
--- a/core/java/android/hardware/usb/UsbEndpoint.java
+++ b/core/java/android/hardware/usb/UsbEndpoint.java
@@ -16,7 +16,6 @@
package android.hardware.usb;
-import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/hardware/usb/UsbInterface.java b/core/java/android/hardware/usb/UsbInterface.java
index d6c54a8..e94baa1 100644
--- a/core/java/android/hardware/usb/UsbInterface.java
+++ b/core/java/android/hardware/usb/UsbInterface.java
@@ -16,7 +16,6 @@
package android.hardware.usb;
-import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/inputmethodservice/ExtractButton.java b/core/java/android/inputmethodservice/ExtractButton.java
index b6b7595..fe63c1e 100644
--- a/core/java/android/inputmethodservice/ExtractButton.java
+++ b/core/java/android/inputmethodservice/ExtractButton.java
@@ -32,8 +32,12 @@ class ExtractButton extends Button {
super(context, attrs, com.android.internal.R.attr.buttonStyle);
}
- public ExtractButton(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public ExtractButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ExtractButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
/**
diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
index 23ae21b..48b604c 100644
--- a/core/java/android/inputmethodservice/ExtractEditText.java
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -38,8 +38,12 @@ public class ExtractEditText extends EditText {
super(context, attrs, com.android.internal.R.attr.editTextStyle);
}
- public ExtractEditText(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public ExtractEditText(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ExtractEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
void setIME(InputMethodService ime) {
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index bbea8ff..8437228 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -25,7 +25,6 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.Looper;
import android.os.Message;
-import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
import android.view.InputChannel;
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 1b7d9ea..81ad28b 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -2322,6 +2322,21 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
+ * @return The recommended height of the input method window.
+ * An IME author can get the last input method's height as the recommended height
+ * by calling this in
+ * {@link android.inputmethodservice.InputMethodService#onStartInputView(EditorInfo, boolean)}.
+ * If you don't need to use a predefined fixed height, you can avoid the window-resizing of IME
+ * switching by using this value as a visible inset height. It's efficient for the smooth
+ * transition between different IMEs. However, note that this may return 0 (or possibly
+ * unexpectedly low height). You should thus avoid relying on the return value of this method
+ * all the time. Please make sure to use a reasonable height for the IME.
+ */
+ public int getInputMethodWindowRecommendedHeight() {
+ return mImm.getInputMethodWindowVisibleHeight();
+ }
+
+ /**
* Performs a dump of the InputMethodService's internal state. Override
* to add your own information to the dump.
*/
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 4916244..af75a0a 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -279,12 +279,15 @@ public class KeyboardView extends View implements View.OnClickListener {
this(context, attrs, com.android.internal.R.attr.keyboardViewStyle);
}
- public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public KeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public KeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a =
- context.obtainStyledAttributes(
- attrs, android.R.styleable.KeyboardView, defStyle, 0);
+ TypedArray a = context.obtainStyledAttributes(
+ attrs, android.R.styleable.KeyboardView, defStyleAttr, defStyleRes);
LayoutInflater inflate =
(LayoutInflater) context
diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java
index 476fefe..804f8ee 100644
--- a/core/java/android/net/BaseNetworkStateTracker.java
+++ b/core/java/android/net/BaseNetworkStateTracker.java
@@ -20,10 +20,10 @@ import android.content.Context;
import android.os.Handler;
import android.os.Messenger;
-import com.android.internal.util.Preconditions;
-
import java.util.concurrent.atomic.AtomicBoolean;
+import com.android.internal.util.Preconditions;
+
/**
* Interface to control and observe state of a specific network, hiding
* network-specific details from {@link ConnectivityManager}. Surfaces events
@@ -108,11 +108,6 @@ public abstract class BaseNetworkStateTracker implements NetworkStateTracker {
}
@Override
- public void captivePortalCheckComplete() {
- // not implemented
- }
-
- @Override
public void captivePortalCheckCompleted(boolean isCaptivePortal) {
// not implemented
}
diff --git a/core/java/android/net/CaptivePortalTracker.java b/core/java/android/net/CaptivePortalTracker.java
index d678f1e..5b6f154 100644
--- a/core/java/android/net/CaptivePortalTracker.java
+++ b/core/java/android/net/CaptivePortalTracker.java
@@ -48,7 +48,6 @@ import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Inet4Address;
-import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.List;
@@ -84,13 +83,12 @@ public class CaptivePortalTracker extends StateMachine {
private String mServer;
private String mUrl;
private boolean mIsCaptivePortalCheckEnabled = false;
- private IConnectivityManager mConnService;
- private TelephonyManager mTelephonyManager;
- private WifiManager mWifiManager;
- private Context mContext;
+ private final IConnectivityManager mConnService;
+ private final TelephonyManager mTelephonyManager;
+ private final WifiManager mWifiManager;
+ private final Context mContext;
private NetworkInfo mNetworkInfo;
- private static final int CMD_DETECT_PORTAL = 0;
private static final int CMD_CONNECTIVITY_CHANGE = 1;
private static final int CMD_DELAYED_CAPTIVE_CHECK = 2;
@@ -98,14 +96,15 @@ public class CaptivePortalTracker extends StateMachine {
private static final int DELAYED_CHECK_INTERVAL_MS = 10000;
private int mDelayedCheckToken = 0;
- private State mDefaultState = new DefaultState();
- private State mNoActiveNetworkState = new NoActiveNetworkState();
- private State mActiveNetworkState = new ActiveNetworkState();
- private State mDelayedCaptiveCheckState = new DelayedCaptiveCheckState();
+ private final State mDefaultState = new DefaultState();
+ private final State mNoActiveNetworkState = new NoActiveNetworkState();
+ private final State mActiveNetworkState = new ActiveNetworkState();
+ private final State mDelayedCaptiveCheckState = new DelayedCaptiveCheckState();
private static final String SETUP_WIZARD_PACKAGE = "com.google.android.setupwizard";
private boolean mDeviceProvisioned = false;
- private ProvisioningObserver mProvisioningObserver;
+ @SuppressWarnings("unused")
+ private final ProvisioningObserver mProvisioningObserver;
private CaptivePortalTracker(Context context, IConnectivityManager cs) {
super(TAG);
@@ -174,29 +173,11 @@ public class CaptivePortalTracker extends StateMachine {
return captivePortal;
}
- public void detectCaptivePortal(NetworkInfo info) {
- sendMessage(obtainMessage(CMD_DETECT_PORTAL, info));
- }
-
private class DefaultState extends State {
@Override
public boolean processMessage(Message message) {
- if (DBG) log(getName() + message.toString());
- switch (message.what) {
- case CMD_DETECT_PORTAL:
- NetworkInfo info = (NetworkInfo) message.obj;
- // Checking on a secondary connection is not supported
- // yet
- notifyPortalCheckComplete(info);
- break;
- case CMD_CONNECTIVITY_CHANGE:
- case CMD_DELAYED_CAPTIVE_CHECK:
- break;
- default:
- loge("Ignoring " + message);
- break;
- }
+ loge("Ignoring " + message);
return HANDLED;
}
}
@@ -316,19 +297,6 @@ public class CaptivePortalTracker extends StateMachine {
}
}
- private void notifyPortalCheckComplete(NetworkInfo info) {
- if (info == null) {
- loge("notifyPortalCheckComplete on null");
- return;
- }
- try {
- if (DBG) log("notifyPortalCheckComplete: ni=" + info);
- mConnService.captivePortalCheckComplete(info);
- } catch(RemoteException e) {
- e.printStackTrace();
- }
- }
-
private void notifyPortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
if (info == null) {
loge("notifyPortalCheckComplete on null");
@@ -464,7 +432,6 @@ public class CaptivePortalTracker extends StateMachine {
latencyBroadcast.putExtra(EXTRA_NETWORK_TYPE, mTelephonyManager.getNetworkType());
List<CellInfo> info = mTelephonyManager.getAllCellInfo();
if (info == null) return;
- StringBuffer uniqueCellId = new StringBuffer();
int numRegisteredCellInfo = 0;
for (CellInfo cellInfo : info) {
if (cellInfo.isRegistered()) {
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c78a973..a9b2533 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -25,7 +25,6 @@ import android.os.Binder;
import android.os.Build.VERSION_CODES;
import android.os.Messenger;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.provider.Settings;
import java.net.InetAddress;
@@ -1330,24 +1329,6 @@ public class ConnectivityManager {
/**
* Signal that the captive portal check on the indicated network
- * is complete and we can turn the network on for general use.
- *
- * @param info the {@link NetworkInfo} object for the networkType
- * in question.
- *
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
- * {@hide}
- */
- public void captivePortalCheckComplete(NetworkInfo info) {
- try {
- mService.captivePortalCheckComplete(info);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * Signal that the captive portal check on the indicated network
* is complete and whether its a captive portal or not.
*
* @param info the {@link NetworkInfo} object for the networkType
diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java
index 3bede5d..788d7d9 100644
--- a/core/java/android/net/DhcpInfo.java
+++ b/core/java/android/net/DhcpInfo.java
@@ -18,7 +18,6 @@ package android.net;
import android.os.Parcelable;
import android.os.Parcel;
-import java.net.InetAddress;
/**
* A simple object for retrieving the results of a DHCP request.
diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java
index a3f70da..22b26b1 100644
--- a/core/java/android/net/DhcpResults.java
+++ b/core/java/android/net/DhcpResults.java
@@ -23,9 +23,6 @@ import android.util.Log;
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
/**
* A simple object for retrieving the results of a DHCP request.
diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java
index 51a1191..a5d059e 100644
--- a/core/java/android/net/DummyDataStateTracker.java
+++ b/core/java/android/net/DummyDataStateTracker.java
@@ -117,11 +117,6 @@ public class DummyDataStateTracker extends BaseNetworkStateTracker {
}
@Override
- public void captivePortalCheckComplete() {
- // not implemented
- }
-
- @Override
public void captivePortalCheckCompleted(boolean isCaptivePortal) {
// not implemented
}
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index cc8c771..95faa77 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -270,11 +270,6 @@ public class EthernetDataTracker extends BaseNetworkStateTracker {
}
@Override
- public void captivePortalCheckComplete() {
- // not implemented
- }
-
- @Override
public void captivePortalCheckCompleted(boolean isCaptivePortal) {
// not implemented
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index c1da2e3..b3217eb 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -129,8 +129,6 @@ interface IConnectivityManager
boolean updateLockdownVpn();
- void captivePortalCheckComplete(in NetworkInfo info);
-
void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal);
void supplyMessenger(int networkType, in Messenger messenger);
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
index b76e4c2..c720c7b 100644
--- a/core/java/android/net/INetworkManagementEventObserver.aidl
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -90,4 +90,13 @@ interface INetworkManagementEventObserver {
* @param active True if the interface is actively transmitting data, false if it is idle.
*/
void interfaceClassDataActivityChanged(String label, boolean active);
+
+ /**
+ * Information about available DNS servers has been received.
+ *
+ * @param iface The interface on which the information was received.
+ * @param lifetime The time in seconds for which the DNS servers may be used.
+ * @param servers The IP addresses of the DNS servers.
+ */
+ void interfaceDnsServerInfo(String iface, long lifetime, in String[] servers);
}
diff --git a/core/java/android/net/LinkSocketNotifier.java b/core/java/android/net/LinkSocketNotifier.java
index 28e2834..e2429d8 100644
--- a/core/java/android/net/LinkSocketNotifier.java
+++ b/core/java/android/net/LinkSocketNotifier.java
@@ -16,8 +16,6 @@
package android.net;
-import java.util.Map;
-
/**
* Interface used to get feedback about a {@link android.net.LinkSocket}. Instance is optionally
* passed when a LinkSocket is constructed. Multiple LinkSockets may use the same notifier.
diff --git a/core/java/android/net/MailTo.java b/core/java/android/net/MailTo.java
index b90dcb1..dadb6d9 100644
--- a/core/java/android/net/MailTo.java
+++ b/core/java/android/net/MailTo.java
@@ -19,7 +19,6 @@ package android.net;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
-import java.util.Set;
/**
*
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index c106514..d9c35c0 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -460,11 +460,6 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker {
}
@Override
- public void captivePortalCheckComplete() {
- // not implemented
- }
-
- @Override
public void captivePortalCheckCompleted(boolean isCaptivePortal) {
if (mIsCaptivePortal.getAndSet(isCaptivePortal) != isCaptivePortal) {
// Captive portal change enable/disable failing fast
diff --git a/core/java/android/net/NetworkConfig.java b/core/java/android/net/NetworkConfig.java
index 5d95f41..32a2cda 100644
--- a/core/java/android/net/NetworkConfig.java
+++ b/core/java/android/net/NetworkConfig.java
@@ -16,7 +16,6 @@
package android.net;
-import android.util.Log;
import java.util.Locale;
/**
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 4d2a70d..53b1308 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -156,18 +156,20 @@ public class NetworkInfo implements Parcelable {
/** {@hide} */
public NetworkInfo(NetworkInfo source) {
if (source != null) {
- mNetworkType = source.mNetworkType;
- mSubtype = source.mSubtype;
- mTypeName = source.mTypeName;
- mSubtypeName = source.mSubtypeName;
- mState = source.mState;
- mDetailedState = source.mDetailedState;
- mReason = source.mReason;
- mExtraInfo = source.mExtraInfo;
- mIsFailover = source.mIsFailover;
- mIsRoaming = source.mIsRoaming;
- mIsAvailable = source.mIsAvailable;
- mIsConnectedToProvisioningNetwork = source.mIsConnectedToProvisioningNetwork;
+ synchronized (source) {
+ mNetworkType = source.mNetworkType;
+ mSubtype = source.mSubtype;
+ mTypeName = source.mTypeName;
+ mSubtypeName = source.mSubtypeName;
+ mState = source.mState;
+ mDetailedState = source.mDetailedState;
+ mReason = source.mReason;
+ mExtraInfo = source.mExtraInfo;
+ mIsFailover = source.mIsFailover;
+ mIsRoaming = source.mIsRoaming;
+ mIsAvailable = source.mIsAvailable;
+ mIsConnectedToProvisioningNetwork = source.mIsConnectedToProvisioningNetwork;
+ }
}
}
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 1ca9255..c49b1d1 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -144,11 +144,6 @@ public interface NetworkStateTracker {
public boolean reconnect();
/**
- * Ready to switch on to the network after captive portal check
- */
- public void captivePortalCheckComplete();
-
- /**
* Captive portal check has completed
*/
public void captivePortalCheckCompleted(boolean isCaptive);
diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java
index 010e527..54fc01d 100644
--- a/core/java/android/net/ProxyProperties.java
+++ b/core/java/android/net/ProxyProperties.java
@@ -22,7 +22,6 @@ import android.os.Parcelable;
import android.text.TextUtils;
import java.net.InetSocketAddress;
-import java.net.UnknownHostException;
import java.util.Locale;
/**
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index b0278d3..12e8791 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -135,7 +135,8 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
* disabled, using an optional handshake timeout and SSL session cache.
*
* <p class="caution"><b>Warning:</b> Sockets created using this factory
- * are vulnerable to man-in-the-middle attacks!</p>
+ * are vulnerable to man-in-the-middle attacks!</p>. The caller must implement
+ * its own verification.
*
* @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
* for none. The socket timeout is reset to 0 after the handshake.
@@ -223,8 +224,6 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
if (mInsecureFactory == null) {
if (mSecure) {
Log.w(TAG, "*** BYPASSING SSL SECURITY CHECKS (socket.relaxsslcheck=yes) ***");
- } else {
- Log.w(TAG, "Bypassing SSL security checks at caller's request");
}
mInsecureFactory = makeSocketFactory(mKeyManagers, INSECURE_TRUST_MANAGER);
}
@@ -431,6 +430,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
s.setAlpnProtocols(mAlpnProtocols);
s.setHandshakeTimeout(mHandshakeTimeoutMillis);
s.setChannelIdPrivateKey(mChannelIdPrivateKey);
+ s.setHostname(host);
if (mSecure) {
verifyHostname(s, host);
}
diff --git a/core/java/android/net/SSLSessionCache.java b/core/java/android/net/SSLSessionCache.java
index 15421de..65db2c3 100644
--- a/core/java/android/net/SSLSessionCache.java
+++ b/core/java/android/net/SSLSessionCache.java
@@ -19,12 +19,16 @@ package android.net;
import android.content.Context;
import android.util.Log;
+import com.android.org.conscrypt.ClientSessionContext;
import com.android.org.conscrypt.FileClientSessionCache;
import com.android.org.conscrypt.SSLClientSessionCache;
import java.io.File;
import java.io.IOException;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSessionContext;
+
/**
* File-based cache of established SSL sessions. When re-establishing a
* connection to the same server, using an SSL session cache can save some time,
@@ -38,6 +42,40 @@ public final class SSLSessionCache {
/* package */ final SSLClientSessionCache mSessionCache;
/**
+ * Installs a {@link SSLSessionCache} on a {@link SSLContext}. The cache will
+ * be used on all socket factories created by this context (including factories
+ * created before this call).
+ *
+ * @param cache the cache instance to install, or {@code null} to uninstall any
+ * existing cache.
+ * @param context the context to install it on.
+ * @throws IllegalArgumentException if the context does not support a session
+ * cache.
+ *
+ * @hide candidate for public API
+ */
+ public static void install(SSLSessionCache cache, SSLContext context) {
+ SSLSessionContext clientContext = context.getClientSessionContext();
+ if (clientContext instanceof ClientSessionContext) {
+ ((ClientSessionContext) clientContext).setPersistentCache(
+ cache == null ? null : cache.mSessionCache);
+ } else {
+ throw new IllegalArgumentException("Incompatible SSLContext: " + context);
+ }
+ }
+
+ /**
+ * NOTE: This needs to be Object (and not SSLClientSessionCache) because apps
+ * that build directly against the framework (and not the SDK) might not declare
+ * a dependency on conscrypt. Javac will then has fail while resolving constructors.
+ *
+ * @hide For unit test use only
+ */
+ public SSLSessionCache(Object cache) {
+ mSessionCache = (SSLClientSessionCache) cache;
+ }
+
+ /**
* Create a session cache using the specified directory.
* Individual session entries will be files within the directory.
* Multiple instances for the same directory share data internally.
diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java
index 316440f..7673011 100644
--- a/core/java/android/net/SntpClient.java
+++ b/core/java/android/net/SntpClient.java
@@ -19,7 +19,6 @@ package android.net;
import android.os.SystemClock;
import android.util.Log;
-import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
diff --git a/core/java/android/net/dhcp/DhcpAckPacket.java b/core/java/android/net/dhcp/DhcpAckPacket.java
index 4eca531..7b8be9c 100644
--- a/core/java/android/net/dhcp/DhcpAckPacket.java
+++ b/core/java/android/net/dhcp/DhcpAckPacket.java
@@ -19,7 +19,6 @@ package android.net.dhcp;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.nio.ByteBuffer;
-import java.util.List;
/**
* This class implements the DHCP-ACK packet.
diff --git a/core/java/android/net/dhcp/DhcpOfferPacket.java b/core/java/android/net/dhcp/DhcpOfferPacket.java
index 3d79f4d..f1c30e1 100644
--- a/core/java/android/net/dhcp/DhcpOfferPacket.java
+++ b/core/java/android/net/dhcp/DhcpOfferPacket.java
@@ -19,7 +19,6 @@ package android.net.dhcp;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.nio.ByteBuffer;
-import java.util.List;
/**
* This class implements the DHCP-OFFER packet.
diff --git a/core/java/android/net/dhcp/DhcpPacket.java b/core/java/android/net/dhcp/DhcpPacket.java
index 317a9b4..c7c25f0 100644
--- a/core/java/android/net/dhcp/DhcpPacket.java
+++ b/core/java/android/net/dhcp/DhcpPacket.java
@@ -1,8 +1,5 @@
package android.net.dhcp;
-import android.util.Log;
-
-import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
diff --git a/core/java/android/net/dhcp/DhcpStateMachine.java b/core/java/android/net/dhcp/DhcpStateMachine.java
index b6c384d..bc9a798 100644
--- a/core/java/android/net/dhcp/DhcpStateMachine.java
+++ b/core/java/android/net/dhcp/DhcpStateMachine.java
@@ -17,7 +17,6 @@
package android.net.dhcp;
import java.net.InetAddress;
-import java.nio.ByteBuffer;
import java.util.List;
/**
diff --git a/core/java/android/net/http/AndroidHttpClientConnection.java b/core/java/android/net/http/AndroidHttpClientConnection.java
index eb96679..6d48fce 100644
--- a/core/java/android/net/http/AndroidHttpClientConnection.java
+++ b/core/java/android/net/http/AndroidHttpClientConnection.java
@@ -16,8 +16,6 @@
package android.net.http;
-import org.apache.http.Header;
-
import org.apache.http.HttpConnection;
import org.apache.http.HttpClientConnection;
import org.apache.http.HttpConnectionMetrics;
@@ -27,12 +25,10 @@ import org.apache.http.HttpException;
import org.apache.http.HttpInetConnection;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
-import org.apache.http.HttpResponseFactory;
import org.apache.http.NoHttpResponseException;
import org.apache.http.StatusLine;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.entity.ContentLengthStrategy;
-import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.HttpConnectionMetricsImpl;
import org.apache.http.impl.entity.EntitySerializer;
import org.apache.http.impl.entity.StrictContentLengthStrategy;
diff --git a/core/java/android/net/http/Connection.java b/core/java/android/net/http/Connection.java
index 95cecd2..834ad69 100644
--- a/core/java/android/net/http/Connection.java
+++ b/core/java/android/net/http/Connection.java
@@ -21,7 +21,6 @@ import android.os.SystemClock;
import java.io.IOException;
import java.net.UnknownHostException;
-import java.util.ListIterator;
import java.util.LinkedList;
import javax.net.ssl.SSLHandshakeException;
diff --git a/core/java/android/net/http/ConnectionThread.java b/core/java/android/net/http/ConnectionThread.java
index 32191d2..d825530 100644
--- a/core/java/android/net/http/ConnectionThread.java
+++ b/core/java/android/net/http/ConnectionThread.java
@@ -19,8 +19,6 @@ package android.net.http;
import android.content.Context;
import android.os.SystemClock;
-import org.apache.http.HttpHost;
-
import java.lang.Thread;
/**
diff --git a/core/java/android/net/http/HttpConnection.java b/core/java/android/net/http/HttpConnection.java
index 6df86bf..edf8fed3 100644
--- a/core/java/android/net/http/HttpConnection.java
+++ b/core/java/android/net/http/HttpConnection.java
@@ -21,9 +21,7 @@ import android.content.Context;
import java.net.Socket;
import java.io.IOException;
-import org.apache.http.HttpClientConnection;
import org.apache.http.HttpHost;
-import org.apache.http.impl.DefaultHttpClientConnection;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
diff --git a/core/java/android/net/http/HttpResponseCache.java b/core/java/android/net/http/HttpResponseCache.java
index 269dfb8..2785a15 100644
--- a/core/java/android/net/http/HttpResponseCache.java
+++ b/core/java/android/net/http/HttpResponseCache.java
@@ -17,9 +17,6 @@
package android.net.http;
import android.content.Context;
-import com.android.okhttp.OkResponseCache;
-import com.android.okhttp.ResponseSource;
-import com.android.okhttp.internal.DiskLruCache;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
@@ -32,7 +29,6 @@ import java.net.URLConnection;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
-import libcore.io.IoUtils;
import org.apache.http.impl.client.DefaultHttpClient;
/**
diff --git a/core/java/android/net/http/HttpsConnection.java b/core/java/android/net/http/HttpsConnection.java
index 7a12e53..6bf01e2 100644
--- a/core/java/android/net/http/HttpsConnection.java
+++ b/core/java/android/net/http/HttpsConnection.java
@@ -40,7 +40,6 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.IOException;
-import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.cert.X509Certificate;
diff --git a/core/java/android/net/http/Request.java b/core/java/android/net/http/Request.java
index 8c0d503..76d7bb9 100644
--- a/core/java/android/net/http/Request.java
+++ b/core/java/android/net/http/Request.java
@@ -26,15 +26,12 @@ import java.util.zip.GZIPInputStream;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.Header;
-import org.apache.http.HttpClientConnection;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
-import org.apache.http.HttpVersion;
import org.apache.http.ParseException;
import org.apache.http.ProtocolVersion;
diff --git a/core/java/android/net/http/RequestQueue.java b/core/java/android/net/http/RequestQueue.java
index ce6b1ad..7d2da1b 100644
--- a/core/java/android/net/http/RequestQueue.java
+++ b/core/java/android/net/http/RequestQueue.java
@@ -29,10 +29,6 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Proxy;
import android.net.WebAddress;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemProperties;
-import android.text.TextUtils;
import android.util.Log;
import java.io.InputStream;
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index 9c3e405..6840207 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -19,8 +19,6 @@ package android.net.nsd;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
-import android.os.Binder;
-import android.os.IBinder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
index 2b58818..de481cf 100644
--- a/core/java/android/nfc/NdefRecord.java
+++ b/core/java/android/nfc/NdefRecord.java
@@ -269,6 +269,7 @@ public final class NdefRecord implements Parcelable {
"urn:epc:pat:", // 0x20
"urn:epc:raw:", // 0x21
"urn:epc:", // 0x22
+ "urn:nfc:", // 0x23
};
private static final int MAX_PAYLOAD_SIZE = 10 * (1 << 20); // 10 MB payload limit
diff --git a/core/java/android/nfc/tech/Ndef.java b/core/java/android/nfc/tech/Ndef.java
index 64aa299..8240ea6 100644
--- a/core/java/android/nfc/tech/Ndef.java
+++ b/core/java/android/nfc/tech/Ndef.java
@@ -20,7 +20,6 @@ import android.nfc.ErrorCodes;
import android.nfc.FormatException;
import android.nfc.INfcTag;
import android.nfc.NdefMessage;
-import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.TagLostException;
import android.os.Bundle;
diff --git a/core/java/android/nfc/tech/NdefFormatable.java b/core/java/android/nfc/tech/NdefFormatable.java
index ffa6a2b..4175cd0 100644
--- a/core/java/android/nfc/tech/NdefFormatable.java
+++ b/core/java/android/nfc/tech/NdefFormatable.java
@@ -20,7 +20,6 @@ import android.nfc.ErrorCodes;
import android.nfc.FormatException;
import android.nfc.INfcTag;
import android.nfc.NdefMessage;
-import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.TagLostException;
import android.os.RemoteException;
diff --git a/core/java/android/os/BatteryProperties.java b/core/java/android/os/BatteryProperties.java
index 5df5214..2d67264 100644
--- a/core/java/android/os/BatteryProperties.java
+++ b/core/java/android/os/BatteryProperties.java
@@ -30,8 +30,6 @@ public class BatteryProperties implements Parcelable {
public boolean batteryPresent;
public int batteryLevel;
public int batteryVoltage;
- public int batteryCurrentNow;
- public int batteryChargeCounter;
public int batteryTemperature;
public String batteryTechnology;
@@ -49,8 +47,6 @@ public class BatteryProperties implements Parcelable {
batteryPresent = p.readInt() == 1 ? true : false;
batteryLevel = p.readInt();
batteryVoltage = p.readInt();
- batteryCurrentNow = p.readInt();
- batteryChargeCounter = p.readInt();
batteryTemperature = p.readInt();
batteryTechnology = p.readString();
}
@@ -64,8 +60,6 @@ public class BatteryProperties implements Parcelable {
p.writeInt(batteryPresent ? 1 : 0);
p.writeInt(batteryLevel);
p.writeInt(batteryVoltage);
- p.writeInt(batteryCurrentNow);
- p.writeInt(batteryChargeCounter);
p.writeInt(batteryTemperature);
p.writeString(batteryTechnology);
}
diff --git a/core/java/android/os/BatteryProperty.aidl b/core/java/android/os/BatteryProperty.aidl
new file mode 100644
index 0000000..b3f2ec3
--- /dev/null
+++ b/core/java/android/os/BatteryProperty.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os;
+
+parcelable BatteryProperty;
diff --git a/core/java/android/os/BatteryProperty.java b/core/java/android/os/BatteryProperty.java
new file mode 100644
index 0000000..346f5de
--- /dev/null
+++ b/core/java/android/os/BatteryProperty.java
@@ -0,0 +1,70 @@
+/* Copyright 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package android.os;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * {@hide}
+ */
+public class BatteryProperty implements Parcelable {
+ /*
+ * Battery property identifiers. These must match the values in
+ * frameworks/native/include/batteryservice/BatteryService.h
+ */
+ public static final int BATTERY_PROP_CHARGE_COUNTER = 1;
+ public static final int BATTERY_PROP_CURRENT_NOW = 2;
+ public static final int BATTERY_PROP_CURRENT_AVG = 3;
+
+ public int valueInt;
+
+ public BatteryProperty() {
+ valueInt = Integer.MIN_VALUE;
+ }
+
+ /*
+ * Parcel read/write code must be kept in sync with
+ * frameworks/native/services/batteryservice/BatteryProperty.cpp
+ */
+
+ private BatteryProperty(Parcel p) {
+ readFromParcel(p);
+ }
+
+ public void readFromParcel(Parcel p) {
+ valueInt = p.readInt();
+ }
+
+ public void writeToParcel(Parcel p, int flags) {
+ p.writeInt(valueInt);
+ }
+
+ public static final Parcelable.Creator<BatteryProperty> CREATOR
+ = new Parcelable.Creator<BatteryProperty>() {
+ public BatteryProperty createFromParcel(Parcel p) {
+ return new BatteryProperty(p);
+ }
+
+ public BatteryProperty[] newArray(int size) {
+ return new BatteryProperty[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b1a9ea3..06fd8fb 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -26,7 +26,6 @@ import java.util.Map;
import android.content.pm.ApplicationInfo;
import android.telephony.SignalStrength;
-import android.util.Log;
import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
diff --git a/core/java/android/os/CommonClock.java b/core/java/android/os/CommonClock.java
index 3a1da97..2ecf317 100644
--- a/core/java/android/os/CommonClock.java
+++ b/core/java/android/os/CommonClock.java
@@ -15,17 +15,8 @@
*/
package android.os;
-import java.net.InetAddress;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.util.NoSuchElementException;
-import static libcore.io.OsConstants.*;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
import android.os.Binder;
import android.os.CommonTimeUtils;
import android.os.IBinder;
diff --git a/core/java/android/os/CommonTimeConfig.java b/core/java/android/os/CommonTimeConfig.java
index 3355ee3..1f9fab5 100644
--- a/core/java/android/os/CommonTimeConfig.java
+++ b/core/java/android/os/CommonTimeConfig.java
@@ -15,7 +15,6 @@
*/
package android.os;
-import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.NoSuchElementException;
diff --git a/core/java/android/os/CountDownTimer.java b/core/java/android/os/CountDownTimer.java
index 15e6405..c5b1146 100644
--- a/core/java/android/os/CountDownTimer.java
+++ b/core/java/android/os/CountDownTimer.java
@@ -16,8 +16,6 @@
package android.os;
-import android.util.Log;
-
/**
* Schedule a countdown until a time in the future, with
* regular notifications on intervals along the way.
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 974bf8d..7f167d5 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -26,7 +26,6 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
-import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.lang.reflect.Field;
@@ -41,7 +40,6 @@ import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
import dalvik.bytecode.OpcodeInfo;
-import dalvik.bytecode.Opcodes;
import dalvik.system.VMDebug;
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index e1c1678..27001dc 100644
--- a/core/java/android/os/DropBoxManager.java
+++ b/core/java/android/os/DropBoxManager.java
@@ -16,14 +16,11 @@
package android.os;
-import android.util.Log;
-
import com.android.internal.os.IDropBoxManagerService;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;
diff --git a/core/java/android/os/FileObserver.java b/core/java/android/os/FileObserver.java
index d633486..4e705e0 100644
--- a/core/java/android/os/FileObserver.java
+++ b/core/java/android/os/FileObserver.java
@@ -18,10 +18,7 @@ package android.os;
import android.util.Log;
-import com.android.internal.os.RuntimeInit;
-
import java.lang.ref.WeakReference;
-import java.util.ArrayList;
import java.util.HashMap;
/**
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index ff3e277..2d60df0 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -20,9 +20,7 @@ import android.util.Log;
import android.util.Slog;
import libcore.io.ErrnoException;
-import libcore.io.IoUtils;
import libcore.io.Libcore;
-import libcore.io.OsConstants;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
diff --git a/core/java/android/os/IBatteryPropertiesRegistrar.aidl b/core/java/android/os/IBatteryPropertiesRegistrar.aidl
index 376f6c9..fd01802 100644
--- a/core/java/android/os/IBatteryPropertiesRegistrar.aidl
+++ b/core/java/android/os/IBatteryPropertiesRegistrar.aidl
@@ -17,6 +17,7 @@
package android.os;
import android.os.IBatteryPropertiesListener;
+import android.os.BatteryProperty;
/**
* {@hide}
@@ -25,4 +26,5 @@ import android.os.IBatteryPropertiesListener;
interface IBatteryPropertiesRegistrar {
void registerListener(IBatteryPropertiesListener listener);
void unregisterListener(IBatteryPropertiesListener listener);
+ int getProperty(in int id, out BatteryProperty prop);
}
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index a2432d6..73a0f65 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -17,7 +17,6 @@
package android.os;
import java.io.FileDescriptor;
-import java.io.PrintWriter;
/**
* Base interface for a remotable object, the core part of a lightweight
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 21e9f6b..ff31130 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -18,7 +18,6 @@ package android.os;
import android.util.Log;
import android.util.Printer;
-import android.util.PrefixPrinter;
/**
* Class used to run a message loop for a thread. Threads by default do
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 799de5c..e90a457 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -126,6 +126,14 @@ public final class MessageQueue {
}
Message next() {
+ // Return here if the message loop has already quit and been disposed.
+ // This can happen if the application tries to restart a looper after quit
+ // which is not supported.
+ final int ptr = mPtr;
+ if (ptr == 0) {
+ return null;
+ }
+
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
@@ -133,9 +141,7 @@ public final class MessageQueue {
Binder.flushPendingCommands();
}
- // We can assume mPtr != 0 because the loop is obviously still running.
- // The looper will not call this method after the loop quits.
- nativePollOnce(mPtr, nextPollTimeoutMillis);
+ nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
index ac6027f..af90bdb 100644
--- a/core/java/android/os/NullVibrator.java
+++ b/core/java/android/os/NullVibrator.java
@@ -16,8 +16,6 @@
package android.os;
-import android.util.Log;
-
/**
* Vibrator implementation that does nothing.
*
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 5273c20..86dc8b4 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -872,6 +872,8 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
*/
@Override
public void writeToParcel(Parcel out, int flags) {
+ // WARNING: This must stay in sync with Parcel::readParcelFileDescriptor()
+ // in frameworks/native/libs/binder/Parcel.cpp
if (mWrapped != null) {
try {
mWrapped.writeToParcel(out, flags);
@@ -897,6 +899,8 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
= new Parcelable.Creator<ParcelFileDescriptor>() {
@Override
public ParcelFileDescriptor createFromParcel(Parcel in) {
+ // WARNING: This must stay in sync with Parcel::writeParcelFileDescriptor()
+ // in frameworks/native/libs/binder/Parcel.cpp
final FileDescriptor fd = in.readRawFileDescriptor();
FileDescriptor commChannel = null;
if (in.readInt() != 0) {
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 631edd6..057f516 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -892,19 +892,6 @@ public class Process {
}
/**
- * Set the out-of-memory badness adjustment for a process.
- *
- * @param pid The process identifier to set.
- * @param amt Adjustment value -- linux allows -16 to +15.
- *
- * @return Returns true if the underlying system supports this
- * feature, else false.
- *
- * {@hide}
- */
- public static final native boolean setOomAdj(int pid, int amt);
-
- /**
* Adjust the swappiness level for a process.
*
* @param pid The process identifier to set.
diff --git a/core/java/android/os/Registrant.java b/core/java/android/os/Registrant.java
index c1780b9..705cc5d 100644
--- a/core/java/android/os/Registrant.java
+++ b/core/java/android/os/Registrant.java
@@ -20,7 +20,6 @@ import android.os.Handler;
import android.os.Message;
import java.lang.ref.WeakReference;
-import java.util.HashMap;
/** @hide */
public class Registrant
diff --git a/core/java/android/os/RegistrantList.java b/core/java/android/os/RegistrantList.java
index 56b9e2b..9ab61f5 100644
--- a/core/java/android/os/RegistrantList.java
+++ b/core/java/android/os/RegistrantList.java
@@ -17,10 +17,8 @@
package android.os;
import android.os.Handler;
-import android.os.Message;
import java.util.ArrayList;
-import java.util.HashMap;
/** @hide */
public class RegistrantList
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index 156600e..1479035 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -18,8 +18,6 @@ package android.os;
import java.util.ArrayList;
-import android.util.Log;
-
/**
* Gives access to the system properties store. The system properties
diff --git a/core/java/android/os/SystemService.java b/core/java/android/os/SystemService.java
index f345271..41e7546 100644
--- a/core/java/android/os/SystemService.java
+++ b/core/java/android/os/SystemService.java
@@ -16,8 +16,6 @@
package android.os;
-import android.util.Slog;
-
import com.google.android.collect.Maps;
import java.util.HashMap;
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 3249bcb..57ed979 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -16,8 +16,6 @@
package android.os;
-import android.util.Log;
-
/**
* Writes trace events to the system trace buffer. These trace events can be
* collected and visualized using the Systrace tool.
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a3752a1..5d087ea 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -17,7 +17,6 @@ package android.os;
import android.app.ActivityManagerNative;
import android.content.Context;
-import android.content.RestrictionEntry;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
diff --git a/core/java/android/preference/CheckBoxPreference.java b/core/java/android/preference/CheckBoxPreference.java
index 1536760..1ce98b8 100644
--- a/core/java/android/preference/CheckBoxPreference.java
+++ b/core/java/android/preference/CheckBoxPreference.java
@@ -34,11 +34,16 @@ import android.widget.Checkable;
*/
public class CheckBoxPreference extends TwoStatePreference {
- public CheckBoxPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.CheckBoxPreference, defStyle, 0);
+ public CheckBoxPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public CheckBoxPreference(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.CheckBoxPreference, defStyleAttr, defStyleRes);
setSummaryOn(a.getString(com.android.internal.R.styleable.CheckBoxPreference_summaryOn));
setSummaryOff(a.getString(com.android.internal.R.styleable.CheckBoxPreference_summaryOff));
setDisableDependentsState(a.getBoolean(
diff --git a/core/java/android/preference/DialogPreference.java b/core/java/android/preference/DialogPreference.java
index a643c8a..5275bc0 100644
--- a/core/java/android/preference/DialogPreference.java
+++ b/core/java/android/preference/DialogPreference.java
@@ -64,12 +64,13 @@ public abstract class DialogPreference extends Preference implements
/** Which button was clicked. */
private int mWhichButtonClicked;
-
- public DialogPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.DialogPreference, defStyle, 0);
+
+ public DialogPreference(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.DialogPreference, defStyleAttr, defStyleRes);
mDialogTitle = a.getString(com.android.internal.R.styleable.DialogPreference_dialogTitle);
if (mDialogTitle == null) {
// Fallback on the regular title of the preference
@@ -83,13 +84,20 @@ public abstract class DialogPreference extends Preference implements
mDialogLayoutResId = a.getResourceId(com.android.internal.R.styleable.DialogPreference_dialogLayout,
mDialogLayoutResId);
a.recycle();
-
+ }
+
+ public DialogPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
public DialogPreference(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle);
}
-
+
+ public DialogPreference(Context context) {
+ this(context, null);
+ }
+
/**
* Sets the title of the dialog. This will be shown on subsequent dialogs.
*
diff --git a/core/java/android/preference/EditTextPreference.java b/core/java/android/preference/EditTextPreference.java
index aa27627..ff37042 100644
--- a/core/java/android/preference/EditTextPreference.java
+++ b/core/java/android/preference/EditTextPreference.java
@@ -49,9 +49,9 @@ public class EditTextPreference extends DialogPreference {
private EditText mEditText;
private String mText;
-
- public EditTextPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+
+ public EditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
mEditText = new EditText(context, attrs);
@@ -67,6 +67,10 @@ public class EditTextPreference extends DialogPreference {
mEditText.setEnabled(true);
}
+ public EditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
public EditTextPreference(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.editTextPreferenceStyle);
}
diff --git a/core/java/android/preference/ListPreference.java b/core/java/android/preference/ListPreference.java
index 9edf112..8081a54 100644
--- a/core/java/android/preference/ListPreference.java
+++ b/core/java/android/preference/ListPreference.java
@@ -42,12 +42,12 @@ public class ListPreference extends DialogPreference {
private String mSummary;
private int mClickedDialogEntryIndex;
private boolean mValueSet;
-
- public ListPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.ListPreference, 0, 0);
+
+ public ListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.ListPreference, defStyleAttr, defStyleRes);
mEntries = a.getTextArray(com.android.internal.R.styleable.ListPreference_entries);
mEntryValues = a.getTextArray(com.android.internal.R.styleable.ListPreference_entryValues);
a.recycle();
@@ -56,11 +56,19 @@ public class ListPreference extends DialogPreference {
* in the Preference class.
*/
a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.Preference, 0, 0);
+ com.android.internal.R.styleable.Preference, defStyleAttr, defStyleRes);
mSummary = a.getString(com.android.internal.R.styleable.Preference_summary);
a.recycle();
}
+ public ListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ListPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle);
+ }
+
public ListPreference(Context context) {
this(context, null);
}
diff --git a/core/java/android/preference/MultiCheckPreference.java b/core/java/android/preference/MultiCheckPreference.java
index 6953075..57c906d 100644
--- a/core/java/android/preference/MultiCheckPreference.java
+++ b/core/java/android/preference/MultiCheckPreference.java
@@ -40,12 +40,13 @@ public class MultiCheckPreference extends DialogPreference {
private boolean[] mSetValues;
private boolean[] mOrigValues;
private String mSummary;
-
- public MultiCheckPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.ListPreference, 0, 0);
+
+ public MultiCheckPreference(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.ListPreference, defStyleAttr, defStyleRes);
mEntries = a.getTextArray(com.android.internal.R.styleable.ListPreference_entries);
if (mEntries != null) {
setEntries(mEntries);
@@ -63,6 +64,14 @@ public class MultiCheckPreference extends DialogPreference {
a.recycle();
}
+ public MultiCheckPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public MultiCheckPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle);
+ }
+
public MultiCheckPreference(Context context) {
this(context, null);
}
diff --git a/core/java/android/preference/MultiSelectListPreference.java b/core/java/android/preference/MultiSelectListPreference.java
index 553ce80..6c4c20f 100644
--- a/core/java/android/preference/MultiSelectListPreference.java
+++ b/core/java/android/preference/MultiSelectListPreference.java
@@ -44,16 +44,26 @@ public class MultiSelectListPreference extends DialogPreference {
private Set<String> mValues = new HashSet<String>();
private Set<String> mNewValues = new HashSet<String>();
private boolean mPreferenceChanged;
-
- public MultiSelectListPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.MultiSelectListPreference, 0, 0);
+
+ public MultiSelectListPreference(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.MultiSelectListPreference, defStyleAttr,
+ defStyleRes);
mEntries = a.getTextArray(com.android.internal.R.styleable.MultiSelectListPreference_entries);
mEntryValues = a.getTextArray(com.android.internal.R.styleable.MultiSelectListPreference_entryValues);
a.recycle();
}
+
+ public MultiSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public MultiSelectListPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle);
+ }
public MultiSelectListPreference(Context context) {
this(context, null);
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index f7d1eb7..76fccc7 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -188,30 +188,33 @@ public class Preference implements Comparable<Preference> {
/**
* Perform inflation from XML and apply a class-specific base style. This
- * constructor of Preference allows subclasses to use their own base
- * style when they are inflating. For example, a {@link CheckBoxPreference}
+ * 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.
- *
+ * supplies {@code android.R.attr.checkBoxPreferenceStyle} for
+ * <var>defStyleAttr</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.
+ *
* @param context The Context this is associated with, through which it can
- * access the current theme, resources, {@link SharedPreferences},
- * etc.
- * @param attrs The attributes of the XML tag that is inflating the preference.
- * @param defStyle The default style to apply to this preference. If 0, no style
- * will be applied (beyond what is included in the theme). This
- * may either be an attribute resource, whose value will be
- * retrieved from the current theme, or an explicit style
- * resource.
+ * access the current theme, resources,
+ * {@link SharedPreferences}, etc.
+ * @param attrs The attributes of the XML tag that is inflating the
+ * preference.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
* @see #Preference(Context, AttributeSet)
*/
- public Preference(Context context, AttributeSet attrs, int defStyle) {
+ public Preference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mContext = context;
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.Preference, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.Preference, defStyleAttr, defStyleRes);
for (int i = a.getIndexCount(); i >= 0; i--) {
int attr = a.getIndex(i);
switch (attr) {
@@ -281,6 +284,30 @@ public class Preference implements Comparable<Preference> {
mCanRecycleLayout = false;
}
}
+
+ /**
+ * Perform inflation from XML and apply a class-specific base style. This
+ * 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>defStyleAttr</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.
+ *
+ * @param context The Context this is associated with, through which it can
+ * access the current theme, resources,
+ * {@link SharedPreferences}, etc.
+ * @param attrs The attributes of the XML tag that is inflating the
+ * preference.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @see #Preference(Context, AttributeSet)
+ */
+ public Preference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
/**
* Constructor that is called when inflating a Preference from XML. This is
diff --git a/core/java/android/preference/PreferenceCategory.java b/core/java/android/preference/PreferenceCategory.java
index 229a96a..253481b 100644
--- a/core/java/android/preference/PreferenceCategory.java
+++ b/core/java/android/preference/PreferenceCategory.java
@@ -16,8 +16,6 @@
package android.preference;
-import java.util.Map;
-
import android.content.Context;
import android.util.AttributeSet;
@@ -34,9 +32,14 @@ import android.util.AttributeSet;
*/
public class PreferenceCategory extends PreferenceGroup {
private static final String TAG = "PreferenceCategory";
-
- public PreferenceCategory(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+
+ public PreferenceCategory(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public PreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
public PreferenceCategory(Context context, AttributeSet attrs) {
diff --git a/core/java/android/preference/PreferenceFrameLayout.java b/core/java/android/preference/PreferenceFrameLayout.java
index 75372aa..886338f 100644
--- a/core/java/android/preference/PreferenceFrameLayout.java
+++ b/core/java/android/preference/PreferenceFrameLayout.java
@@ -16,7 +16,6 @@
package android.preference;
-import android.app.FragmentBreadCrumbs;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
@@ -45,10 +44,15 @@ public class PreferenceFrameLayout extends FrameLayout {
this(context, attrs, com.android.internal.R.attr.preferenceFrameLayoutStyle);
}
- public PreferenceFrameLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.PreferenceFrameLayout, defStyle, 0);
+ public PreferenceFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public PreferenceFrameLayout(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.PreferenceFrameLayout, defStyleAttr, defStyleRes);
float density = context.getResources().getDisplayMetrics().density;
int defaultBorderTop = (int) (density * DEFAULT_BORDER_TOP + 0.5f);
diff --git a/core/java/android/preference/PreferenceGroup.java b/core/java/android/preference/PreferenceGroup.java
index 5f8c78d..2d35b1b 100644
--- a/core/java/android/preference/PreferenceGroup.java
+++ b/core/java/android/preference/PreferenceGroup.java
@@ -55,19 +55,23 @@ public abstract class PreferenceGroup extends Preference implements GenericInfla
private int mCurrentPreferenceOrder = 0;
private boolean mAttachedToActivity = false;
-
- public PreferenceGroup(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+
+ public PreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
mPreferenceList = new ArrayList<Preference>();
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.PreferenceGroup, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.PreferenceGroup, defStyleAttr, defStyleRes);
mOrderingAsAdded = a.getBoolean(com.android.internal.R.styleable.PreferenceGroup_orderingFromXml,
mOrderingAsAdded);
a.recycle();
}
+ public PreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
public PreferenceGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
diff --git a/core/java/android/preference/PreferenceInflater.java b/core/java/android/preference/PreferenceInflater.java
index c21aa18..727fbca 100644
--- a/core/java/android/preference/PreferenceInflater.java
+++ b/core/java/android/preference/PreferenceInflater.java
@@ -19,16 +19,13 @@ package android.preference;
import com.android.internal.util.XmlUtils;
import java.io.IOException;
-import java.util.Map;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import android.app.AliasActivity;
import android.content.Context;
import android.content.Intent;
import android.util.AttributeSet;
-import android.util.Log;
/**
* The {@link PreferenceInflater} is used to inflate preference hierarchies from
diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java
index db80676..b1317e6 100644
--- a/core/java/android/preference/PreferenceScreen.java
+++ b/core/java/android/preference/PreferenceScreen.java
@@ -27,7 +27,6 @@ import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
-import android.widget.AbsListView;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.ListAdapter;
diff --git a/core/java/android/preference/RingtonePreference.java b/core/java/android/preference/RingtonePreference.java
index 2ebf294..488a0c4 100644
--- a/core/java/android/preference/RingtonePreference.java
+++ b/core/java/android/preference/RingtonePreference.java
@@ -50,11 +50,11 @@ public class RingtonePreference extends Preference implements
private int mRequestCode;
- public RingtonePreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.RingtonePreference, defStyle, 0);
+ public RingtonePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.RingtonePreference, defStyleAttr, defStyleRes);
mRingtoneType = a.getInt(com.android.internal.R.styleable.RingtonePreference_ringtoneType,
RingtoneManager.TYPE_RINGTONE);
mShowDefault = a.getBoolean(com.android.internal.R.styleable.RingtonePreference_showDefault,
@@ -64,6 +64,10 @@ public class RingtonePreference extends Preference implements
a.recycle();
}
+ public RingtonePreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
public RingtonePreference(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.ringtonePreferenceStyle);
}
diff --git a/core/java/android/preference/SeekBarDialogPreference.java b/core/java/android/preference/SeekBarDialogPreference.java
index 0e89b16..9a08827 100644
--- a/core/java/android/preference/SeekBarDialogPreference.java
+++ b/core/java/android/preference/SeekBarDialogPreference.java
@@ -32,8 +32,9 @@ public class SeekBarDialogPreference extends DialogPreference {
private Drawable mMyIcon;
- public SeekBarDialogPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
+ public SeekBarDialogPreference(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
setDialogLayoutResource(com.android.internal.R.layout.seekbar_dialog);
createActionButtons();
@@ -43,6 +44,18 @@ public class SeekBarDialogPreference extends DialogPreference {
setDialogIcon(null);
}
+ public SeekBarDialogPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SeekBarDialogPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle);
+ }
+
+ public SeekBarDialogPreference(Context context) {
+ this(context, null);
+ }
+
// Allow subclasses to override the action buttons
public void createActionButtons() {
setPositiveButtonText(android.R.string.ok);
diff --git a/core/java/android/preference/SeekBarPreference.java b/core/java/android/preference/SeekBarPreference.java
index 7133d3a..e32890d 100644
--- a/core/java/android/preference/SeekBarPreference.java
+++ b/core/java/android/preference/SeekBarPreference.java
@@ -37,15 +37,20 @@ public class SeekBarPreference extends Preference
private boolean mTrackingTouch;
public SeekBarPreference(
- Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.ProgressBar, defStyle, 0);
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.ProgressBar, defStyleAttr, defStyleRes);
setMax(a.getInt(com.android.internal.R.styleable.ProgressBar_max, mMax));
a.recycle();
setLayoutResource(com.android.internal.R.layout.preference_widget_seekbar);
}
+ public SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
public SeekBarPreference(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
diff --git a/core/java/android/preference/SwitchPreference.java b/core/java/android/preference/SwitchPreference.java
index 8bac6bd..76ef544 100644
--- a/core/java/android/preference/SwitchPreference.java
+++ b/core/java/android/preference/SwitchPreference.java
@@ -60,13 +60,19 @@ public class SwitchPreference extends TwoStatePreference {
*
* @param context The Context that will style this preference
* @param attrs Style attributes that differ from the default
- * @param defStyle Theme attribute defining the default style options
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
*/
- public SwitchPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.SwitchPreference, defStyle, 0);
+ com.android.internal.R.styleable.SwitchPreference, defStyleAttr, defStyleRes);
setSummaryOn(a.getString(com.android.internal.R.styleable.SwitchPreference_summaryOn));
setSummaryOff(a.getString(com.android.internal.R.styleable.SwitchPreference_summaryOff));
setSwitchTextOn(a.getString(
@@ -83,6 +89,19 @@ public class SwitchPreference extends TwoStatePreference {
*
* @param context The Context that will style this preference
* @param attrs Style attributes that differ from the default
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ */
+ public SwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ /**
+ * Construct a new SwitchPreference with the given style options.
+ *
+ * @param context The Context that will style this preference
+ * @param attrs Style attributes that differ from the default
*/
public SwitchPreference(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.switchPreferenceStyle);
diff --git a/core/java/android/preference/TwoStatePreference.java b/core/java/android/preference/TwoStatePreference.java
index af83953..6f8be1f 100644
--- a/core/java/android/preference/TwoStatePreference.java
+++ b/core/java/android/preference/TwoStatePreference.java
@@ -42,9 +42,13 @@ public abstract class TwoStatePreference extends Preference {
private boolean mSendClickAccessibilityEvent;
private boolean mDisableDependentsState;
+ public TwoStatePreference(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
- public TwoStatePreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public TwoStatePreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
public TwoStatePreference(Context context, AttributeSet attrs) {
diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java
index dc683a6..29f2545 100644
--- a/core/java/android/preference/VolumePreference.java
+++ b/core/java/android/preference/VolumePreference.java
@@ -51,15 +51,24 @@ public class VolumePreference extends SeekBarDialogPreference implements
/** May be null if the dialog isn't visible. */
private SeekBarVolumizer mSeekBarVolumizer;
- public VolumePreference(Context context, AttributeSet attrs) {
- super(context, attrs);
+ public VolumePreference(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.VolumePreference, 0, 0);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.VolumePreference, defStyleAttr, defStyleRes);
mStreamType = a.getInt(android.R.styleable.VolumePreference_streamType, 0);
a.recycle();
}
+ public VolumePreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public VolumePreference(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
public void setStreamType(int streamType) {
mStreamType = streamType;
}
diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java
index c7e3c08..9e2aacd 100644
--- a/core/java/android/provider/Contacts.java
+++ b/core/java/android/provider/Contacts.java
@@ -26,7 +26,6 @@ import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
-import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.widget.ImageView;
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index b16df28..daa9881 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -45,9 +45,6 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* <p>
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 04f3f0a..0dffc17 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3764,6 +3764,97 @@ public final class Settings {
"accessibility_captioning_font_scale";
/**
+ * Setting that specifies whether the quick setting tile for display
+ * color inversion is enabled.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_INVERSION_QUICK_SETTING_ENABLED =
+ "accessibility_display_inversion_quick_setting_enabled";
+
+ /**
+ * Setting that specifies whether display color inversion is enabled.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_INVERSION_ENABLED =
+ "accessibility_display_inversion_enabled";
+
+ /**
+ * Integer property that specifies the type of color inversion to
+ * perform. Valid values are defined in AccessibilityManager.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_INVERSION =
+ "accessibility_display_inversion";
+
+ /**
+ * Setting that specifies whether the quick setting tile for display
+ * color space adjustment is enabled.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_DALTONIZER_QUICK_SETTING_ENABLED =
+ "accessibility_display_daltonizer_quick_setting_enabled";
+
+ /**
+ * Setting that specifies whether display color space adjustment is
+ * enabled.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED =
+ "accessibility_display_daltonizer_enabled";
+
+ /**
+ * Integer property that specifies the type of color space adjustment to
+ * perform. Valid values are defined in AccessibilityManager.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_DALTONIZER =
+ "accessibility_display_daltonizer";
+
+ /**
+ * Setting that specifies whether the quick setting tile for display
+ * contrast enhancement is enabled.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_CONTRAST_QUICK_SETTING_ENABLED =
+ "accessibility_display_contrast_quick_setting_enabled";
+
+ /**
+ * Setting that specifies whether display contrast enhancement is
+ * enabled.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_CONTRAST_ENABLED =
+ "accessibility_display_contrast_enabled";
+
+ /**
+ * Floating point property that specifies display contrast adjustment.
+ * Valid range is [0, ...] where 0 is gray, 1 is normal, and higher
+ * values indicate enhanced contrast.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_CONTRAST =
+ "accessibility_display_contrast";
+
+ /**
+ * Floating point property that specifies display brightness adjustment.
+ * Valid range is [-1, 1] where -1 is black, 0 is default, and 1 is
+ * white.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_BRIGHTNESS =
+ "accessibility_display_brightness";
+
+ /**
* The timout for considering a press to be a long press in milliseconds.
* @hide
*/
diff --git a/core/java/android/service/textservice/SpellCheckerService.java b/core/java/android/service/textservice/SpellCheckerService.java
index 77b22ed..acfef82 100644
--- a/core/java/android/service/textservice/SpellCheckerService.java
+++ b/core/java/android/service/textservice/SpellCheckerService.java
@@ -32,7 +32,6 @@ import android.util.Log;
import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
-import android.widget.SpellChecker;
import java.lang.ref.WeakReference;
import java.text.BreakIterator;
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 5db8168..03ce4e0 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -38,7 +38,6 @@ import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.util.Log;
-import android.util.LogPrinter;
import android.view.Display;
import android.view.Gravity;
import android.view.IWindowSession;
diff --git a/core/java/android/speech/srec/Recognizer.java b/core/java/android/speech/srec/Recognizer.java
index db5d8fd..4bdaf5b 100644
--- a/core/java/android/speech/srec/Recognizer.java
+++ b/core/java/android/speech/srec/Recognizer.java
@@ -22,8 +22,6 @@
package android.speech.srec;
-import android.util.Log;
-
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
diff --git a/core/java/android/speech/tts/AbstractEventLogger.java b/core/java/android/speech/tts/AbstractEventLogger.java
new file mode 100644
index 0000000..37f8656
--- /dev/null
+++ b/core/java/android/speech/tts/AbstractEventLogger.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.os.SystemClock;
+
+/**
+ * Base class for storing data about a given speech synthesis request to the
+ * event logs. The data that is logged depends on actual implementation. Note
+ * that {@link AbstractEventLogger#onAudioDataWritten()} and
+ * {@link AbstractEventLogger#onEngineComplete()} must be called from a single
+ * thread (usually the audio playback thread}.
+ */
+abstract class AbstractEventLogger {
+ protected final String mServiceApp;
+ protected final int mCallerUid;
+ protected final int mCallerPid;
+ protected final long mReceivedTime;
+ protected long mPlaybackStartTime = -1;
+
+ private volatile long mRequestProcessingStartTime = -1;
+ private volatile long mEngineStartTime = -1;
+ private volatile long mEngineCompleteTime = -1;
+
+ private boolean mLogWritten = false;
+
+ AbstractEventLogger(int callerUid, int callerPid, String serviceApp) {
+ mCallerUid = callerUid;
+ mCallerPid = callerPid;
+ mServiceApp = serviceApp;
+ mReceivedTime = SystemClock.elapsedRealtime();
+ }
+
+ /**
+ * Notifies the logger that this request has been selected from
+ * the processing queue for processing. Engine latency / total time
+ * is measured from this baseline.
+ */
+ public void onRequestProcessingStart() {
+ mRequestProcessingStartTime = SystemClock.elapsedRealtime();
+ }
+
+ /**
+ * Notifies the logger that a chunk of data has been received from
+ * the engine. Might be called multiple times.
+ */
+ public void onEngineDataReceived() {
+ if (mEngineStartTime == -1) {
+ mEngineStartTime = SystemClock.elapsedRealtime();
+ }
+ }
+
+ /**
+ * Notifies the logger that the engine has finished processing data.
+ * Will be called exactly once.
+ */
+ public void onEngineComplete() {
+ mEngineCompleteTime = SystemClock.elapsedRealtime();
+ }
+
+ /**
+ * Notifies the logger that audio playback has started for some section
+ * of the synthesis. This is normally some amount of time after the engine
+ * has synthesized data and varies depending on utterances and
+ * other audio currently in the queue.
+ */
+ public void onAudioDataWritten() {
+ // For now, keep track of only the first chunk of audio
+ // that was played.
+ if (mPlaybackStartTime == -1) {
+ mPlaybackStartTime = SystemClock.elapsedRealtime();
+ }
+ }
+
+ /**
+ * Notifies the logger that the current synthesis has completed.
+ * All available data is not logged.
+ */
+ public void onCompleted(int statusCode) {
+ if (mLogWritten) {
+ return;
+ } else {
+ mLogWritten = true;
+ }
+
+ long completionTime = SystemClock.elapsedRealtime();
+
+ // We don't report latency for stopped syntheses because their overall
+ // total time spent will be inaccurate (will not correlate with
+ // the length of the utterance).
+
+ // onAudioDataWritten() should normally always be called, and hence mPlaybackStartTime
+ // should be set, if an error does not occur.
+ if (statusCode != TextToSpeechClient.Status.SUCCESS
+ || mPlaybackStartTime == -1 || mEngineCompleteTime == -1) {
+ logFailure(statusCode);
+ return;
+ }
+
+ final long audioLatency = mPlaybackStartTime - mReceivedTime;
+ final long engineLatency = mEngineStartTime - mRequestProcessingStartTime;
+ final long engineTotal = mEngineCompleteTime - mRequestProcessingStartTime;
+ logSuccess(audioLatency, engineLatency, engineTotal);
+ }
+
+ protected abstract void logFailure(int statusCode);
+ protected abstract void logSuccess(long audioLatency, long engineLatency,
+ long engineTotal);
+
+
+}
diff --git a/core/java/android/speech/tts/AbstractSynthesisCallback.java b/core/java/android/speech/tts/AbstractSynthesisCallback.java
index c7a4af0..91e119b 100644
--- a/core/java/android/speech/tts/AbstractSynthesisCallback.java
+++ b/core/java/android/speech/tts/AbstractSynthesisCallback.java
@@ -15,15 +15,28 @@
*/
package android.speech.tts;
+
/**
* Defines additional methods the synthesis callback must implement that
* are private to the TTS service implementation.
+ *
+ * All of these class methods (with the exception of {@link #stop()}) can be only called on the
+ * synthesis thread, while inside
+ * {@link TextToSpeechService#onSynthesizeText} or {@link TextToSpeechService#onSynthesizeTextV2}.
+ * {@link #stop()} is the exception, it may be called from multiple threads.
*/
abstract class AbstractSynthesisCallback implements SynthesisCallback {
+ /** If true, request comes from V2 TTS interface */
+ protected final boolean mClientIsUsingV2;
+
/**
- * Checks whether the synthesis request completed successfully.
+ * Constructor.
+ * @param clientIsUsingV2 If true, this callback will be used inside
+ * {@link TextToSpeechService#onSynthesizeTextV2} method.
*/
- abstract boolean isDone();
+ AbstractSynthesisCallback(boolean clientIsUsingV2) {
+ mClientIsUsingV2 = clientIsUsingV2;
+ }
/**
* Aborts the speech request.
@@ -31,4 +44,16 @@ abstract class AbstractSynthesisCallback implements SynthesisCallback {
* Can be called from multiple threads.
*/
abstract void stop();
+
+ /**
+ * Get status code for a "stop".
+ *
+ * V2 Clients will receive special status, V1 clients will receive standard error.
+ *
+ * This method should only be called on the synthesis thread,
+ * while in {@link TextToSpeechService#onSynthesizeText}.
+ */
+ int errorCodeOnStop() {
+ return mClientIsUsingV2 ? TextToSpeechClient.Status.STOPPED : TextToSpeech.ERROR;
+ }
}
diff --git a/core/java/android/speech/tts/AudioPlaybackHandler.java b/core/java/android/speech/tts/AudioPlaybackHandler.java
index d63f605..dcf49b0 100644
--- a/core/java/android/speech/tts/AudioPlaybackHandler.java
+++ b/core/java/android/speech/tts/AudioPlaybackHandler.java
@@ -43,7 +43,7 @@ class AudioPlaybackHandler {
return;
}
- item.stop(false);
+ item.stop(TextToSpeechClient.Status.STOPPED);
}
public void enqueue(PlaybackQueueItem item) {
diff --git a/core/java/android/speech/tts/AudioPlaybackQueueItem.java b/core/java/android/speech/tts/AudioPlaybackQueueItem.java
index 1a1fda8..c514639 100644
--- a/core/java/android/speech/tts/AudioPlaybackQueueItem.java
+++ b/core/java/android/speech/tts/AudioPlaybackQueueItem.java
@@ -53,7 +53,7 @@ class AudioPlaybackQueueItem extends PlaybackQueueItem {
dispatcher.dispatchOnStart();
mPlayer = MediaPlayer.create(mContext, mUri);
if (mPlayer == null) {
- dispatcher.dispatchOnError();
+ dispatcher.dispatchOnError(TextToSpeechClient.Status.ERROR_OUTPUT);
return;
}
@@ -83,9 +83,9 @@ class AudioPlaybackQueueItem extends PlaybackQueueItem {
}
if (mFinished) {
- dispatcher.dispatchOnDone();
+ dispatcher.dispatchOnSuccess();
} else {
- dispatcher.dispatchOnError();
+ dispatcher.dispatchOnStop();
}
}
@@ -99,7 +99,7 @@ class AudioPlaybackQueueItem extends PlaybackQueueItem {
}
@Override
- void stop(boolean isError) {
+ void stop(int errorCode) {
mDone.open();
}
}
diff --git a/core/java/android/speech/tts/EventLogTags.logtags b/core/java/android/speech/tts/EventLogTags.logtags
index f8654ad..e209a28 100644
--- a/core/java/android/speech/tts/EventLogTags.logtags
+++ b/core/java/android/speech/tts/EventLogTags.logtags
@@ -4,3 +4,6 @@ option java_package android.speech.tts;
76001 tts_speak_success (engine|3),(caller_uid|1),(caller_pid|1),(length|1),(locale|3),(rate|1),(pitch|1),(engine_latency|2|3),(engine_total|2|3),(audio_latency|2|3)
76002 tts_speak_failure (engine|3),(caller_uid|1),(caller_pid|1),(length|1),(locale|3),(rate|1),(pitch|1)
+
+76003 tts_v2_speak_success (engine|3),(caller_uid|1),(caller_pid|1),(length|1),(request_config|3),(engine_latency|2|3),(engine_total|2|3),(audio_latency|2|3)
+76004 tts_v2_speak_failure (engine|3),(caller_uid|1),(caller_pid|1),(length|1),(request_config|3), (statusCode|1)
diff --git a/core/java/android/speech/tts/EventLogger.java b/core/java/android/speech/tts/EventLogger.java
deleted file mode 100644
index 82ed4dd..0000000
--- a/core/java/android/speech/tts/EventLogger.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.speech.tts;
-
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Log;
-
-/**
- * Writes data about a given speech synthesis request to the event logs.
- * The data that is logged includes the calling app, length of the utterance,
- * speech rate / pitch and the latency and overall time taken.
- *
- * Note that {@link EventLogger#onStopped()} and {@link EventLogger#onError()}
- * might be called from any thread, but on {@link EventLogger#onAudioDataWritten()} and
- * {@link EventLogger#onComplete()} must be called from a single thread
- * (usually the audio playback thread}
- */
-class EventLogger {
- private final SynthesisRequest mRequest;
- private final String mServiceApp;
- private final int mCallerUid;
- private final int mCallerPid;
- private final long mReceivedTime;
- private long mPlaybackStartTime = -1;
- private volatile long mRequestProcessingStartTime = -1;
- private volatile long mEngineStartTime = -1;
- private volatile long mEngineCompleteTime = -1;
-
- private volatile boolean mError = false;
- private volatile boolean mStopped = false;
- private boolean mLogWritten = false;
-
- EventLogger(SynthesisRequest request, int callerUid, int callerPid, String serviceApp) {
- mRequest = request;
- mCallerUid = callerUid;
- mCallerPid = callerPid;
- mServiceApp = serviceApp;
- mReceivedTime = SystemClock.elapsedRealtime();
- }
-
- /**
- * Notifies the logger that this request has been selected from
- * the processing queue for processing. Engine latency / total time
- * is measured from this baseline.
- */
- public void onRequestProcessingStart() {
- mRequestProcessingStartTime = SystemClock.elapsedRealtime();
- }
-
- /**
- * Notifies the logger that a chunk of data has been received from
- * the engine. Might be called multiple times.
- */
- public void onEngineDataReceived() {
- if (mEngineStartTime == -1) {
- mEngineStartTime = SystemClock.elapsedRealtime();
- }
- }
-
- /**
- * Notifies the logger that the engine has finished processing data.
- * Will be called exactly once.
- */
- public void onEngineComplete() {
- mEngineCompleteTime = SystemClock.elapsedRealtime();
- }
-
- /**
- * Notifies the logger that audio playback has started for some section
- * of the synthesis. This is normally some amount of time after the engine
- * has synthesized data and varies depending on utterances and
- * other audio currently in the queue.
- */
- public void onAudioDataWritten() {
- // For now, keep track of only the first chunk of audio
- // that was played.
- if (mPlaybackStartTime == -1) {
- mPlaybackStartTime = SystemClock.elapsedRealtime();
- }
- }
-
- /**
- * Notifies the logger that the current synthesis was stopped.
- * Latency numbers are not reported for stopped syntheses.
- */
- public void onStopped() {
- mStopped = false;
- }
-
- /**
- * Notifies the logger that the current synthesis resulted in
- * an error. This is logged using {@link EventLogTags#writeTtsSpeakFailure}.
- */
- public void onError() {
- mError = true;
- }
-
- /**
- * Notifies the logger that the current synthesis has completed.
- * All available data is not logged.
- */
- public void onWriteData() {
- if (mLogWritten) {
- return;
- } else {
- mLogWritten = true;
- }
-
- long completionTime = SystemClock.elapsedRealtime();
- // onAudioDataWritten() should normally always be called if an
- // error does not occur.
- if (mError || mPlaybackStartTime == -1 || mEngineCompleteTime == -1) {
- EventLogTags.writeTtsSpeakFailure(mServiceApp, mCallerUid, mCallerPid,
- getUtteranceLength(), getLocaleString(),
- mRequest.getSpeechRate(), mRequest.getPitch());
- return;
- }
-
- // We don't report stopped syntheses because their overall
- // total time spent will be innacurate (will not correlate with
- // the length of the utterance).
- if (mStopped) {
- return;
- }
-
- final long audioLatency = mPlaybackStartTime - mReceivedTime;
- final long engineLatency = mEngineStartTime - mRequestProcessingStartTime;
- final long engineTotal = mEngineCompleteTime - mRequestProcessingStartTime;
-
- EventLogTags.writeTtsSpeakSuccess(mServiceApp, mCallerUid, mCallerPid,
- getUtteranceLength(), getLocaleString(),
- mRequest.getSpeechRate(), mRequest.getPitch(),
- engineLatency, engineTotal, audioLatency);
- }
-
- /**
- * @return the length of the utterance for the given synthesis, 0
- * if the utterance was {@code null}.
- */
- private int getUtteranceLength() {
- final String utterance = mRequest.getText();
- return utterance == null ? 0 : utterance.length();
- }
-
- /**
- * Returns a formatted locale string from the synthesis params of the
- * form lang-country-variant.
- */
- private String getLocaleString() {
- StringBuilder sb = new StringBuilder(mRequest.getLanguage());
- if (!TextUtils.isEmpty(mRequest.getCountry())) {
- sb.append('-');
- sb.append(mRequest.getCountry());
-
- if (!TextUtils.isEmpty(mRequest.getVariant())) {
- sb.append('-');
- sb.append(mRequest.getVariant());
- }
- }
-
- return sb.toString();
- }
-
-}
diff --git a/core/java/android/speech/tts/EventLoggerV1.java b/core/java/android/speech/tts/EventLoggerV1.java
new file mode 100644
index 0000000..f484347
--- /dev/null
+++ b/core/java/android/speech/tts/EventLoggerV1.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+import android.text.TextUtils;
+
+/**
+ * Writes data about a given speech synthesis request for V1 API to the event
+ * logs. The data that is logged includes the calling app, length of the
+ * utterance, speech rate / pitch, the latency, and overall time taken.
+ */
+class EventLoggerV1 extends AbstractEventLogger {
+ private final SynthesisRequest mRequest;
+
+ EventLoggerV1(SynthesisRequest request, int callerUid, int callerPid, String serviceApp) {
+ super(callerUid, callerPid, serviceApp);
+ mRequest = request;
+ }
+
+ @Override
+ protected void logFailure(int statusCode) {
+ // We don't report stopped syntheses because their overall
+ // total time spent will be inaccurate (will not correlate with
+ // the length of the utterance).
+ if (statusCode != TextToSpeechClient.Status.STOPPED) {
+ EventLogTags.writeTtsSpeakFailure(mServiceApp, mCallerUid, mCallerPid,
+ getUtteranceLength(), getLocaleString(),
+ mRequest.getSpeechRate(), mRequest.getPitch());
+ }
+ }
+
+ @Override
+ protected void logSuccess(long audioLatency, long engineLatency, long engineTotal) {
+ EventLogTags.writeTtsSpeakSuccess(mServiceApp, mCallerUid, mCallerPid,
+ getUtteranceLength(), getLocaleString(),
+ mRequest.getSpeechRate(), mRequest.getPitch(),
+ engineLatency, engineTotal, audioLatency);
+ }
+
+ /**
+ * @return the length of the utterance for the given synthesis, 0
+ * if the utterance was {@code null}.
+ */
+ private int getUtteranceLength() {
+ final String utterance = mRequest.getText();
+ return utterance == null ? 0 : utterance.length();
+ }
+
+ /**
+ * Returns a formatted locale string from the synthesis params of the
+ * form lang-country-variant.
+ */
+ private String getLocaleString() {
+ StringBuilder sb = new StringBuilder(mRequest.getLanguage());
+ if (!TextUtils.isEmpty(mRequest.getCountry())) {
+ sb.append('-');
+ sb.append(mRequest.getCountry());
+
+ if (!TextUtils.isEmpty(mRequest.getVariant())) {
+ sb.append('-');
+ sb.append(mRequest.getVariant());
+ }
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/core/java/android/speech/tts/EventLoggerV2.java b/core/java/android/speech/tts/EventLoggerV2.java
new file mode 100644
index 0000000..b8e4dae
--- /dev/null
+++ b/core/java/android/speech/tts/EventLoggerV2.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.speech.tts;
+
+
+
+/**
+ * Writes data about a given speech synthesis request for V2 API to the event logs.
+ * The data that is logged includes the calling app, length of the utterance,
+ * synthesis request configuration and the latency and overall time taken.
+ */
+class EventLoggerV2 extends AbstractEventLogger {
+ private final SynthesisRequestV2 mRequest;
+
+ EventLoggerV2(SynthesisRequestV2 request, int callerUid, int callerPid, String serviceApp) {
+ super(callerUid, callerPid, serviceApp);
+ mRequest = request;
+ }
+
+ @Override
+ protected void logFailure(int statusCode) {
+ // We don't report stopped syntheses because their overall
+ // total time spent will be inaccurate (will not correlate with
+ // the length of the utterance).
+ if (statusCode != TextToSpeechClient.Status.STOPPED) {
+ EventLogTags.writeTtsV2SpeakFailure(mServiceApp,
+ mCallerUid, mCallerPid, getUtteranceLength(), getRequestConfigString(), statusCode);
+ }
+ }
+
+ @Override
+ protected void logSuccess(long audioLatency, long engineLatency, long engineTotal) {
+ EventLogTags.writeTtsV2SpeakSuccess(mServiceApp,
+ mCallerUid, mCallerPid, getUtteranceLength(), getRequestConfigString(),
+ engineLatency, engineTotal, audioLatency);
+ }
+
+ /**
+ * @return the length of the utterance for the given synthesis, 0
+ * if the utterance was {@code null}.
+ */
+ private int getUtteranceLength() {
+ final String utterance = mRequest.getText();
+ return utterance == null ? 0 : utterance.length();
+ }
+
+ /**
+ * Returns a string representation of the synthesis request configuration.
+ */
+ private String getRequestConfigString() {
+ // Ensure the bundles are unparceled.
+ mRequest.getVoiceParams().size();
+ mRequest.getAudioParams().size();
+
+ return new StringBuilder(64).append("VoiceName: ").append(mRequest.getVoiceName())
+ .append(" ,VoiceParams: ").append(mRequest.getVoiceParams())
+ .append(" ,SystemParams: ").append(mRequest.getAudioParams())
+ .append("]").toString();
+ }
+}
diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java
index ab8f82f..717aeb6 100644
--- a/core/java/android/speech/tts/FileSynthesisCallback.java
+++ b/core/java/android/speech/tts/FileSynthesisCallback.java
@@ -16,13 +16,10 @@
package android.speech.tts;
import android.media.AudioFormat;
-import android.os.FileUtils;
+import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
import android.util.Log;
-import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
@@ -48,19 +45,39 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
private FileChannel mFileChannel;
+ private final UtteranceProgressDispatcher mDispatcher;
+ private final Object mCallerIdentity;
+
private boolean mStarted = false;
- private boolean mStopped = false;
private boolean mDone = false;
- FileSynthesisCallback(FileChannel fileChannel) {
+ /** Status code of synthesis */
+ protected int mStatusCode;
+
+ FileSynthesisCallback(FileChannel fileChannel, UtteranceProgressDispatcher dispatcher,
+ Object callerIdentity, boolean clientIsUsingV2) {
+ super(clientIsUsingV2);
mFileChannel = fileChannel;
+ mDispatcher = dispatcher;
+ mCallerIdentity = callerIdentity;
+ mStatusCode = TextToSpeechClient.Status.SUCCESS;
}
@Override
void stop() {
synchronized (mStateLock) {
- mStopped = true;
+ if (mDone) {
+ return;
+ }
+ if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
+ return;
+ }
+
+ mStatusCode = TextToSpeechClient.Status.STOPPED;
cleanUp();
+ if (mDispatcher != null) {
+ mDispatcher.dispatchOnStop();
+ }
}
}
@@ -75,14 +92,8 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
* Must be called while holding the monitor on {@link #mStateLock}.
*/
private void closeFile() {
- try {
- if (mFileChannel != null) {
- mFileChannel.close();
- mFileChannel = null;
- }
- } catch (IOException ex) {
- Log.e(TAG, "Failed to close output file descriptor", ex);
- }
+ // File will be closed by the SpeechItem in the speech service.
+ mFileChannel = null;
}
@Override
@@ -91,38 +102,46 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
}
@Override
- boolean isDone() {
- return mDone;
- }
-
- @Override
public int start(int sampleRateInHz, int audioFormat, int channelCount) {
if (DBG) {
Log.d(TAG, "FileSynthesisRequest.start(" + sampleRateInHz + "," + audioFormat
+ "," + channelCount + ")");
}
+ FileChannel fileChannel = null;
synchronized (mStateLock) {
- if (mStopped) {
+ if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
if (DBG) Log.d(TAG, "Request has been aborted.");
+ return errorCodeOnStop();
+ }
+ if (mStatusCode != TextToSpeechClient.Status.SUCCESS) {
+ if (DBG) Log.d(TAG, "Error was raised");
return TextToSpeech.ERROR;
}
if (mStarted) {
- cleanUp();
- throw new IllegalArgumentException("FileSynthesisRequest.start() called twice");
+ Log.e(TAG, "Start called twice");
+ return TextToSpeech.ERROR;
}
mStarted = true;
mSampleRateInHz = sampleRateInHz;
mAudioFormat = audioFormat;
mChannelCount = channelCount;
- try {
- mFileChannel.write(ByteBuffer.allocate(WAV_HEADER_LENGTH));
+ if (mDispatcher != null) {
+ mDispatcher.dispatchOnStart();
+ }
+ fileChannel = mFileChannel;
+ }
+
+ try {
+ fileChannel.write(ByteBuffer.allocate(WAV_HEADER_LENGTH));
return TextToSpeech.SUCCESS;
- } catch (IOException ex) {
- Log.e(TAG, "Failed to write wav header to output file descriptor" + ex);
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to write wav header to output file descriptor", ex);
+ synchronized (mStateLock) {
cleanUp();
- return TextToSpeech.ERROR;
+ mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT;
}
+ return TextToSpeech.ERROR;
}
}
@@ -132,66 +151,128 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
Log.d(TAG, "FileSynthesisRequest.audioAvailable(" + buffer + "," + offset
+ "," + length + ")");
}
+ FileChannel fileChannel = null;
synchronized (mStateLock) {
- if (mStopped) {
+ if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
if (DBG) Log.d(TAG, "Request has been aborted.");
+ return errorCodeOnStop();
+ }
+ if (mStatusCode != TextToSpeechClient.Status.SUCCESS) {
+ if (DBG) Log.d(TAG, "Error was raised");
return TextToSpeech.ERROR;
}
if (mFileChannel == null) {
Log.e(TAG, "File not open");
+ mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT;
return TextToSpeech.ERROR;
}
- try {
- mFileChannel.write(ByteBuffer.wrap(buffer, offset, length));
- return TextToSpeech.SUCCESS;
- } catch (IOException ex) {
- Log.e(TAG, "Failed to write to output file descriptor", ex);
- cleanUp();
+ if (!mStarted) {
+ Log.e(TAG, "Start method was not called");
return TextToSpeech.ERROR;
}
+ fileChannel = mFileChannel;
+ }
+
+ try {
+ fileChannel.write(ByteBuffer.wrap(buffer, offset, length));
+ return TextToSpeech.SUCCESS;
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to write to output file descriptor", ex);
+ synchronized (mStateLock) {
+ cleanUp();
+ mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT;
+ }
+ return TextToSpeech.ERROR;
}
}
@Override
public int done() {
if (DBG) Log.d(TAG, "FileSynthesisRequest.done()");
+ FileChannel fileChannel = null;
+
+ int sampleRateInHz = 0;
+ int audioFormat = 0;
+ int channelCount = 0;
+
synchronized (mStateLock) {
if (mDone) {
- if (DBG) Log.d(TAG, "Duplicate call to done()");
- // This preserves existing behaviour. Earlier, if done was called twice
- // we'd return ERROR because mFile == null and we'd add to logspam.
+ Log.w(TAG, "Duplicate call to done()");
+ // This is not an error that would prevent synthesis. Hence no
+ // setStatusCode is set.
return TextToSpeech.ERROR;
}
- if (mStopped) {
+ if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
if (DBG) Log.d(TAG, "Request has been aborted.");
+ return errorCodeOnStop();
+ }
+ if (mDispatcher != null && mStatusCode != TextToSpeechClient.Status.SUCCESS &&
+ mStatusCode != TextToSpeechClient.Status.STOPPED) {
+ mDispatcher.dispatchOnError(mStatusCode);
return TextToSpeech.ERROR;
}
if (mFileChannel == null) {
Log.e(TAG, "File not open");
return TextToSpeech.ERROR;
}
- try {
- // Write WAV header at start of file
- mFileChannel.position(0);
- int dataLength = (int) (mFileChannel.size() - WAV_HEADER_LENGTH);
- mFileChannel.write(
- makeWavHeader(mSampleRateInHz, mAudioFormat, mChannelCount, dataLength));
+ mDone = true;
+ fileChannel = mFileChannel;
+ sampleRateInHz = mSampleRateInHz;
+ audioFormat = mAudioFormat;
+ channelCount = mChannelCount;
+ }
+
+ try {
+ // Write WAV header at start of file
+ fileChannel.position(0);
+ int dataLength = (int) (fileChannel.size() - WAV_HEADER_LENGTH);
+ fileChannel.write(
+ makeWavHeader(sampleRateInHz, audioFormat, channelCount, dataLength));
+
+ synchronized (mStateLock) {
closeFile();
- mDone = true;
+ if (mDispatcher != null) {
+ mDispatcher.dispatchOnSuccess();
+ }
return TextToSpeech.SUCCESS;
- } catch (IOException ex) {
- Log.e(TAG, "Failed to write to output file descriptor", ex);
+ }
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to write to output file descriptor", ex);
+ synchronized (mStateLock) {
cleanUp();
- return TextToSpeech.ERROR;
}
+ return TextToSpeech.ERROR;
}
}
@Override
public void error() {
+ error(TextToSpeechClient.Status.ERROR_SYNTHESIS);
+ }
+
+ @Override
+ public void error(int errorCode) {
if (DBG) Log.d(TAG, "FileSynthesisRequest.error()");
synchronized (mStateLock) {
+ if (mDone) {
+ return;
+ }
cleanUp();
+ mStatusCode = errorCode;
+ }
+ }
+
+ @Override
+ public boolean hasStarted() {
+ synchronized (mStateLock) {
+ return mStarted;
+ }
+ }
+
+ @Override
+ public boolean hasFinished() {
+ synchronized (mStateLock) {
+ return mDone;
}
}
@@ -225,4 +306,16 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
return header;
}
+ @Override
+ public int fallback() {
+ synchronized (mStateLock) {
+ if (hasStarted() || hasFinished()) {
+ return TextToSpeech.ERROR;
+ }
+
+ mDispatcher.dispatchOnFallback();
+ mStatusCode = TextToSpeechClient.Status.SUCCESS;
+ return TextToSpeechClient.Status.SUCCESS;
+ }
+ }
}
diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
index f0287d4..3c808ff 100644
--- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
@@ -15,13 +15,53 @@
*/
package android.speech.tts;
+import android.speech.tts.VoiceInfo;
+
/**
* Interface for callbacks from TextToSpeechService
*
* {@hide}
*/
oneway interface ITextToSpeechCallback {
+ /**
+ * Tells the client that the synthesis has started.
+ *
+ * @param utteranceId Unique id identifying synthesis request.
+ */
void onStart(String utteranceId);
- void onDone(String utteranceId);
- void onError(String utteranceId);
+
+ /**
+ * Tells the client that the synthesis has finished.
+ *
+ * @param utteranceId Unique id identifying synthesis request.
+ */
+ void onSuccess(String utteranceId);
+
+ /**
+ * Tells the client that the synthesis was stopped.
+ *
+ * @param utteranceId Unique id identifying synthesis request.
+ */
+ void onStop(String utteranceId);
+
+ /**
+ * Tells the client that the synthesis failed, and fallback synthesis will be attempted.
+ *
+ * @param utteranceId Unique id identifying synthesis request.
+ */
+ void onFallback(String utteranceId);
+
+ /**
+ * Tells the client that the synthesis has failed.
+ *
+ * @param utteranceId Unique id identifying synthesis request.
+ * @param errorCode One of the values from
+ * {@link android.speech.tts.v2.TextToSpeechClient.Status}.
+ */
+ void onError(String utteranceId, int errorCode);
+
+ /**
+ * Inform the client that set of available voices changed.
+ */
+ void onVoicesInfoChange(in List<VoiceInfo> voices);
}
diff --git a/core/java/android/speech/tts/ITextToSpeechService.aidl b/core/java/android/speech/tts/ITextToSpeechService.aidl
index b7bc70c..9cf49ff 100644
--- a/core/java/android/speech/tts/ITextToSpeechService.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechService.aidl
@@ -20,6 +20,8 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.speech.tts.ITextToSpeechCallback;
+import android.speech.tts.VoiceInfo;
+import android.speech.tts.SynthesisRequestV2;
/**
* Interface for TextToSpeech to talk to TextToSpeechService.
@@ -70,9 +72,10 @@ interface ITextToSpeechService {
* TextToSpeech object.
* @param duration Number of milliseconds of silence to play.
* @param queueMode Determines what to do to requests already in the queue.
- * @param param Request parameters.
+ * @param utteranceId Unique id used to identify this request in callbacks.
*/
- int playSilence(in IBinder callingInstance, in long duration, in int queueMode, in Bundle params);
+ int playSilence(in IBinder callingInstance, in long duration, in int queueMode,
+ in String utteranceId);
/**
* Checks whether the service is currently playing some audio.
@@ -90,7 +93,6 @@ interface ITextToSpeechService {
/**
* Returns the language, country and variant currently being used by the TTS engine.
- *
* Can be called from multiple threads.
*
* @return A 3-element array, containing language (ISO 3-letter code),
@@ -99,7 +101,7 @@ interface ITextToSpeechService {
* be empty too.
*/
String[] getLanguage();
-
+
/**
* Returns a default TTS language, country and variant as set by the user.
*
@@ -111,7 +113,7 @@ interface ITextToSpeechService {
* be empty too.
*/
String[] getClientDefaultLanguage();
-
+
/**
* Checks whether the engine supports a given language.
*
@@ -137,7 +139,7 @@ interface ITextToSpeechService {
* @param country ISO-3 country code. May be empty or null.
* @param variant Language variant. May be empty or null.
* @return An array of strings containing the set of features supported for
- * the supplied locale. The array of strings must not contain
+ * the supplied locale. The array of strings must not contain
* duplicates.
*/
String[] getFeaturesForLanguage(in String lang, in String country, in String variant);
@@ -169,4 +171,44 @@ interface ITextToSpeechService {
*/
void setCallback(in IBinder caller, ITextToSpeechCallback cb);
+ /**
+ * Tells the engine to synthesize some speech and play it back.
+ *
+ * @param callingInstance a binder representing the identity of the calling
+ * TextToSpeech object.
+ * @param text The text to synthesize.
+ * @param queueMode Determines what to do to requests already in the queue.
+ * @param request Request parameters.
+ */
+ int speakV2(in IBinder callingInstance, in SynthesisRequestV2 request);
+
+ /**
+ * Tells the engine to synthesize some speech and write it to a file.
+ *
+ * @param callingInstance a binder representing the identity of the calling
+ * TextToSpeech object.
+ * @param text The text to synthesize.
+ * @param fileDescriptor The file descriptor to write the synthesized audio to. Has to be
+ writable.
+ * @param request Request parameters.
+ */
+ int synthesizeToFileDescriptorV2(in IBinder callingInstance,
+ in ParcelFileDescriptor fileDescriptor, in SynthesisRequestV2 request);
+
+ /**
+ * Plays an existing audio resource. V2 version
+ *
+ * @param callingInstance a binder representing the identity of the calling
+ * TextToSpeech object.
+ * @param audioUri URI for the audio resource (a file or android.resource URI)
+ * @param utteranceId Unique identifier.
+ * @param audioParameters Parameters for audio playback (from {@link SynthesisRequestV2}).
+ */
+ int playAudioV2(in IBinder callingInstance, in Uri audioUri, in String utteranceId,
+ in Bundle audioParameters);
+
+ /**
+ * Request the list of available voices from the service.
+ */
+ List<VoiceInfo> getVoicesInfo();
}
diff --git a/core/java/android/speech/tts/PlaybackQueueItem.java b/core/java/android/speech/tts/PlaybackQueueItem.java
index d0957ff..b2e323e 100644
--- a/core/java/android/speech/tts/PlaybackQueueItem.java
+++ b/core/java/android/speech/tts/PlaybackQueueItem.java
@@ -22,6 +22,16 @@ abstract class PlaybackQueueItem implements Runnable {
return mDispatcher;
}
+ @Override
public abstract void run();
- abstract void stop(boolean isError);
+
+ /**
+ * Stop the playback.
+ *
+ * @param errorCode Cause of the stop. Can be either one of the error codes from
+ * {@link android.speech.tts.TextToSpeechClient.Status} or
+ * {@link android.speech.tts.TextToSpeechClient.Status#STOPPED}
+ * if stopped on a client request.
+ */
+ abstract void stop(int errorCode);
}
diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
index c99f201..e345e89 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
@@ -55,20 +55,20 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
private final AudioPlaybackHandler mAudioTrackHandler;
// A request "token", which will be non null after start() has been called.
private SynthesisPlaybackQueueItem mItem = null;
- // Whether this request has been stopped. This is useful for keeping
- // track whether stop() has been called before start(). In all other cases,
- // a non-null value of mItem will provide the same information.
- private boolean mStopped = false;
private volatile boolean mDone = false;
+ /** Status code of synthesis */
+ protected int mStatusCode;
+
private final UtteranceProgressDispatcher mDispatcher;
private final Object mCallerIdentity;
- private final EventLogger mLogger;
+ private final AbstractEventLogger mLogger;
PlaybackSynthesisCallback(int streamType, float volume, float pan,
AudioPlaybackHandler audioTrackHandler, UtteranceProgressDispatcher dispatcher,
- Object callerIdentity, EventLogger logger) {
+ Object callerIdentity, AbstractEventLogger logger, boolean clientIsUsingV2) {
+ super(clientIsUsingV2);
mStreamType = streamType;
mVolume = volume;
mPan = pan;
@@ -76,28 +76,25 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
mDispatcher = dispatcher;
mCallerIdentity = callerIdentity;
mLogger = logger;
+ mStatusCode = TextToSpeechClient.Status.SUCCESS;
}
@Override
void stop() {
- stopImpl(false);
- }
-
- void stopImpl(boolean wasError) {
if (DBG) Log.d(TAG, "stop()");
- // Note that mLogger.mError might be true too at this point.
- mLogger.onStopped();
-
SynthesisPlaybackQueueItem item;
synchronized (mStateLock) {
- if (mStopped) {
+ if (mDone) {
+ return;
+ }
+ if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
Log.w(TAG, "stop() called twice");
return;
}
item = mItem;
- mStopped = true;
+ mStatusCode = TextToSpeechClient.Status.STOPPED;
}
if (item != null) {
@@ -105,19 +102,15 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
// point it will write an additional buffer to the item - but we
// won't worry about that because the audio playback queue will be cleared
// soon after (see SynthHandler#stop(String).
- item.stop(wasError);
+ item.stop(TextToSpeechClient.Status.STOPPED);
} else {
// This happens when stop() or error() were called before start() was.
// In all other cases, mAudioTrackHandler.stop() will
// result in onSynthesisDone being called, and we will
// write data there.
- mLogger.onWriteData();
-
- if (wasError) {
- // We have to dispatch the error ourselves.
- mDispatcher.dispatchOnError();
- }
+ mLogger.onCompleted(TextToSpeechClient.Status.STOPPED);
+ mDispatcher.dispatchOnStop();
}
}
@@ -129,26 +122,42 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
}
@Override
- boolean isDone() {
- return mDone;
+ public boolean hasStarted() {
+ synchronized (mStateLock) {
+ return mItem != null;
+ }
}
@Override
- public int start(int sampleRateInHz, int audioFormat, int channelCount) {
- if (DBG) {
- Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat
- + "," + channelCount + ")");
+ public boolean hasFinished() {
+ synchronized (mStateLock) {
+ return mDone;
}
+ }
+
+ @Override
+ public int start(int sampleRateInHz, int audioFormat, int channelCount) {
+ if (DBG) Log.d(TAG, "start(" + sampleRateInHz + "," + audioFormat + "," + channelCount
+ + ")");
int channelConfig = BlockingAudioTrack.getChannelConfig(channelCount);
- if (channelConfig == 0) {
- Log.e(TAG, "Unsupported number of channels :" + channelCount);
- return TextToSpeech.ERROR;
- }
synchronized (mStateLock) {
- if (mStopped) {
+ if (channelConfig == 0) {
+ Log.e(TAG, "Unsupported number of channels :" + channelCount);
+ mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT;
+ return TextToSpeech.ERROR;
+ }
+ if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
if (DBG) Log.d(TAG, "stop() called before start(), returning.");
+ return errorCodeOnStop();
+ }
+ if (mStatusCode != TextToSpeechClient.Status.SUCCESS) {
+ if (DBG) Log.d(TAG, "Error was raised");
+ return TextToSpeech.ERROR;
+ }
+ if (mItem != null) {
+ Log.e(TAG, "Start called twice");
return TextToSpeech.ERROR;
}
SynthesisPlaybackQueueItem item = new SynthesisPlaybackQueueItem(
@@ -161,13 +170,11 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
return TextToSpeech.SUCCESS;
}
-
@Override
public int audioAvailable(byte[] buffer, int offset, int length) {
- if (DBG) {
- Log.d(TAG, "audioAvailable(byte[" + buffer.length + "],"
- + offset + "," + length + ")");
- }
+ if (DBG) Log.d(TAG, "audioAvailable(byte[" + buffer.length + "]," + offset + "," + length
+ + ")");
+
if (length > getMaxBufferSize() || length <= 0) {
throw new IllegalArgumentException("buffer is too large or of zero length (" +
+ length + " bytes)");
@@ -175,9 +182,17 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
SynthesisPlaybackQueueItem item = null;
synchronized (mStateLock) {
- if (mItem == null || mStopped) {
+ if (mItem == null) {
+ mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT;
return TextToSpeech.ERROR;
}
+ if (mStatusCode != TextToSpeechClient.Status.SUCCESS) {
+ if (DBG) Log.d(TAG, "Error was raised");
+ return TextToSpeech.ERROR;
+ }
+ if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
+ return errorCodeOnStop();
+ }
item = mItem;
}
@@ -190,11 +205,13 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
try {
item.put(bufferCopy);
} catch (InterruptedException ie) {
- return TextToSpeech.ERROR;
+ synchronized (mStateLock) {
+ mStatusCode = TextToSpeechClient.Status.ERROR_OUTPUT;
+ return TextToSpeech.ERROR;
+ }
}
mLogger.onEngineDataReceived();
-
return TextToSpeech.SUCCESS;
}
@@ -202,35 +219,74 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
public int done() {
if (DBG) Log.d(TAG, "done()");
+ int statusCode = 0;
SynthesisPlaybackQueueItem item = null;
synchronized (mStateLock) {
if (mDone) {
Log.w(TAG, "Duplicate call to done()");
+ // Not an error that would prevent synthesis. Hence no
+ // setStatusCode
return TextToSpeech.ERROR;
}
-
+ if (mStatusCode == TextToSpeechClient.Status.STOPPED) {
+ if (DBG) Log.d(TAG, "Request has been aborted.");
+ return errorCodeOnStop();
+ }
mDone = true;
if (mItem == null) {
+ // .done() was called before .start. Treat it as successful synthesis
+ // for a client, despite service bad implementation.
+ Log.w(TAG, "done() was called before start() call");
+ if (mStatusCode == TextToSpeechClient.Status.SUCCESS) {
+ mDispatcher.dispatchOnSuccess();
+ } else {
+ mDispatcher.dispatchOnError(mStatusCode);
+ }
+ mLogger.onEngineComplete();
return TextToSpeech.ERROR;
}
item = mItem;
+ statusCode = mStatusCode;
}
- item.done();
+ // Signal done or error to item
+ if (statusCode == TextToSpeechClient.Status.SUCCESS) {
+ item.done();
+ } else {
+ item.stop(statusCode);
+ }
mLogger.onEngineComplete();
-
return TextToSpeech.SUCCESS;
}
@Override
public void error() {
+ error(TextToSpeechClient.Status.ERROR_SYNTHESIS);
+ }
+
+ @Override
+ public void error(int errorCode) {
if (DBG) Log.d(TAG, "error() [will call stop]");
- // Currently, this call will not be logged if error( ) is called
- // before start.
- mLogger.onError();
- stopImpl(true);
+ synchronized (mStateLock) {
+ if (mDone) {
+ return;
+ }
+ mStatusCode = errorCode;
+ }
}
+ @Override
+ public int fallback() {
+ synchronized (mStateLock) {
+ if (hasStarted() || hasFinished()) {
+ return TextToSpeech.ERROR;
+ }
+
+ mDispatcher.dispatchOnFallback();
+ mStatusCode = TextToSpeechClient.Status.SUCCESS;
+ return TextToSpeechClient.Status.SUCCESS;
+ }
+ }
}
diff --git a/core/java/android/speech/tts/RequestConfig.java b/core/java/android/speech/tts/RequestConfig.java
new file mode 100644
index 0000000..4b5385f
--- /dev/null
+++ b/core/java/android/speech/tts/RequestConfig.java
@@ -0,0 +1,213 @@
+package android.speech.tts;
+
+import android.media.AudioManager;
+import android.os.Bundle;
+
+/**
+ * Synthesis request configuration.
+ *
+ * This class is immutable, and can only be constructed using
+ * {@link RequestConfig.Builder}.
+ */
+public final class RequestConfig {
+
+ /** Builder for constructing RequestConfig objects. */
+ public static final class Builder {
+ private VoiceInfo mCurrentVoiceInfo;
+ private Bundle mVoiceParams;
+ private Bundle mAudioParams;
+
+ Builder(VoiceInfo currentVoiceInfo, Bundle voiceParams, Bundle audioParams) {
+ mCurrentVoiceInfo = currentVoiceInfo;
+ mVoiceParams = voiceParams;
+ mAudioParams = audioParams;
+ }
+
+ /**
+ * Create new RequestConfig builder.
+ */
+ public static Builder newBuilder() {
+ return new Builder(null, new Bundle(), new Bundle());
+ }
+
+ /**
+ * Create new RequestConfig builder.
+ * @param prototype
+ * Prototype of new RequestConfig. Copies all fields of the
+ * prototype to the constructed object.
+ */
+ public static Builder newBuilder(RequestConfig prototype) {
+ return new Builder(prototype.mCurrentVoiceInfo,
+ (Bundle)prototype.mVoiceParams.clone(),
+ (Bundle)prototype.mAudioParams.clone());
+ }
+
+ /** Set voice for request. Will reset voice parameters to the defaults. */
+ public Builder setVoice(VoiceInfo voice) {
+ mCurrentVoiceInfo = voice;
+ mVoiceParams = (Bundle)voice.getParamsWithDefaults().clone();
+ return this;
+ }
+
+ /**
+ * Set request voice parameter.
+ *
+ * @param paramName
+ * The name of the parameter. It has to be one of the keys
+ * from {@link VoiceInfo#getParamsWithDefaults()}
+ * @param value
+ * Value of the parameter. Its type can be one of: Integer, Float,
+ * Boolean, String, VoiceInfo (will be set as a String, result of a call to
+ * the {@link VoiceInfo#getName()}) or byte[]. It has to be of the same type
+ * as the default value from {@link VoiceInfo#getParamsWithDefaults()}
+ * for that parameter.
+ * @throws IllegalArgumentException
+ * If paramName is not a valid parameter name or its value is of a wrong
+ * type.
+ * @throws IllegalStateException
+ * If no voice is set.
+ */
+ public Builder setVoiceParam(String paramName, Object value){
+ if (mCurrentVoiceInfo == null) {
+ throw new IllegalStateException(
+ "Couldn't set voice parameter, no voice is set");
+ }
+ Object defaultValue = mCurrentVoiceInfo.getParamsWithDefaults().get(paramName);
+ if (defaultValue == null) {
+ throw new IllegalArgumentException(
+ "Parameter \"" + paramName + "\" is not available in set voice with " +
+ "name: " + mCurrentVoiceInfo.getName());
+ }
+
+ // If it's VoiceInfo, get its name
+ if (value instanceof VoiceInfo) {
+ value = ((VoiceInfo)value).getName();
+ }
+
+ // Check type information
+ if (!defaultValue.getClass().equals(value.getClass())) {
+ throw new IllegalArgumentException(
+ "Parameter \"" + paramName +"\" is of different type. Value passed has " +
+ "type " + value.getClass().getSimpleName() + " but should have " +
+ "type " + defaultValue.getClass().getSimpleName());
+ }
+
+ setParam(mVoiceParams, paramName, value);
+ return this;
+ }
+
+ /**
+ * Set request audio parameter.
+ *
+ * Doesn't requires a set voice.
+ *
+ * @param paramName
+ * Name of parameter.
+ * @param value
+ * Value of parameter. Its type can be one of: Integer, Float, Boolean, String
+ * or byte[].
+ */
+ public Builder setAudioParam(String paramName, Object value) {
+ setParam(mAudioParams, paramName, value);
+ return this;
+ }
+
+ /**
+ * Set the {@link TextToSpeechClient.Params#AUDIO_PARAM_STREAM} audio parameter.
+ *
+ * @param streamId One of the STREAM_ constants defined in {@link AudioManager}.
+ */
+ public void setAudioParamStream(int streamId) {
+ setAudioParam(TextToSpeechClient.Params.AUDIO_PARAM_STREAM, streamId);
+ }
+
+ /**
+ * Set the {@link TextToSpeechClient.Params#AUDIO_PARAM_VOLUME} audio parameter.
+ *
+ * @param volume Float in range of 0.0 to 1.0.
+ */
+ public void setAudioParamVolume(float volume) {
+ setAudioParam(TextToSpeechClient.Params.AUDIO_PARAM_VOLUME, volume);
+ }
+
+ /**
+ * Set the {@link TextToSpeechClient.Params#AUDIO_PARAM_PAN} audio parameter.
+ *
+ * @param pan Float in range of -1.0 to +1.0.
+ */
+ public void setAudioParamPan(float pan) {
+ setAudioParam(TextToSpeechClient.Params.AUDIO_PARAM_PAN, pan);
+ }
+
+ private void setParam(Bundle bundle, String featureName, Object value) {
+ if (value instanceof String) {
+ bundle.putString(featureName, (String)value);
+ } else if(value instanceof byte[]) {
+ bundle.putByteArray(featureName, (byte[])value);
+ } else if(value instanceof Integer) {
+ bundle.putInt(featureName, (Integer)value);
+ } else if(value instanceof Float) {
+ bundle.putFloat(featureName, (Float)value);
+ } else if(value instanceof Double) {
+ bundle.putFloat(featureName, (Float)value);
+ } else if(value instanceof Boolean) {
+ bundle.putBoolean(featureName, (Boolean)value);
+ } else {
+ throw new IllegalArgumentException("Illegal type of object");
+ }
+ return;
+ }
+
+ /**
+ * Build new RequestConfig instance.
+ */
+ public RequestConfig build() {
+ RequestConfig config =
+ new RequestConfig(mCurrentVoiceInfo, mVoiceParams, mAudioParams);
+ return config;
+ }
+ }
+
+ private RequestConfig(VoiceInfo voiceInfo, Bundle voiceParams, Bundle audioParams) {
+ mCurrentVoiceInfo = voiceInfo;
+ mVoiceParams = voiceParams;
+ mAudioParams = audioParams;
+ }
+
+ /**
+ * Currently set voice.
+ */
+ private final VoiceInfo mCurrentVoiceInfo;
+
+ /**
+ * Voice parameters bundle.
+ */
+ private final Bundle mVoiceParams;
+
+ /**
+ * Audio parameters bundle.
+ */
+ private final Bundle mAudioParams;
+
+ /**
+ * @return Currently set request voice.
+ */
+ public VoiceInfo getVoice() {
+ return mCurrentVoiceInfo;
+ }
+
+ /**
+ * @return Request audio parameters.
+ */
+ public Bundle getAudioParams() {
+ return mAudioParams;
+ }
+
+ /**
+ * @return Request voice parameters.
+ */
+ public Bundle getVoiceParams() {
+ return mVoiceParams;
+ }
+
+}
diff --git a/core/java/android/speech/tts/RequestConfigHelper.java b/core/java/android/speech/tts/RequestConfigHelper.java
new file mode 100644
index 0000000..b25c985
--- /dev/null
+++ b/core/java/android/speech/tts/RequestConfigHelper.java
@@ -0,0 +1,170 @@
+package android.speech.tts;
+
+import android.speech.tts.TextToSpeechClient.EngineStatus;
+
+import java.util.Locale;
+
+/**
+ * Set of common heuristics for selecting {@link VoiceInfo} from
+ * {@link TextToSpeechClient#getEngineStatus()} output.
+ */
+public final class RequestConfigHelper {
+ private RequestConfigHelper() {}
+
+ /**
+ * Interface for scoring VoiceInfo object.
+ */
+ public static interface VoiceScorer {
+ /**
+ * Score VoiceInfo. If the score is less than or equal to zero, that voice is discarded.
+ * If two voices have same desired primary characteristics (highest quality, lowest
+ * latency or others), the one with the higher score is selected.
+ */
+ public int scoreVoice(VoiceInfo voiceInfo);
+ }
+
+ /**
+ * Score positively voices that exactly match the locale supplied to the constructor.
+ */
+ public static final class ExactLocaleMatcher implements VoiceScorer {
+ private final Locale mLocale;
+
+ /**
+ * Score positively voices that exactly match the given locale
+ * @param locale Reference locale. If null, the default locale will be used.
+ */
+ public ExactLocaleMatcher(Locale locale) {
+ if (locale == null) {
+ mLocale = Locale.getDefault();
+ } else {
+ mLocale = locale;
+ }
+ }
+ @Override
+ public int scoreVoice(VoiceInfo voiceInfo) {
+ return mLocale.equals(voiceInfo.getLocale()) ? 1 : 0;
+ }
+ }
+
+ /**
+ * Score positively voices that match exactly the given locale (score 3)
+ * or that share same language and country (score 2), or that share just a language (score 1).
+ */
+ public static final class LanguageMatcher implements VoiceScorer {
+ private final Locale mLocale;
+
+ /**
+ * Score positively voices with similar locale.
+ * @param locale Reference locale. If null, default will be used.
+ */
+ public LanguageMatcher(Locale locale) {
+ if (locale == null) {
+ mLocale = Locale.getDefault();
+ } else {
+ mLocale = locale;
+ }
+ }
+
+ @Override
+ public int scoreVoice(VoiceInfo voiceInfo) {
+ final Locale voiceLocale = voiceInfo.getLocale();
+ if (mLocale.equals(voiceLocale)) {
+ return 3;
+ } else {
+ if (mLocale.getLanguage().equals(voiceLocale.getLanguage())) {
+ if (mLocale.getCountry().equals(voiceLocale.getCountry())) {
+ return 2;
+ }
+ return 1;
+ }
+ return 0;
+ }
+ }
+ }
+
+ /**
+ * Get the highest quality voice from voices that score more than zero from the passed scorer.
+ * If there is more than one voice with the same highest quality, then this method returns one
+ * with the highest score. If they share same score as well, one with the lower index in the
+ * voices list is returned.
+ *
+ * @param engineStatus
+ * Voices status received from a {@link TextToSpeechClient#getEngineStatus()} call.
+ * @param voiceScorer
+ * Used to discard unsuitable voices and help settle cases where more than
+ * one voice has the desired characteristic.
+ * @param hasToBeEmbedded
+ * If true, require the voice to be an embedded voice (no network
+ * access will be required for synthesis).
+ */
+ private static VoiceInfo getHighestQualityVoice(EngineStatus engineStatus,
+ VoiceScorer voiceScorer, boolean hasToBeEmbedded) {
+ VoiceInfo bestVoice = null;
+ int bestScoreMatch = 1;
+ int bestVoiceQuality = 0;
+
+ for (VoiceInfo voice : engineStatus.getVoices()) {
+ int score = voiceScorer.scoreVoice(voice);
+ if (score <= 0 || hasToBeEmbedded && voice.getRequiresNetworkConnection()
+ || voice.getQuality() < bestVoiceQuality) {
+ continue;
+ }
+
+ if (bestVoice == null ||
+ voice.getQuality() > bestVoiceQuality ||
+ score > bestScoreMatch) {
+ bestVoice = voice;
+ bestScoreMatch = score;
+ bestVoiceQuality = voice.getQuality();
+ }
+ }
+ return bestVoice;
+ }
+
+ /**
+ * Get highest quality voice.
+ *
+ * Highest quality voice is selected from voices that score more than zero from the passed
+ * scorer. If there is more than one voice with the same highest quality, then this method
+ * will return one with the highest score. If they share same score as well, one with the lower
+ * index in the voices list is returned.
+
+ * @param engineStatus
+ * Voices status received from a {@link TextToSpeechClient#getEngineStatus()} call.
+ * @param hasToBeEmbedded
+ * If true, require the voice to be an embedded voice (no network
+ * access will be required for synthesis).
+ * @param voiceScorer
+ * Scorer is used to discard unsuitable voices and help settle cases where more than
+ * one voice has highest quality.
+ * @return RequestConfig with selected voice or null if suitable voice was not found.
+ */
+ public static RequestConfig highestQuality(EngineStatus engineStatus,
+ boolean hasToBeEmbedded, VoiceScorer voiceScorer) {
+ VoiceInfo voice = getHighestQualityVoice(engineStatus, voiceScorer, hasToBeEmbedded);
+ if (voice == null) {
+ return null;
+ }
+ return RequestConfig.Builder.newBuilder().setVoice(voice).build();
+ }
+
+ /**
+ * Get highest quality voice for the default locale.
+ *
+ * Call {@link #highestQuality(EngineStatus, boolean, VoiceScorer)} with
+ * {@link LanguageMatcher} set to device default locale.
+ *
+ * @param engineStatus
+ * Voices status received from a {@link TextToSpeechClient#getEngineStatus()} call.
+ * @param hasToBeEmbedded
+ * If true, require the voice to be an embedded voice (no network
+ * access will be required for synthesis).
+ * @return RequestConfig with selected voice or null if suitable voice was not found.
+ */
+ public static RequestConfig highestQuality(EngineStatus engineStatus,
+ boolean hasToBeEmbedded) {
+ return highestQuality(engineStatus, hasToBeEmbedded,
+ new LanguageMatcher(Locale.getDefault()));
+ }
+
+}
diff --git a/core/java/android/speech/tts/SilencePlaybackQueueItem.java b/core/java/android/speech/tts/SilencePlaybackQueueItem.java
index a5e47ae..88b7c70 100644
--- a/core/java/android/speech/tts/SilencePlaybackQueueItem.java
+++ b/core/java/android/speech/tts/SilencePlaybackQueueItem.java
@@ -17,7 +17,6 @@ package android.speech.tts;
import android.os.ConditionVariable;
import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
-import android.util.Log;
class SilencePlaybackQueueItem extends PlaybackQueueItem {
private final ConditionVariable mCondVar = new ConditionVariable();
@@ -32,14 +31,20 @@ class SilencePlaybackQueueItem extends PlaybackQueueItem {
@Override
public void run() {
getDispatcher().dispatchOnStart();
+ boolean wasStopped = false;
if (mSilenceDurationMs > 0) {
- mCondVar.block(mSilenceDurationMs);
+ wasStopped = mCondVar.block(mSilenceDurationMs);
}
- getDispatcher().dispatchOnDone();
+ if (wasStopped) {
+ getDispatcher().dispatchOnStop();
+ } else {
+ getDispatcher().dispatchOnSuccess();
+ }
+
}
@Override
- void stop(boolean isError) {
+ void stop(int errorCode) {
mCondVar.open();
}
}
diff --git a/core/java/android/speech/tts/SynthesisCallback.java b/core/java/android/speech/tts/SynthesisCallback.java
index f98bb09..bc2f239 100644
--- a/core/java/android/speech/tts/SynthesisCallback.java
+++ b/core/java/android/speech/tts/SynthesisCallback.java
@@ -26,7 +26,9 @@ package android.speech.tts;
* indicate that an error has occurred, but if the call is made after a call
* to {@link #done}, it might be discarded.
*
- * After {@link #start} been called, {@link #done} must be called regardless of errors.
+ * {@link #done} must be called at the end of synthesis, regardless of errors.
+ *
+ * All methods can be only called on the synthesis thread.
*/
public interface SynthesisCallback {
/**
@@ -41,13 +43,16 @@ public interface SynthesisCallback {
* request.
*
* This method should only be called on the synthesis thread,
- * while in {@link TextToSpeechService#onSynthesizeText}.
+ * while in {@link TextToSpeechService#onSynthesizeText} or
+ * {@link TextToSpeechService#onSynthesizeTextV2}.
*
* @param sampleRateInHz Sample rate in HZ of the generated audio.
* @param audioFormat Audio format of the generated audio. Must be one of
* the ENCODING_ constants defined in {@link android.media.AudioFormat}.
* @param channelCount The number of channels. Must be {@code 1} or {@code 2}.
- * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ * @return {@link TextToSpeech#SUCCESS}, {@link TextToSpeech#ERROR}.
+ * {@link TextToSpeechClient.Status#STOPPED} is also possible if called in context of
+ * {@link TextToSpeechService#onSynthesizeTextV2}.
*/
public int start(int sampleRateInHz, int audioFormat, int channelCount);
@@ -55,7 +60,8 @@ public interface SynthesisCallback {
* The service should call this method when synthesized audio is ready for consumption.
*
* This method should only be called on the synthesis thread,
- * while in {@link TextToSpeechService#onSynthesizeText}.
+ * while in {@link TextToSpeechService#onSynthesizeText} or
+ * {@link TextToSpeechService#onSynthesizeTextV2}.
*
* @param buffer The generated audio data. This method will not hold on to {@code buffer},
* so the caller is free to modify it after this method returns.
@@ -63,6 +69,8 @@ public interface SynthesisCallback {
* @param length The number of bytes of audio data in {@code buffer}. This must be
* less than or equal to the return value of {@link #getMaxBufferSize}.
* @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ * {@link TextToSpeechClient.Status#STOPPED} is also possible if called in context of
+ * {@link TextToSpeechService#onSynthesizeTextV2}.
*/
public int audioAvailable(byte[] buffer, int offset, int length);
@@ -71,11 +79,14 @@ public interface SynthesisCallback {
* been passed to {@link #audioAvailable}.
*
* This method should only be called on the synthesis thread,
- * while in {@link TextToSpeechService#onSynthesizeText}.
+ * while in {@link TextToSpeechService#onSynthesizeText} or
+ * {@link TextToSpeechService#onSynthesizeTextV2}.
*
- * This method has to be called if {@link #start} was called.
+ * This method has to be called if {@link #start} and/or {@link #error} was called.
*
* @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ * {@link TextToSpeechClient.Status#STOPPED} is also possible if called in context of
+ * {@link TextToSpeechService#onSynthesizeTextV2}.
*/
public int done();
@@ -87,4 +98,58 @@ public interface SynthesisCallback {
*/
public void error();
-} \ No newline at end of file
+
+ /**
+ * The service should call this method if the speech synthesis fails.
+ *
+ * This method should only be called on the synthesis thread,
+ * while in {@link TextToSpeechService#onSynthesizeText} or
+ * {@link TextToSpeechService#onSynthesizeTextV2}.
+ *
+ * @param errorCode Error code to pass to the client. One of the ERROR_ values from
+ * {@link TextToSpeechClient.Status}
+ */
+ public void error(int errorCode);
+
+ /**
+ * Communicate to client that the original request can't be done and client-requested
+ * fallback is happening.
+ *
+ * Fallback can be requested by the client by setting
+ * {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME} voice parameter with a id of
+ * the voice that is expected to be used for the fallback.
+ *
+ * This method will fail if user called {@link #start(int, int, int)} and/or
+ * {@link #done()}.
+ *
+ * This method should only be called on the synthesis thread,
+ * while in {@link TextToSpeechService#onSynthesizeTextV2}.
+ *
+ * @return {@link TextToSpeech#SUCCESS}, {@link TextToSpeech#ERROR} if client already
+ * called {@link #start(int, int, int)}, {@link TextToSpeechClient.Status#STOPPED}
+ * if stop was requested.
+ */
+ public int fallback();
+
+ /**
+ * Check if {@link #start} was called or not.
+ *
+ * This method should only be called on the synthesis thread,
+ * while in {@link TextToSpeechService#onSynthesizeText} or
+ * {@link TextToSpeechService#onSynthesizeTextV2}.
+ *
+ * Useful for checking if a fallback from network request is possible.
+ */
+ public boolean hasStarted();
+
+ /**
+ * Check if {@link #done} was called or not.
+ *
+ * This method should only be called on the synthesis thread,
+ * while in {@link TextToSpeechService#onSynthesizeText} or
+ * {@link TextToSpeechService#onSynthesizeTextV2}.
+ *
+ * Useful for checking if a fallback from network request is possible.
+ */
+ public boolean hasFinished();
+}
diff --git a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
index e853c9e..b424356 100644
--- a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
+++ b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
@@ -57,23 +57,22 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem {
*/
private volatile boolean mStopped;
private volatile boolean mDone;
- private volatile boolean mIsError;
+ private volatile int mStatusCode;
private final BlockingAudioTrack mAudioTrack;
- private final EventLogger mLogger;
-
+ private final AbstractEventLogger mLogger;
SynthesisPlaybackQueueItem(int streamType, int sampleRate,
int audioFormat, int channelCount,
float volume, float pan, UtteranceProgressDispatcher dispatcher,
- Object callerIdentity, EventLogger logger) {
+ Object callerIdentity, AbstractEventLogger logger) {
super(dispatcher, callerIdentity);
mUnconsumedBytes = 0;
mStopped = false;
mDone = false;
- mIsError = false;
+ mStatusCode = TextToSpeechClient.Status.SUCCESS;
mAudioTrack = new BlockingAudioTrack(streamType, sampleRate, audioFormat,
channelCount, volume, pan);
@@ -86,9 +85,8 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem {
final UtteranceProgressDispatcher dispatcher = getDispatcher();
dispatcher.dispatchOnStart();
-
if (!mAudioTrack.init()) {
- dispatcher.dispatchOnError();
+ dispatcher.dispatchOnError(TextToSpeechClient.Status.ERROR_OUTPUT);
return;
}
@@ -112,23 +110,25 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem {
mAudioTrack.waitAndRelease();
- if (mIsError) {
- dispatcher.dispatchOnError();
+ if (mStatusCode == TextToSpeechClient.Status.SUCCESS) {
+ dispatcher.dispatchOnSuccess();
+ } else if(mStatusCode == TextToSpeechClient.Status.STOPPED) {
+ dispatcher.dispatchOnStop();
} else {
- dispatcher.dispatchOnDone();
+ dispatcher.dispatchOnError(mStatusCode);
}
- mLogger.onWriteData();
+ mLogger.onCompleted(mStatusCode);
}
@Override
- void stop(boolean isError) {
+ void stop(int statusCode) {
try {
mListLock.lock();
// Update our internal state.
mStopped = true;
- mIsError = isError;
+ mStatusCode = statusCode;
// Wake up the audio playback thread if it was waiting on take().
// take() will return null since mStopped was true, and will then
diff --git a/core/java/android/speech/tts/SynthesisRequestV2.aidl b/core/java/android/speech/tts/SynthesisRequestV2.aidl
new file mode 100644
index 0000000..2ac7da6
--- /dev/null
+++ b/core/java/android/speech/tts/SynthesisRequestV2.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.speech.tts;
+
+parcelable SynthesisRequestV2; \ No newline at end of file
diff --git a/core/java/android/speech/tts/SynthesisRequestV2.java b/core/java/android/speech/tts/SynthesisRequestV2.java
new file mode 100644
index 0000000..ed268b7
--- /dev/null
+++ b/core/java/android/speech/tts/SynthesisRequestV2.java
@@ -0,0 +1,132 @@
+package android.speech.tts;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.speech.tts.TextToSpeechClient.UtteranceId;
+
+/**
+ * Service-side representation of a synthesis request from a V2 API client. Contains:
+ * <ul>
+ * <li>The utterance to synthesize</li>
+ * <li>The id of the utterance (String, result of {@link UtteranceId#toUniqueString()}</li>
+ * <li>The synthesis voice name (String, result of {@link VoiceInfo#getName()})</li>
+ * <li>Voice parameters (Bundle of parameters)</li>
+ * <li>Audio parameters (Bundle of parameters)</li>
+ * </ul>
+ */
+public final class SynthesisRequestV2 implements Parcelable {
+ /** Synthesis utterance. */
+ private final String mText;
+
+ /** Synthesis id. */
+ private final String mUtteranceId;
+
+ /** Voice ID. */
+ private final String mVoiceName;
+
+ /** Voice Parameters. */
+ private final Bundle mVoiceParams;
+
+ /** Audio Parameters. */
+ private final Bundle mAudioParams;
+
+ /**
+ * Parcel based constructor.
+ *
+ * @hide
+ */
+ public SynthesisRequestV2(Parcel in) {
+ this.mText = in.readString();
+ this.mUtteranceId = in.readString();
+ this.mVoiceName = in.readString();
+ this.mVoiceParams = in.readBundle();
+ this.mAudioParams = in.readBundle();
+ }
+
+ SynthesisRequestV2(String text, String utteranceId, RequestConfig rconfig) {
+ this.mText = text;
+ this.mUtteranceId = utteranceId;
+ this.mVoiceName = rconfig.getVoice().getName();
+ this.mVoiceParams = rconfig.getVoiceParams();
+ this.mAudioParams = rconfig.getAudioParams();
+ }
+
+ /**
+ * Write to parcel.
+ *
+ * @hide
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mText);
+ dest.writeString(mUtteranceId);
+ dest.writeString(mVoiceName);
+ dest.writeBundle(mVoiceParams);
+ dest.writeBundle(mAudioParams);
+ }
+
+ /**
+ * @return the text which should be synthesized.
+ */
+ public String getText() {
+ return mText;
+ }
+
+ /**
+ * @return the id of the synthesis request. It's an output of a call to the
+ * {@link UtteranceId#toUniqueString()} method of the {@link UtteranceId} associated with
+ * this request.
+ */
+ public String getUtteranceId() {
+ return mUtteranceId;
+ }
+
+ /**
+ * @return the name of the voice to use for this synthesis request. Result of a call to
+ * the {@link VoiceInfo#getName()} method.
+ */
+ public String getVoiceName() {
+ return mVoiceName;
+ }
+
+ /**
+ * @return bundle of voice parameters.
+ */
+ public Bundle getVoiceParams() {
+ return mVoiceParams;
+ }
+
+ /**
+ * @return bundle of audio parameters.
+ */
+ public Bundle getAudioParams() {
+ return mAudioParams;
+ }
+
+ /**
+ * Parcel creators.
+ *
+ * @hide
+ */
+ public static final Parcelable.Creator<SynthesisRequestV2> CREATOR =
+ new Parcelable.Creator<SynthesisRequestV2>() {
+ @Override
+ public SynthesisRequestV2 createFromParcel(Parcel source) {
+ return new SynthesisRequestV2(source);
+ }
+
+ @Override
+ public SynthesisRequestV2[] newArray(int size) {
+ return new SynthesisRequestV2[size];
+ }
+ };
+
+ /**
+ * @hide
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 2752085..02152fb 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -54,7 +54,9 @@ import java.util.Set;
* When you are done using the TextToSpeech instance, call the {@link #shutdown()} method
* to release the native resources used by the TextToSpeech engine.
*
+ * @deprecated Use {@link TextToSpeechClient} instead
*/
+@Deprecated
public class TextToSpeech {
private static final String TAG = "TextToSpeech";
@@ -970,7 +972,7 @@ public class TextToSpeech {
@Override
public Integer run(ITextToSpeechService service) throws RemoteException {
return service.playSilence(getCallerIdentity(), durationInMs, queueMode,
- getParams(params));
+ params == null ? null : params.get(Engine.KEY_PARAM_UTTERANCE_ID));
}
}, ERROR, "playSilence");
}
@@ -1443,8 +1445,17 @@ public class TextToSpeech {
private boolean mEstablished;
private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
+ public void onStop(String utteranceId) throws RemoteException {
+ // do nothing
+ };
+
+ @Override
+ public void onFallback(String utteranceId) throws RemoteException {
+ // do nothing
+ }
+
@Override
- public void onDone(String utteranceId) {
+ public void onSuccess(String utteranceId) {
UtteranceProgressListener listener = mUtteranceProgressListener;
if (listener != null) {
listener.onDone(utteranceId);
@@ -1452,7 +1463,7 @@ public class TextToSpeech {
}
@Override
- public void onError(String utteranceId) {
+ public void onError(String utteranceId, int errorCode) {
UtteranceProgressListener listener = mUtteranceProgressListener;
if (listener != null) {
listener.onError(utteranceId);
@@ -1466,6 +1477,11 @@ public class TextToSpeech {
listener.onStart(utteranceId);
}
}
+
+ @Override
+ public void onVoicesInfoChange(List<VoiceInfo> voicesInfo) throws RemoteException {
+ // Ignore it
+ }
};
private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
diff --git a/core/java/android/speech/tts/TextToSpeechClient.java b/core/java/android/speech/tts/TextToSpeechClient.java
new file mode 100644
index 0000000..0d8d42c
--- /dev/null
+++ b/core/java/android/speech/tts/TextToSpeechClient.java
@@ -0,0 +1,1054 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.speech.tts;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.speech.tts.ITextToSpeechCallback;
+import android.speech.tts.ITextToSpeechService;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Synthesizes speech from text for immediate playback or to create a sound
+ * file.
+ * <p>
+ * This is an updated version of the speech synthesis client that supersedes
+ * {@link android.speech.tts.TextToSpeech}.
+ * <p>
+ * A TextToSpeechClient instance can only be used to synthesize text once it has
+ * connected to the service. The TextToSpeechClient instance will start establishing
+ * the connection after a call to the {@link #connect()} method. This is usually done in
+ * {@link Application#onCreate()} or {@link Activity#onCreate}. When the connection
+ * is established, the instance will call back using the
+ * {@link TextToSpeechClient.ConnectionCallbacks} interface. Only after a
+ * successful callback is the client usable.
+ * <p>
+ * After successful connection, the list of all available voices can be obtained
+ * by calling the {@link TextToSpeechClient#getEngineStatus() method. The client can
+ * choose a voice using some custom heuristic and build a {@link RequestConfig} object
+ * using {@link RequestConfig.Builder}, or can use one of the common heuristics found
+ * in ({@link RequestConfigHelper}.
+ * <p>
+ * When you are done using the TextToSpeechClient instance, call the
+ * {@link #disconnect()} method to release the connection.
+ * <p>
+ * In the rare case of a change to the set of available voices, the service will call to the
+ * {@link ConnectionCallbacks#onEngineStatusChange} with new set of available voices as argument.
+ * In response, the client HAVE to recreate all {@link RequestConfig} instances in use.
+ */
+public final class TextToSpeechClient {
+ private static final String TAG = TextToSpeechClient.class.getSimpleName();
+
+ private final Object mLock = new Object();
+ private final TtsEngines mEnginesHelper;
+ private final Context mContext;
+
+ // Guarded by mLock
+ private Connection mServiceConnection;
+ private final RequestCallbacks mDefaultRequestCallbacks;
+ private final ConnectionCallbacks mConnectionCallbacks;
+ private EngineStatus mEngineStatus;
+ private String mRequestedEngine;
+ private boolean mFallbackToDefault;
+ private HashMap<String, Pair<UtteranceId, RequestCallbacks>> mCallbacks;
+ // Guarded by mLock
+
+ /** Common voices parameters */
+ public static final class Params {
+ private Params() {}
+
+ /**
+ * Maximum allowed time for a single request attempt, in milliseconds, before synthesis
+ * fails (or fallback request starts, if requested using
+ * {@link #FALLBACK_VOICE_NAME}).
+ */
+ public static final String NETWORK_TIMEOUT_MS = "networkTimeoutMs";
+
+ /**
+ * Number of network request retries that are attempted in case of failure
+ */
+ public static final String NETWORK_RETRIES_COUNT = "networkRetriesCount";
+
+ /**
+ * Should synthesizer report sub-utterance progress on synthesis. Only applicable
+ * for the {@link TextToSpeechClient#queueSpeak} method.
+ */
+ public static final String TRACK_SUBUTTERANCE_PROGRESS = "trackSubutteranceProgress";
+
+ /**
+ * If a voice exposes this parameter then it supports the fallback request feature.
+ *
+ * If it is set to a valid name of some other voice ({@link VoiceInfo#getName()}) then
+ * in case of request failure (due to network problems or missing data), fallback request
+ * will be attempted. Request will be done using the voice referenced by this parameter.
+ * If it is the case, the client will be informed by a callback to the {@link
+ * RequestCallbacks#onSynthesisFallback(UtteranceId)}.
+ */
+ public static final String FALLBACK_VOICE_NAME = "fallbackVoiceName";
+
+ /**
+ * Audio parameter for specifying a linear multiplier to the speaking speed of the voice.
+ * The value is a float. Values below zero decrease speed of the synthesized speech
+ * values above one increase it. If the value of this parameter is equal to zero,
+ * then it will be replaced by a settings-configurable default before it reaches
+ * TTS service.
+ */
+ public static final String SPEECH_SPEED = "speechSpeed";
+
+ /**
+ * Audio parameter for controlling the pitch of the output. The Value is a positive float,
+ * with default of {@code 1.0}. The value is used to scale the primary frequency linearly.
+ * Lower values lower the tone of the synthesized voice, greater values increase it.
+ */
+ public static final String SPEECH_PITCH = "speechPitch";
+
+ /**
+ * Audio parameter for controlling output volume. Value is a float with scale of 0 to 1
+ */
+ public static final String AUDIO_PARAM_VOLUME = TextToSpeech.Engine.KEY_PARAM_VOLUME;
+
+ /**
+ * Audio parameter for controlling output pan.
+ * Value is a float ranging from -1 to +1 where -1 maps to a hard-left pan,
+ * 0 to center (the default behavior), and +1 to hard-right.
+ */
+ public static final String AUDIO_PARAM_PAN = TextToSpeech.Engine.KEY_PARAM_PAN;
+
+ /**
+ * Audio parameter for specifying the audio stream type to be used when speaking text
+ * or playing back a file. The value should be one of the STREAM_ constants
+ * defined in {@link AudioManager}.
+ */
+ public static final String AUDIO_PARAM_STREAM = TextToSpeech.Engine.KEY_PARAM_STREAM;
+ }
+
+ /**
+ * Result codes for TTS operations.
+ */
+ public static final class Status {
+ private Status() {}
+
+ /**
+ * Denotes a successful operation.
+ */
+ public static final int SUCCESS = 0;
+
+ /**
+ * Denotes a stop requested by a client. It's used only on the service side of the API,
+ * client should never expect to see this result code.
+ */
+ public static final int STOPPED = 100;
+
+ /**
+ * Denotes a generic failure.
+ */
+ public static final int ERROR_UNKNOWN = -1;
+
+ /**
+ * Denotes a failure of a TTS engine to synthesize the given input.
+ */
+ public static final int ERROR_SYNTHESIS = 10;
+
+ /**
+ * Denotes a failure of a TTS service.
+ */
+ public static final int ERROR_SERVICE = 11;
+
+ /**
+ * Denotes a failure related to the output (audio device or a file).
+ */
+ public static final int ERROR_OUTPUT = 12;
+
+ /**
+ * Denotes a failure caused by a network connectivity problems.
+ */
+ public static final int ERROR_NETWORK = 13;
+
+ /**
+ * Denotes a failure caused by network timeout.
+ */
+ public static final int ERROR_NETWORK_TIMEOUT = 14;
+
+ /**
+ * Denotes a failure caused by an invalid request.
+ */
+ public static final int ERROR_INVALID_REQUEST = 15;
+
+ /**
+ * Denotes a failure related to passing a non-unique utterance id.
+ */
+ public static final int ERROR_NON_UNIQUE_UTTERANCE_ID = 16;
+
+ /**
+ * Denotes a failure related to missing data. The TTS implementation may download
+ * the missing data, and if so, request will succeed in future. This error can only happen
+ * for voices with {@link VoiceInfo#FEATURE_MAY_AUTOINSTALL} feature.
+ * Note: the recommended way to avoid this error is to create a request with the fallback
+ * voice.
+ */
+ public static final int ERROR_DOWNLOADING_ADDITIONAL_DATA = 17;
+ }
+
+ /**
+ * Set of callbacks for the events related to the progress of a synthesis request
+ * through the synthesis queue. Each synthesis request is associated with a call to
+ * {@link #queueSpeak} or {@link #queueSynthesizeToFile}.
+ *
+ * The callbacks specified in this method will NOT be called on UI thread.
+ */
+ public static abstract class RequestCallbacks {
+ /**
+ * Called after synthesis of utterance successfully starts.
+ */
+ public void onSynthesisStart(UtteranceId utteranceId) {}
+
+ /**
+ * Called after synthesis successfully finishes.
+ * @param utteranceId
+ * Unique identifier of synthesized utterance.
+ */
+ public void onSynthesisSuccess(UtteranceId utteranceId) {}
+
+ /**
+ * Called after synthesis was stopped in middle of synthesis process.
+ * @param utteranceId
+ * Unique identifier of synthesized utterance.
+ */
+ public void onSynthesisStop(UtteranceId utteranceId) {}
+
+ /**
+ * Called when requested synthesis failed and fallback synthesis is about to be attempted.
+ *
+ * Requires voice with available {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME}
+ * parameter, and request with this parameter enabled.
+ *
+ * This callback will be followed by callback to the {@link #onSynthesisStart},
+ * {@link #onSynthesisFailure} or {@link #onSynthesisSuccess} that depends on the
+ * fallback outcome.
+ *
+ * For more fallback feature reference, look at the
+ * {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME}.
+ *
+ * @param utteranceId
+ * Unique identifier of synthesized utterance.
+ */
+ public void onSynthesisFallback(UtteranceId utteranceId) {}
+
+ /**
+ * Called after synthesis of utterance fails.
+ *
+ * It may be called instead or after a {@link #onSynthesisStart} callback.
+ *
+ * @param utteranceId
+ * Unique identifier of synthesized utterance.
+ * @param errorCode
+ * One of the values from {@link Status}.
+ */
+ public void onSynthesisFailure(UtteranceId utteranceId, int errorCode) {}
+
+ /**
+ * Called during synthesis to mark synthesis progress.
+ *
+ * Requires voice with available
+ * {@link TextToSpeechClient.Params#TRACK_SUBUTTERANCE_PROGRESS} parameter, and
+ * request with this parameter enabled.
+ *
+ * @param utteranceId
+ * Unique identifier of synthesized utterance.
+ * @param charIndex
+ * String index (java char offset) of recently synthesized character.
+ * @param msFromStart
+ * Miliseconds from the start of the synthesis.
+ */
+ public void onSynthesisProgress(UtteranceId utteranceId, int charIndex, int msFromStart) {}
+ }
+
+ /**
+ * Interface definition of callbacks that are called when the client is
+ * connected or disconnected from the TTS service.
+ */
+ public static interface ConnectionCallbacks {
+ /**
+ * After calling {@link TextToSpeechClient#connect()}, this method will be invoked
+ * asynchronously when the connect request has successfully completed.
+ *
+ * Clients are strongly encouraged to call {@link TextToSpeechClient#getEngineStatus()}
+ * and create {@link RequestConfig} objects used in subsequent synthesis requests.
+ */
+ public void onConnectionSuccess();
+
+ /**
+ * After calling {@link TextToSpeechClient#connect()}, this method may be invoked
+ * asynchronously when the connect request has failed to complete.
+ *
+ * It may be also invoked synchronously, from the body of
+ * {@link TextToSpeechClient#connect()} method.
+ */
+ public void onConnectionFailure();
+
+ /**
+ * Called when the connection to the service is lost. This can happen if there is a problem
+ * with the speech service (e.g. a crash or resource problem causes it to be killed by the
+ * system). When called, all requests have been canceled and no outstanding listeners will
+ * be executed. Applications should disable UI components that require the service.
+ */
+ public void onServiceDisconnected();
+
+ /**
+ * After receiving {@link #onConnectionSuccess()} callback, this method may be invoked
+ * if engine status obtained from {@link TextToSpeechClient#getEngineStatus()}) changes.
+ * It usually means that some voices were removed, changed or added.
+ *
+ * Clients are required to recreate {@link RequestConfig} objects used in subsequent
+ * synthesis requests.
+ */
+ public void onEngineStatusChange(EngineStatus newEngineStatus);
+ }
+
+ /** State of voices as provided by engine and user. */
+ public static final class EngineStatus {
+ /** All available voices. */
+ private final List<VoiceInfo> mVoices;
+
+ /** Name of the TTS engine package */
+ private final String mPackageName;
+
+ private EngineStatus(String packageName, List<VoiceInfo> voices) {
+ this.mVoices = Collections.unmodifiableList(voices);
+ this.mPackageName = packageName;
+ }
+
+ /**
+ * Get an immutable list of all Voices exposed by the TTS engine.
+ */
+ public List<VoiceInfo> getVoices() {
+ return mVoices;
+ }
+
+ /**
+ * Get name of the TTS engine package currently in use.
+ */
+ public String getEnginePackage() {
+ return mPackageName;
+ }
+ }
+
+ /** Unique synthesis request identifier. */
+ public static final class UtteranceId {
+ private final String mDescription;
+ /**
+ * Create new, unique UtteranceId instance.
+ */
+ public UtteranceId() {
+ mDescription = null;
+ }
+
+ /**
+ * Create new, unique UtteranceId instance.
+ *
+ * @param description Additional string, that will be appended to
+ * {@link #toUniqueString()} output, allowing easier identification of the utterance in
+ * callbacks.
+ */
+ public UtteranceId(String description) {
+ mDescription = description;
+ }
+
+ /**
+ * Returns a unique string associated with an instance of this object.
+ *
+ * If you subclass {@link UtteranceId} make sure that output of this method is
+ * consistent across multiple calls and unique for the instance.
+ *
+ * This string will be used to identify the synthesis request/utterance inside the
+ * TTS service.
+ */
+ public String toUniqueString() {
+ return mDescription == null ? "UtteranceId" + System.identityHashCode(this) :
+ "UtteranceId" + System.identityHashCode(this) + ": " + mDescription;
+ }
+ }
+
+ /**
+ * Create TextToSpeech service client.
+ *
+ * Will connect to the default TTS service. In order to be usable, {@link #connect()} need
+ * to be called first and successful connection callback need to be received.
+ *
+ * @param context
+ * The context this instance is running in.
+ * @param engine
+ * Package name of requested TTS engine. If it's null, then default engine will
+ * be selected regardless of {@code fallbackToDefaultEngine} parameter value.
+ * @param fallbackToDefaultEngine
+ * If requested engine is not available, should we fallback to the default engine?
+ * @param defaultRequestCallbacks
+ * Default request callbacks, it will be used for all synthesis requests without
+ * supplied RequestCallbacks instance. Can't be null.
+ * @param connectionCallbacks
+ * Callbacks for connecting and disconnecting from the service. Can't be null.
+ */
+ public TextToSpeechClient(Context context,
+ String engine, boolean fallbackToDefaultEngine,
+ RequestCallbacks defaultRequestCallbacks,
+ ConnectionCallbacks connectionCallbacks) {
+ if (context == null)
+ throw new IllegalArgumentException("context can't be null");
+ if (defaultRequestCallbacks == null)
+ throw new IllegalArgumentException("defaultRequestCallbacks can't be null");
+ if (connectionCallbacks == null)
+ throw new IllegalArgumentException("connectionCallbacks can't be null");
+ mContext = context;
+ mEnginesHelper = new TtsEngines(mContext);
+ mCallbacks = new HashMap<String, Pair<UtteranceId, RequestCallbacks>>();
+ mDefaultRequestCallbacks = defaultRequestCallbacks;
+ mConnectionCallbacks = connectionCallbacks;
+
+ mRequestedEngine = engine;
+ mFallbackToDefault = fallbackToDefaultEngine;
+ }
+
+ /**
+ * Create TextToSpeech service client. Will connect to the default TTS
+ * service. In order to be usable, {@link #connect()} need to be called
+ * first and successful connection callback need to be received.
+ *
+ * @param context Context this instance is running in.
+ * @param defaultRequestCallbacks Default request callbacks, it
+ * will be used for all synthesis requests without supplied
+ * RequestCallbacks instance. Can't be null.
+ * @param connectionCallbacks Callbacks for connecting and disconnecting
+ * from the service. Can't be null.
+ */
+ public TextToSpeechClient(Context context, RequestCallbacks defaultRequestCallbacks,
+ ConnectionCallbacks connectionCallbacks) {
+ this(context, null, true, defaultRequestCallbacks, connectionCallbacks);
+ }
+
+
+ private boolean initTts(String requestedEngine, boolean fallbackToDefaultEngine) {
+ // Step 1: Try connecting to the engine that was requested.
+ if (requestedEngine != null) {
+ if (mEnginesHelper.isEngineInstalled(requestedEngine)) {
+ if ((mServiceConnection = connectToEngine(requestedEngine)) != null) {
+ return true;
+ } else if (!fallbackToDefaultEngine) {
+ Log.w(TAG, "Couldn't connect to requested engine: " + requestedEngine);
+ return false;
+ }
+ } else if (!fallbackToDefaultEngine) {
+ Log.w(TAG, "Requested engine not installed: " + requestedEngine);
+ return false;
+ }
+ }
+
+ // Step 2: Try connecting to the user's default engine.
+ final String defaultEngine = mEnginesHelper.getDefaultEngine();
+ if (defaultEngine != null && !defaultEngine.equals(requestedEngine)) {
+ if ((mServiceConnection = connectToEngine(defaultEngine)) != null) {
+ return true;
+ }
+ }
+
+ // Step 3: Try connecting to the highest ranked engine in the
+ // system.
+ final String highestRanked = mEnginesHelper.getHighestRankedEngineName();
+ if (highestRanked != null && !highestRanked.equals(requestedEngine) &&
+ !highestRanked.equals(defaultEngine)) {
+ if ((mServiceConnection = connectToEngine(highestRanked)) != null) {
+ return true;
+ }
+ }
+
+ Log.w(TAG, "Couldn't find working TTS engine");
+ return false;
+ }
+
+ private Connection connectToEngine(String engine) {
+ Connection connection = new Connection(engine);
+ Intent intent = new Intent(TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE);
+ intent.setPackage(engine);
+ boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+ if (!bound) {
+ Log.e(TAG, "Failed to bind to " + engine);
+ return null;
+ } else {
+ Log.i(TAG, "Successfully bound to " + engine);
+ return connection;
+ }
+ }
+
+
+ /**
+ * Connects the client to TTS service. This method returns immediately, and connects to the
+ * service in the background.
+ *
+ * After connection initializes successfully, {@link ConnectionCallbacks#onConnectionSuccess()}
+ * is called. On a failure {@link ConnectionCallbacks#onConnectionFailure} is called.
+ *
+ * Both of those callback may be called asynchronously on the main thread,
+ * {@link ConnectionCallbacks#onConnectionFailure} may be called synchronously, before
+ * this method returns.
+ */
+ public void connect() {
+ synchronized (mLock) {
+ if (mServiceConnection != null) {
+ return;
+ }
+ if(!initTts(mRequestedEngine, mFallbackToDefault)) {
+ mConnectionCallbacks.onConnectionFailure();
+ }
+ }
+ }
+
+ /**
+ * Checks if the client is currently connected to the service, so that
+ * requests to other methods will succeed.
+ */
+ public boolean isConnected() {
+ synchronized (mLock) {
+ return mServiceConnection != null && mServiceConnection.isEstablished();
+ }
+ }
+
+ /**
+ * Closes the connection to TextToSpeech service. No calls can be made on this object after
+ * calling this method.
+ * It is good practice to call this method in the onDestroy() method of an Activity
+ * so the TextToSpeech engine can be cleanly stopped.
+ */
+ public void disconnect() {
+ synchronized (mLock) {
+ if (mServiceConnection != null) {
+ mServiceConnection.disconnect();
+ mServiceConnection = null;
+ mCallbacks.clear();
+ }
+ }
+ }
+
+ /**
+ * Register callback.
+ *
+ * @param utteranceId Non-null UtteranceId instance.
+ * @param callback Non-null callbacks for the request
+ * @return Status.SUCCESS or error code in case of invalid arguments.
+ */
+ private int addCallback(UtteranceId utteranceId, RequestCallbacks callback) {
+ synchronized (mLock) {
+ if (utteranceId == null || callback == null) {
+ return Status.ERROR_INVALID_REQUEST;
+ }
+ if (mCallbacks.put(utteranceId.toUniqueString(),
+ new Pair<UtteranceId, RequestCallbacks>(utteranceId, callback)) != null) {
+ return Status.ERROR_NON_UNIQUE_UTTERANCE_ID;
+ }
+ return Status.SUCCESS;
+ }
+ }
+
+ /**
+ * Remove and return callback.
+ *
+ * @param utteranceIdStr Unique string obtained from {@link UtteranceId#toUniqueString}.
+ */
+ private Pair<UtteranceId, RequestCallbacks> removeCallback(String utteranceIdStr) {
+ synchronized (mLock) {
+ return mCallbacks.remove(utteranceIdStr);
+ }
+ }
+
+ /**
+ * Get callback and utterance id.
+ *
+ * @param utteranceIdStr Unique string obtained from {@link UtteranceId#toUniqueString}.
+ */
+ private Pair<UtteranceId, RequestCallbacks> getCallback(String utteranceIdStr) {
+ synchronized (mLock) {
+ return mCallbacks.get(utteranceIdStr);
+ }
+ }
+
+ /**
+ * Remove callback and call {@link RequestCallbacks#onSynthesisFailure} with passed
+ * error code.
+ *
+ * @param utteranceIdStr Unique string obtained from {@link UtteranceId#toUniqueString}.
+ * @param errorCode argument to {@link RequestCallbacks#onSynthesisFailure} call.
+ */
+ private void removeCallbackAndErr(String utteranceIdStr, int errorCode) {
+ synchronized (mLock) {
+ Pair<UtteranceId, RequestCallbacks> c = mCallbacks.remove(utteranceIdStr);
+ c.second.onSynthesisFailure(c.first, errorCode);
+ }
+ }
+
+ /**
+ * Retrieve TTS engine status {@link EngineStatus}. Requires connected client.
+ */
+ public EngineStatus getEngineStatus() {
+ synchronized (mLock) {
+ return mEngineStatus;
+ }
+ }
+
+ /**
+ * Query TTS engine about available voices and defaults.
+ *
+ * @return EngineStatus is connected or null if client is disconnected.
+ */
+ private EngineStatus requestEngineStatus(ITextToSpeechService service)
+ throws RemoteException {
+ List<VoiceInfo> voices = service.getVoicesInfo();
+ if (voices == null) {
+ Log.e(TAG, "Requested engine doesn't support TTS V2 API");
+ return null;
+ }
+
+ return new EngineStatus(mServiceConnection.getEngineName(), voices);
+ }
+
+ private class Connection implements ServiceConnection {
+ private final String mEngineName;
+
+ private ITextToSpeechService mService;
+
+ private boolean mEstablished;
+
+ private PrepareConnectionAsyncTask mSetupConnectionAsyncTask;
+
+ public Connection(String engineName) {
+ this.mEngineName = engineName;
+ }
+
+ private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
+
+ @Override
+ public void onStart(String utteranceIdStr) {
+ synchronized (mLock) {
+ Pair<UtteranceId, RequestCallbacks> callbacks = getCallback(utteranceIdStr);
+ callbacks.second.onSynthesisStart(callbacks.first);
+ }
+ }
+
+ public void onStop(String utteranceIdStr) {
+ synchronized (mLock) {
+ Pair<UtteranceId, RequestCallbacks> callbacks = removeCallback(utteranceIdStr);
+ callbacks.second.onSynthesisStop(callbacks.first);
+ }
+ }
+
+ @Override
+ public void onSuccess(String utteranceIdStr) {
+ synchronized (mLock) {
+ Pair<UtteranceId, RequestCallbacks> callbacks = removeCallback(utteranceIdStr);
+ callbacks.second.onSynthesisSuccess(callbacks.first);
+ }
+ }
+
+ public void onFallback(String utteranceIdStr) {
+ synchronized (mLock) {
+ Pair<UtteranceId, RequestCallbacks> callbacks = getCallback(utteranceIdStr);
+ callbacks.second.onSynthesisFallback(callbacks.first);
+ }
+ };
+
+ @Override
+ public void onError(String utteranceIdStr, int errorCode) {
+ removeCallbackAndErr(utteranceIdStr, errorCode);
+ }
+
+ @Override
+ public void onVoicesInfoChange(List<VoiceInfo> voicesInfo) {
+ synchronized (mLock) {
+ mEngineStatus = new EngineStatus(mServiceConnection.getEngineName(),
+ voicesInfo);
+ mConnectionCallbacks.onEngineStatusChange(mEngineStatus);
+ }
+ }
+ };
+
+ private class PrepareConnectionAsyncTask extends AsyncTask<Void, Void, EngineStatus> {
+
+ private final ComponentName mName;
+
+ public PrepareConnectionAsyncTask(ComponentName name) {
+ mName = name;
+ }
+
+ @Override
+ protected EngineStatus doInBackground(Void... params) {
+ synchronized(mLock) {
+ if (isCancelled()) {
+ return null;
+ }
+ try {
+ mService.setCallback(getCallerIdentity(), mCallback);
+ return requestEngineStatus(mService);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Error setting up the TTS service");
+ return null;
+ }
+ }
+ }
+
+ @Override
+ protected void onPostExecute(EngineStatus result) {
+ synchronized(mLock) {
+ if (mSetupConnectionAsyncTask == this) {
+ mSetupConnectionAsyncTask = null;
+ }
+ if (result == null) {
+ Log.e(TAG, "Setup task failed");
+ disconnect();
+ mConnectionCallbacks.onConnectionFailure();
+ return;
+ }
+
+ mEngineStatus = result;
+ mEstablished = true;
+ }
+ mConnectionCallbacks.onConnectionSuccess();
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.i(TAG, "Connected to " + name);
+
+ synchronized(mLock) {
+ mEstablished = false;
+ mService = ITextToSpeechService.Stub.asInterface(service);
+ startSetupConnectionTask(name);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.i(TAG, "Asked to disconnect from " + name);
+
+ synchronized(mLock) {
+ stopSetupConnectionTask();
+ }
+ mConnectionCallbacks.onServiceDisconnected();
+ }
+
+ private void startSetupConnectionTask(ComponentName name) {
+ stopSetupConnectionTask();
+ mSetupConnectionAsyncTask = new PrepareConnectionAsyncTask(name);
+ mSetupConnectionAsyncTask.execute();
+ }
+
+ private boolean stopSetupConnectionTask() {
+ boolean result = false;
+ if (mSetupConnectionAsyncTask != null) {
+ result = mSetupConnectionAsyncTask.cancel(false);
+ mSetupConnectionAsyncTask = null;
+ }
+ return result;
+ }
+
+ IBinder getCallerIdentity() {
+ return mCallback;
+ }
+
+ boolean isEstablished() {
+ return mService != null && mEstablished;
+ }
+
+ boolean runAction(Action action) {
+ synchronized (mLock) {
+ try {
+ action.run(mService);
+ return true;
+ } catch (Exception ex) {
+ Log.e(TAG, action.getName() + " failed", ex);
+ disconnect();
+ return false;
+ }
+ }
+ }
+
+ void disconnect() {
+ mContext.unbindService(this);
+ stopSetupConnectionTask();
+ mService = null;
+ mEstablished = false;
+ if (mServiceConnection == this) {
+ mServiceConnection = null;
+ }
+ }
+
+ String getEngineName() {
+ return mEngineName;
+ }
+ }
+
+ private abstract class Action {
+ private final String mName;
+
+ public Action(String name) {
+ mName = name;
+ }
+
+ public String getName() {return mName;}
+ abstract void run(ITextToSpeechService service) throws RemoteException;
+ }
+
+ private IBinder getCallerIdentity() {
+ if (mServiceConnection != null) {
+ return mServiceConnection.getCallerIdentity();
+ }
+ return null;
+ }
+
+ private boolean runAction(Action action) {
+ synchronized (mLock) {
+ if (mServiceConnection == null) {
+ return false;
+ }
+ if (!mServiceConnection.isEstablished()) {
+ return false;
+ }
+ mServiceConnection.runAction(action);
+ return true;
+ }
+ }
+
+ private static final String ACTION_STOP_NAME = "stop";
+
+ /**
+ * Interrupts the current utterance spoken (whether played or rendered to file) and discards
+ * other utterances in the queue.
+ */
+ public void stop() {
+ runAction(new Action(ACTION_STOP_NAME) {
+ @Override
+ public void run(ITextToSpeechService service) throws RemoteException {
+ if (service.stop(getCallerIdentity()) != Status.SUCCESS) {
+ Log.e(TAG, "Stop failed");
+ }
+ mCallbacks.clear();
+ }
+ });
+ }
+
+ private static final String ACTION_QUEUE_SPEAK_NAME = "queueSpeak";
+
+ /**
+ * Speaks the string using the specified queuing strategy using current
+ * voice. This method is asynchronous, i.e. the method just adds the request
+ * to the queue of TTS requests and then returns. The synthesis might not
+ * have finished (or even started!) at the time when this method returns.
+ *
+ * @param utterance The string of text to be spoken. No longer than
+ * 1000 characters.
+ * @param utteranceId Unique identificator used to track the synthesis progress
+ * in {@link RequestCallbacks}.
+ * @param config Synthesis request configuration. Can't be null. Has to contain a
+ * voice.
+ * @param callbacks Synthesis request callbacks. If null, default request
+ * callbacks object will be used.
+ */
+ public void queueSpeak(final String utterance, final UtteranceId utteranceId,
+ final RequestConfig config,
+ final RequestCallbacks callbacks) {
+ runAction(new Action(ACTION_QUEUE_SPEAK_NAME) {
+ @Override
+ public void run(ITextToSpeechService service) throws RemoteException {
+ RequestCallbacks c = mDefaultRequestCallbacks;
+ if (callbacks != null) {
+ c = callbacks;
+ }
+ int addCallbackStatus = addCallback(utteranceId, c);
+ if (addCallbackStatus != Status.SUCCESS) {
+ c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST);
+ return;
+ }
+
+ int queueResult = service.speakV2(
+ getCallerIdentity(),
+ new SynthesisRequestV2(utterance, utteranceId.toUniqueString(), config));
+ if (queueResult != Status.SUCCESS) {
+ removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
+ }
+ }
+ });
+ }
+
+ private static final String ACTION_QUEUE_SYNTHESIZE_TO_FILE = "queueSynthesizeToFile";
+
+ /**
+ * Synthesizes the given text to a file using the specified parameters. This
+ * method is asynchronous, i.e. the method just adds the request to the
+ * queue of TTS requests and then returns. The synthesis might not have
+ * finished (or even started!) at the time when this method returns.
+ *
+ * @param utterance The text that should be synthesized. No longer than
+ * 1000 characters.
+ * @param utteranceId Unique identificator used to track the synthesis progress
+ * in {@link RequestCallbacks}.
+ * @param outputFile File to write the generated audio data to.
+ * @param config Synthesis request configuration. Can't be null. Have to contain a
+ * voice.
+ * @param callbacks Synthesis request callbacks. If null, default request
+ * callbacks object will be used.
+ */
+ public void queueSynthesizeToFile(final String utterance, final UtteranceId utteranceId,
+ final File outputFile, final RequestConfig config,
+ final RequestCallbacks callbacks) {
+ runAction(new Action(ACTION_QUEUE_SYNTHESIZE_TO_FILE) {
+ @Override
+ public void run(ITextToSpeechService service) throws RemoteException {
+ RequestCallbacks c = mDefaultRequestCallbacks;
+ if (callbacks != null) {
+ c = callbacks;
+ }
+ int addCallbackStatus = addCallback(utteranceId, c);
+ if (addCallbackStatus != Status.SUCCESS) {
+ c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST);
+ return;
+ }
+
+ ParcelFileDescriptor fileDescriptor = null;
+ try {
+ if (outputFile.exists() && !outputFile.canWrite()) {
+ Log.e(TAG, "No permissions to write to " + outputFile);
+ removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT);
+ return;
+ }
+ fileDescriptor = ParcelFileDescriptor.open(outputFile,
+ ParcelFileDescriptor.MODE_WRITE_ONLY |
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE);
+
+ int queueResult = service.synthesizeToFileDescriptorV2(getCallerIdentity(),
+ fileDescriptor,
+ new SynthesisRequestV2(utterance, utteranceId.toUniqueString(),
+ config));
+ fileDescriptor.close();
+ if (queueResult != Status.SUCCESS) {
+ removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
+ }
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Opening file " + outputFile + " failed", e);
+ removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT);
+ } catch (IOException e) {
+ Log.e(TAG, "Closing file " + outputFile + " failed", e);
+ removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT);
+ }
+ }
+ });
+ }
+
+ private static final String ACTION_QUEUE_SILENCE_NAME = "queueSilence";
+
+ /**
+ * Plays silence for the specified amount of time. This method is asynchronous,
+ * i.e. the method just adds the request to the queue of TTS requests and then
+ * returns. The synthesis might not have finished (or even started!) at the time
+ * when this method returns.
+ *
+ * @param durationInMs The duration of the silence in milliseconds.
+ * @param utteranceId Unique identificator used to track the synthesis progress
+ * in {@link RequestCallbacks}.
+ * @param callbacks Synthesis request callbacks. If null, default request
+ * callbacks object will be used.
+ */
+ public void queueSilence(final long durationInMs, final UtteranceId utteranceId,
+ final RequestCallbacks callbacks) {
+ runAction(new Action(ACTION_QUEUE_SILENCE_NAME) {
+ @Override
+ public void run(ITextToSpeechService service) throws RemoteException {
+ RequestCallbacks c = mDefaultRequestCallbacks;
+ if (callbacks != null) {
+ c = callbacks;
+ }
+ int addCallbackStatus = addCallback(utteranceId, c);
+ if (addCallbackStatus != Status.SUCCESS) {
+ c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST);
+ }
+
+ int queueResult = service.playSilence(getCallerIdentity(), durationInMs,
+ TextToSpeech.QUEUE_ADD, utteranceId.toUniqueString());
+
+ if (queueResult != Status.SUCCESS) {
+ removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
+ }
+ }
+ });
+ }
+
+
+ private static final String ACTION_QUEUE_AUDIO_NAME = "queueAudio";
+
+ /**
+ * Plays the audio resource using the specified parameters.
+ * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
+ * requests and then returns. The synthesis might not have finished (or even started!) at the
+ * time when this method returns.
+ *
+ * @param audioUrl The audio resource that should be played
+ * @param utteranceId Unique identificator used to track synthesis progress
+ * in {@link RequestCallbacks}.
+ * @param config Synthesis request configuration. Can't be null. Doesn't have to contain a
+ * voice (only system parameters are used).
+ * @param callbacks Synthesis request callbacks. If null, default request
+ * callbacks object will be used.
+ */
+ public void queueAudio(final Uri audioUrl, final UtteranceId utteranceId,
+ final RequestConfig config, final RequestCallbacks callbacks) {
+ runAction(new Action(ACTION_QUEUE_AUDIO_NAME) {
+ @Override
+ public void run(ITextToSpeechService service) throws RemoteException {
+ RequestCallbacks c = mDefaultRequestCallbacks;
+ if (callbacks != null) {
+ c = callbacks;
+ }
+ int addCallbackStatus = addCallback(utteranceId, c);
+ if (addCallbackStatus != Status.SUCCESS) {
+ c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST);
+ }
+
+ int queueResult = service.playAudioV2(getCallerIdentity(), audioUrl,
+ utteranceId.toUniqueString(), config.getVoiceParams());
+
+ if (queueResult != Status.SUCCESS) {
+ removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
+ }
+ }
+ });
+ }
+}
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 575855c..d7c51fc 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -34,26 +34,27 @@ import android.speech.tts.TextToSpeech.Engine;
import android.text.TextUtils;
import android.util.Log;
-import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
import java.util.Set;
/**
* Abstract base class for TTS engine implementations. The following methods
- * need to be implemented.
- *
+ * need to be implemented for V1 API ({@link TextToSpeech}) implementation.
* <ul>
- * <li>{@link #onIsLanguageAvailable}</li>
- * <li>{@link #onLoadLanguage}</li>
- * <li>{@link #onGetLanguage}</li>
- * <li>{@link #onSynthesizeText}</li>
- * <li>{@link #onStop}</li>
+ * <li>{@link #onIsLanguageAvailable}</li>
+ * <li>{@link #onLoadLanguage}</li>
+ * <li>{@link #onGetLanguage}</li>
+ * <li>{@link #onSynthesizeText}</li>
+ * <li>{@link #onStop}</li>
* </ul>
- *
* The first three deal primarily with language management, and are used to
* query the engine for it's support for a given language and indicate to it
* that requests in a given language are imminent.
@@ -61,22 +62,44 @@ import java.util.Set;
* {@link #onSynthesizeText} is central to the engine implementation. The
* implementation should synthesize text as per the request parameters and
* return synthesized data via the supplied callback. This class and its helpers
- * will then consume that data, which might mean queueing it for playback or writing
- * it to a file or similar. All calls to this method will be on a single
- * thread, which will be different from the main thread of the service. Synthesis
- * must be synchronous which means the engine must NOT hold on the callback or call
- * any methods on it after the method returns
+ * will then consume that data, which might mean queuing it for playback or writing
+ * it to a file or similar. All calls to this method will be on a single thread,
+ * which will be different from the main thread of the service. Synthesis must be
+ * synchronous which means the engine must NOT hold on to the callback or call any
+ * methods on it after the method returns.
*
- * {@link #onStop} tells the engine that it should stop all ongoing synthesis, if
- * any. Any pending data from the current synthesis will be discarded.
+ * {@link #onStop} tells the engine that it should stop
+ * all ongoing synthesis, if any. Any pending data from the current synthesis
+ * will be discarded.
*
+ * {@link #onGetLanguage} is not required as of JELLYBEAN_MR2 (API 18) and later, it is only
+ * called on earlier versions of Android.
+ * <p>
+ * In order to fully support the V2 API ({@link TextToSpeechClient}),
+ * these methods must be implemented:
+ * <ul>
+ * <li>{@link #onSynthesizeTextV2}</li>
+ * <li>{@link #checkVoicesInfo}</li>
+ * <li>{@link #onVoicesInfoChange}</li>
+ * <li>{@link #implementsV2API}</li>
+ * </ul>
+ * In addition {@link #implementsV2API} has to return true.
+ * <p>
+ * If the service does not implement these methods and {@link #implementsV2API} returns false,
+ * then the V2 API will be provided by converting V2 requests ({@link #onSynthesizeTextV2})
+ * to V1 requests ({@link #onSynthesizeText}). On service setup, all of the available device
+ * locales will be fed to {@link #onIsLanguageAvailable} to check if they are supported.
+ * If they are, embedded and/or network voices will be created depending on the result of
+ * {@link #onGetFeaturesForLanguage}.
+ * <p>
+ * Note that a V2 service will still receive requests from V1 clients and has to implement all
+ * of the V1 API methods.
*/
public abstract class TextToSpeechService extends Service {
private static final boolean DBG = false;
private static final String TAG = "TextToSpeechService";
-
private static final String SYNTH_THREAD_NAME = "SynthThread";
private SynthHandler mSynthHandler;
@@ -89,6 +112,11 @@ public abstract class TextToSpeechService extends Service {
private CallbackMap mCallbacks;
private String mPackageName;
+ private final Object mVoicesInfoLock = new Object();
+
+ private List<VoiceInfo> mVoicesInfoList;
+ private Map<String, VoiceInfo> mVoicesInfoLookup;
+
@Override
public void onCreate() {
if (DBG) Log.d(TAG, "onCreate()");
@@ -108,6 +136,7 @@ public abstract class TextToSpeechService extends Service {
mPackageName = getApplicationInfo().packageName;
String[] defaultLocale = getSettingsLocale();
+
// Load default language
onLoadLanguage(defaultLocale[0], defaultLocale[1], defaultLocale[2]);
}
@@ -148,6 +177,9 @@ public abstract class TextToSpeechService extends Service {
/**
* Returns the language, country and variant currently being used by the TTS engine.
*
+ * This method will be called only on Android 4.2 and before (API <= 17). In later versions
+ * this method is not called by the Android TTS framework.
+ *
* Can be called on multiple threads.
*
* @return A 3-element array, containing language (ISO 3-letter code),
@@ -191,21 +223,159 @@ public abstract class TextToSpeechService extends Service {
protected abstract void onStop();
/**
- * Tells the service to synthesize speech from the given text. This method should
- * block until the synthesis is finished.
- *
- * Called on the synthesis thread.
+ * Tells the service to synthesize speech from the given text. This method
+ * should block until the synthesis is finished. Used for requests from V1
+ * clients ({@link android.speech.tts.TextToSpeech}). Called on the synthesis
+ * thread.
*
* @param request The synthesis request.
- * @param callback The callback the the engine must use to make data available for
- * playback or for writing to a file.
+ * @param callback The callback that the engine must use to make data
+ * available for playback or for writing to a file.
*/
protected abstract void onSynthesizeText(SynthesisRequest request,
SynthesisCallback callback);
/**
+ * Check the available voices data and return an immutable list of the available voices.
+ * The output of this method will be passed to clients to allow them to configure synthesis
+ * requests.
+ *
+ * Can be called on multiple threads.
+ *
+ * The result of this method will be saved and served to all TTS clients. If a TTS service wants
+ * to update the set of available voices, it should call the {@link #forceVoicesInfoCheck()}
+ * method.
+ */
+ protected List<VoiceInfo> checkVoicesInfo() {
+ if (implementsV2API()) {
+ throw new IllegalStateException("For proper V2 API implementation this method has to" +
+ " be implemented");
+ }
+
+ // V2 to V1 interface adapter. This allows using V2 client interface on V1-only services.
+ Bundle defaultParams = new Bundle();
+ defaultParams.putFloat(TextToSpeechClient.Params.SPEECH_PITCH, 1.0f);
+ defaultParams.putFloat(TextToSpeechClient.Params.SPEECH_SPEED, -1.0f);
+
+ // Enumerate all locales and check if they are available
+ ArrayList<VoiceInfo> voicesInfo = new ArrayList<VoiceInfo>();
+ int id = 0;
+ for (Locale locale : Locale.getAvailableLocales()) {
+ int expectedStatus = TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE;
+ if (locale.getVariant().isEmpty()) {
+ if (locale.getCountry().isEmpty()) {
+ expectedStatus = TextToSpeech.LANG_AVAILABLE;
+ } else {
+ expectedStatus = TextToSpeech.LANG_COUNTRY_AVAILABLE;
+ }
+ }
+ try {
+ int localeStatus = onIsLanguageAvailable(locale.getISO3Language(),
+ locale.getISO3Country(), locale.getVariant());
+ if (localeStatus != expectedStatus) {
+ continue;
+ }
+ } catch (MissingResourceException e) {
+ // Ignore locale without iso 3 codes
+ continue;
+ }
+
+ Set<String> features = onGetFeaturesForLanguage(locale.getISO3Language(),
+ locale.getISO3Country(), locale.getVariant());
+
+ VoiceInfo.Builder builder = new VoiceInfo.Builder();
+ builder.setLatency(VoiceInfo.LATENCY_NORMAL);
+ builder.setQuality(VoiceInfo.QUALITY_NORMAL);
+ builder.setLocale(locale);
+ builder.setParamsWithDefaults(defaultParams);
+
+ if (features == null || features.contains(
+ TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS)) {
+ builder.setName(locale.toString() + "-embedded");
+ builder.setRequiresNetworkConnection(false);
+ voicesInfo.add(builder.build());
+ }
+
+ if (features != null && features.contains(
+ TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS)) {
+ builder.setName(locale.toString() + "-network");
+ builder.setRequiresNetworkConnection(true);
+ voicesInfo.add(builder.build());
+ }
+ }
+
+ return voicesInfo;
+ }
+
+ /**
+ * Tells the synthesis thread that it should reload voice data.
+ * There's a high probability that the underlying set of available voice data has changed.
+ * Called only on the synthesis thread.
+ */
+ protected void onVoicesInfoChange() {
+
+ }
+
+ /**
+ * Tells the service to synthesize speech from the given text. This method
+ * should block until the synthesis is finished. Used for requests from V2
+ * client {@link android.speech.tts.TextToSpeechClient}. Called on the
+ * synthesis thread.
+ *
+ * @param request The synthesis request.
+ * @param callback The callback the the engine must use to make data
+ * available for playback or for writing to a file.
+ */
+ protected void onSynthesizeTextV2(SynthesisRequestV2 request,
+ VoiceInfo selectedVoice,
+ SynthesisCallback callback) {
+ if (implementsV2API()) {
+ throw new IllegalStateException("For proper V2 API implementation this method has to" +
+ " be implemented");
+ }
+
+ // Convert to V1 params
+ int speechRate = (int) (request.getVoiceParams().getFloat(
+ TextToSpeechClient.Params.SPEECH_SPEED, 1.0f) * 100);
+ int speechPitch = (int) (request.getVoiceParams().getFloat(
+ TextToSpeechClient.Params.SPEECH_PITCH, 1.0f) * 100);
+
+ // Provide adapter to V1 API
+ Bundle params = new Bundle();
+ params.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, request.getUtteranceId());
+ params.putInt(TextToSpeech.Engine.KEY_PARAM_PITCH, speechPitch);
+ params.putInt(TextToSpeech.Engine.KEY_PARAM_RATE, speechRate);
+ if (selectedVoice.getRequiresNetworkConnection()) {
+ params.putString(TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS, "true");
+ } else {
+ params.putString(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS, "true");
+ }
+
+ // Build V1 request
+ SynthesisRequest requestV1 = new SynthesisRequest(request.getText(), params);
+ Locale locale = selectedVoice.getLocale();
+ requestV1.setLanguage(locale.getISO3Language(), locale.getISO3Country(),
+ locale.getVariant());
+ requestV1.setSpeechRate(speechRate);
+ requestV1.setPitch(speechPitch);
+
+ // Synthesize using V1 interface
+ onSynthesizeText(requestV1, callback);
+ }
+
+ /**
+ * If true, this service implements proper V2 TTS API service. If it's false,
+ * V2 API will be provided through adapter.
+ */
+ protected boolean implementsV2API() {
+ return false;
+ }
+
+ /**
* Queries the service for a set of features supported for a given language.
*
+ * Can be called on multiple threads.
+ *
* @param lang ISO-3 language code.
* @param country ISO-3 country code. May be empty or null.
* @param variant Language variant. May be empty or null.
@@ -215,6 +385,69 @@ public abstract class TextToSpeechService extends Service {
return null;
}
+ private List<VoiceInfo> getVoicesInfo() {
+ synchronized (mVoicesInfoLock) {
+ if (mVoicesInfoList == null) {
+ // Get voices. Defensive copy to make sure TTS engine won't alter the list.
+ mVoicesInfoList = new ArrayList<VoiceInfo>(checkVoicesInfo());
+ // Build lookup map
+ mVoicesInfoLookup = new HashMap<String, VoiceInfo>((int) (
+ mVoicesInfoList.size()*1.5f));
+ for (VoiceInfo voiceInfo : mVoicesInfoList) {
+ VoiceInfo prev = mVoicesInfoLookup.put(voiceInfo.getName(), voiceInfo);
+ if (prev != null) {
+ Log.e(TAG, "Duplicate name (" + voiceInfo.getName() + ") of the voice ");
+ }
+ }
+ }
+ return mVoicesInfoList;
+ }
+ }
+
+ public VoiceInfo getVoicesInfoWithName(String name) {
+ synchronized (mVoicesInfoLock) {
+ if (mVoicesInfoLookup != null) {
+ return mVoicesInfoLookup.get(name);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Force TTS service to reevaluate the set of available languages. Will result in
+ * a call to {@link #checkVoicesInfo()} on the same thread, {@link #onVoicesInfoChange}
+ * on the synthesizer thread and callback to
+ * {@link TextToSpeechClient.ConnectionCallbacks#onEngineStatusChange} of all connected
+ * TTS clients.
+ *
+ * Use this method only if you know that set of available languages changed.
+ *
+ * Can be called on multiple threads.
+ */
+ public void forceVoicesInfoCheck() {
+ synchronized (mVoicesInfoLock) {
+ List<VoiceInfo> old = mVoicesInfoList;
+
+ mVoicesInfoList = null; // Force recreation of voices info list
+ getVoicesInfo();
+
+ if (mVoicesInfoList == null) {
+ throw new IllegalStateException("This method applies only to services " +
+ "supporting V2 TTS API. This services doesn't support V2 TTS API.");
+ }
+
+ if (old != null) {
+ // Flush all existing items, and inform synthesis thread about the change.
+ mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_FLUSH,
+ new VoicesInfoChangeItem());
+ // TODO: Handle items that may be added to queue after SynthesizerRestartItem
+ // but before client reconnection
+ // Disconnect all of them
+ mCallbacks.dispatchVoicesInfoChange(mVoicesInfoList);
+ }
+ }
+ }
+
private int getDefaultSpeechRate() {
return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
}
@@ -317,7 +550,8 @@ public abstract class TextToSpeechService extends Service {
if (!speechItem.isValid()) {
if (utterenceProgress != null) {
- utterenceProgress.dispatchOnError();
+ utterenceProgress.dispatchOnError(
+ TextToSpeechClient.Status.ERROR_INVALID_REQUEST);
}
return TextToSpeech.ERROR;
}
@@ -342,12 +576,13 @@ public abstract class TextToSpeechService extends Service {
//
// Note that this string is interned, so the == comparison works.
msg.obj = speechItem.getCallerIdentity();
+
if (sendMessage(msg)) {
return TextToSpeech.SUCCESS;
} else {
Log.w(TAG, "SynthThread has quit");
if (utterenceProgress != null) {
- utterenceProgress.dispatchOnError();
+ utterenceProgress.dispatchOnError(TextToSpeechClient.Status.ERROR_SERVICE);
}
return TextToSpeech.ERROR;
}
@@ -399,9 +634,11 @@ public abstract class TextToSpeechService extends Service {
}
interface UtteranceProgressDispatcher {
- public void dispatchOnDone();
+ public void dispatchOnFallback();
+ public void dispatchOnStop();
+ public void dispatchOnSuccess();
public void dispatchOnStart();
- public void dispatchOnError();
+ public void dispatchOnError(int errorCode);
}
/**
@@ -409,15 +646,13 @@ public abstract class TextToSpeechService extends Service {
*/
private abstract class SpeechItem {
private final Object mCallerIdentity;
- protected final Bundle mParams;
private final int mCallerUid;
private final int mCallerPid;
private boolean mStarted = false;
private boolean mStopped = false;
- public SpeechItem(Object caller, int callerUid, int callerPid, Bundle params) {
+ public SpeechItem(Object caller, int callerUid, int callerPid) {
mCallerIdentity = caller;
- mParams = params;
mCallerUid = callerUid;
mCallerPid = callerPid;
}
@@ -446,20 +681,18 @@ public abstract class TextToSpeechService extends Service {
* Must not be called more than once.
*
* Only called on the synthesis thread.
- *
- * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
*/
- public int play() {
+ public void play() {
synchronized (this) {
if (mStarted) {
throw new IllegalStateException("play() called twice");
}
mStarted = true;
}
- return playImpl();
+ playImpl();
}
- protected abstract int playImpl();
+ protected abstract void playImpl();
/**
* Stops the speech item.
@@ -485,20 +718,37 @@ public abstract class TextToSpeechService extends Service {
}
/**
- * An item in the synth thread queue that process utterance.
+ * An item in the synth thread queue that process utterance (and call back to client about
+ * progress).
*/
private abstract class UtteranceSpeechItem extends SpeechItem
implements UtteranceProgressDispatcher {
- public UtteranceSpeechItem(Object caller, int callerUid, int callerPid, Bundle params) {
- super(caller, callerUid, callerPid, params);
+ public UtteranceSpeechItem(Object caller, int callerUid, int callerPid) {
+ super(caller, callerUid, callerPid);
+ }
+
+ @Override
+ public void dispatchOnSuccess() {
+ final String utteranceId = getUtteranceId();
+ if (utteranceId != null) {
+ mCallbacks.dispatchOnSuccess(getCallerIdentity(), utteranceId);
+ }
}
@Override
- public void dispatchOnDone() {
+ public void dispatchOnStop() {
final String utteranceId = getUtteranceId();
if (utteranceId != null) {
- mCallbacks.dispatchOnDone(getCallerIdentity(), utteranceId);
+ mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId);
+ }
+ }
+
+ @Override
+ public void dispatchOnFallback() {
+ final String utteranceId = getUtteranceId();
+ if (utteranceId != null) {
+ mCallbacks.dispatchOnFallback(getCallerIdentity(), utteranceId);
}
}
@@ -511,44 +761,260 @@ public abstract class TextToSpeechService extends Service {
}
@Override
- public void dispatchOnError() {
+ public void dispatchOnError(int errorCode) {
final String utteranceId = getUtteranceId();
if (utteranceId != null) {
- mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId);
+ mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId, errorCode);
}
}
- public int getStreamType() {
- return getIntParam(Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
+ abstract public String getUtteranceId();
+
+ String getStringParam(Bundle params, String key, String defaultValue) {
+ return params == null ? defaultValue : params.getString(key, defaultValue);
+ }
+
+ int getIntParam(Bundle params, String key, int defaultValue) {
+ return params == null ? defaultValue : params.getInt(key, defaultValue);
+ }
+
+ float getFloatParam(Bundle params, String key, float defaultValue) {
+ return params == null ? defaultValue : params.getFloat(key, defaultValue);
+ }
+ }
+
+ /**
+ * UtteranceSpeechItem for V1 API speech items. V1 API speech items keep
+ * synthesis parameters in a single Bundle passed as parameter. This class
+ * allow subclasses to access them conveniently.
+ */
+ private abstract class SpeechItemV1 extends UtteranceSpeechItem {
+ protected final Bundle mParams;
+
+ SpeechItemV1(Object callerIdentity, int callerUid, int callerPid,
+ Bundle params) {
+ super(callerIdentity, callerUid, callerPid);
+ mParams = params;
+ }
+
+ boolean hasLanguage() {
+ return !TextUtils.isEmpty(getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, null));
}
- public float getVolume() {
- return getFloatParam(Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME);
+ int getSpeechRate() {
+ return getIntParam(mParams, Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
}
- public float getPan() {
- return getFloatParam(Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN);
+ int getPitch() {
+ return getIntParam(mParams, Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH);
}
+ @Override
public String getUtteranceId() {
- return getStringParam(Engine.KEY_PARAM_UTTERANCE_ID, null);
+ return getStringParam(mParams, Engine.KEY_PARAM_UTTERANCE_ID, null);
+ }
+
+ int getStreamType() {
+ return getIntParam(mParams, Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
+ }
+
+ float getVolume() {
+ return getFloatParam(mParams, Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME);
+ }
+
+ float getPan() {
+ return getFloatParam(mParams, Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN);
+ }
+ }
+
+ class SynthesisSpeechItemV2 extends UtteranceSpeechItem {
+ private final SynthesisRequestV2 mSynthesisRequest;
+ private AbstractSynthesisCallback mSynthesisCallback;
+ private final EventLoggerV2 mEventLogger;
+
+ public SynthesisSpeechItemV2(Object callerIdentity, int callerUid, int callerPid,
+ SynthesisRequestV2 synthesisRequest) {
+ super(callerIdentity, callerUid, callerPid);
+
+ mSynthesisRequest = synthesisRequest;
+ mEventLogger = new EventLoggerV2(synthesisRequest, callerUid, callerPid,
+ mPackageName);
+
+ updateSpeechSpeedParam(synthesisRequest);
+ }
+
+ private void updateSpeechSpeedParam(SynthesisRequestV2 synthesisRequest) {
+ Bundle voiceParams = mSynthesisRequest.getVoiceParams();
+
+ // Inject default speech speed if needed
+ if (voiceParams.containsKey(TextToSpeechClient.Params.SPEECH_SPEED)) {
+ if (voiceParams.getFloat(TextToSpeechClient.Params.SPEECH_SPEED) <= 0) {
+ voiceParams.putFloat(TextToSpeechClient.Params.SPEECH_SPEED,
+ getDefaultSpeechRate() / 100.0f);
+ }
+ }
}
- protected String getStringParam(String key, String defaultValue) {
- return mParams == null ? defaultValue : mParams.getString(key, defaultValue);
+ @Override
+ public boolean isValid() {
+ if (mSynthesisRequest.getText() == null) {
+ Log.e(TAG, "null synthesis text");
+ return false;
+ }
+ if (mSynthesisRequest.getText().length() >= TextToSpeech.getMaxSpeechInputLength()) {
+ Log.w(TAG, "Text too long: " + mSynthesisRequest.getText().length() + " chars");
+ return false;
+ }
+
+ return true;
}
- protected int getIntParam(String key, int defaultValue) {
- return mParams == null ? defaultValue : mParams.getInt(key, defaultValue);
+ @Override
+ protected void playImpl() {
+ AbstractSynthesisCallback synthesisCallback;
+ if (mEventLogger != null) {
+ mEventLogger.onRequestProcessingStart();
+ }
+ synchronized (this) {
+ // stop() might have been called before we enter this
+ // synchronized block.
+ if (isStopped()) {
+ return;
+ }
+ mSynthesisCallback = createSynthesisCallback();
+ synthesisCallback = mSynthesisCallback;
+ }
+
+ // Get voice info
+ VoiceInfo voiceInfo = getVoicesInfoWithName(mSynthesisRequest.getVoiceName());
+ if (voiceInfo != null) {
+ // Primary voice
+ TextToSpeechService.this.onSynthesizeTextV2(mSynthesisRequest, voiceInfo,
+ synthesisCallback);
+ } else {
+ Log.e(TAG, "Unknown voice name:" + mSynthesisRequest.getVoiceName());
+ synthesisCallback.error(TextToSpeechClient.Status.ERROR_INVALID_REQUEST);
+ }
+
+ // Fix for case where client called .start() & .error(), but did not called .done()
+ if (!synthesisCallback.hasFinished()) {
+ synthesisCallback.done();
+ }
}
- protected float getFloatParam(String key, float defaultValue) {
- return mParams == null ? defaultValue : mParams.getFloat(key, defaultValue);
+ @Override
+ protected void stopImpl() {
+ AbstractSynthesisCallback synthesisCallback;
+ synchronized (this) {
+ synthesisCallback = mSynthesisCallback;
+ }
+ if (synthesisCallback != null) {
+ // If the synthesis callback is null, it implies that we haven't
+ // entered the synchronized(this) block in playImpl which in
+ // turn implies that synthesis would not have started.
+ synthesisCallback.stop();
+ TextToSpeechService.this.onStop();
+ }
}
+ protected AbstractSynthesisCallback createSynthesisCallback() {
+ return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
+ mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger,
+ implementsV2API());
+ }
+
+ private int getStreamType() {
+ return getIntParam(mSynthesisRequest.getAudioParams(),
+ TextToSpeechClient.Params.AUDIO_PARAM_STREAM,
+ Engine.DEFAULT_STREAM);
+ }
+
+ private float getVolume() {
+ return getFloatParam(mSynthesisRequest.getAudioParams(),
+ TextToSpeechClient.Params.AUDIO_PARAM_VOLUME,
+ Engine.DEFAULT_VOLUME);
+ }
+
+ private float getPan() {
+ return getFloatParam(mSynthesisRequest.getAudioParams(),
+ TextToSpeechClient.Params.AUDIO_PARAM_PAN,
+ Engine.DEFAULT_PAN);
+ }
+
+ @Override
+ public String getUtteranceId() {
+ return mSynthesisRequest.getUtteranceId();
+ }
+ }
+
+ private class SynthesisToFileOutputStreamSpeechItemV2 extends SynthesisSpeechItemV2 {
+ private final FileOutputStream mFileOutputStream;
+
+ public SynthesisToFileOutputStreamSpeechItemV2(Object callerIdentity, int callerUid,
+ int callerPid,
+ SynthesisRequestV2 synthesisRequest,
+ FileOutputStream fileOutputStream) {
+ super(callerIdentity, callerUid, callerPid, synthesisRequest);
+ mFileOutputStream = fileOutputStream;
+ }
+
+ @Override
+ protected AbstractSynthesisCallback createSynthesisCallback() {
+ return new FileSynthesisCallback(mFileOutputStream.getChannel(),
+ this, getCallerIdentity(), implementsV2API());
+ }
+
+ @Override
+ protected void playImpl() {
+ super.playImpl();
+ try {
+ mFileOutputStream.close();
+ } catch(IOException e) {
+ Log.w(TAG, "Failed to close output file", e);
+ }
+ }
+ }
+
+ private class AudioSpeechItemV2 extends UtteranceSpeechItem {
+ private final AudioPlaybackQueueItem mItem;
+ private final Bundle mAudioParams;
+ private final String mUtteranceId;
+
+ public AudioSpeechItemV2(Object callerIdentity, int callerUid, int callerPid,
+ String utteranceId, Bundle audioParams, Uri uri) {
+ super(callerIdentity, callerUid, callerPid);
+ mUtteranceId = utteranceId;
+ mAudioParams = audioParams;
+ mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
+ TextToSpeechService.this, uri, getStreamType());
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ protected void playImpl() {
+ mAudioPlaybackHandler.enqueue(mItem);
+ }
+
+ @Override
+ protected void stopImpl() {
+ // Do nothing.
+ }
+
+ protected int getStreamType() {
+ return mAudioParams.getInt(TextToSpeechClient.Params.AUDIO_PARAM_STREAM);
+ }
+
+ public String getUtteranceId() {
+ return mUtteranceId;
+ }
}
- class SynthesisSpeechItem extends UtteranceSpeechItem {
+
+ class SynthesisSpeechItemV1 extends SpeechItemV1 {
// Never null.
private final String mText;
private final SynthesisRequest mSynthesisRequest;
@@ -556,10 +1022,10 @@ public abstract class TextToSpeechService extends Service {
// Non null after synthesis has started, and all accesses
// guarded by 'this'.
private AbstractSynthesisCallback mSynthesisCallback;
- private final EventLogger mEventLogger;
+ private final EventLoggerV1 mEventLogger;
private final int mCallerUid;
- public SynthesisSpeechItem(Object callerIdentity, int callerUid, int callerPid,
+ public SynthesisSpeechItemV1(Object callerIdentity, int callerUid, int callerPid,
Bundle params, String text) {
super(callerIdentity, callerUid, callerPid, params);
mText = text;
@@ -567,7 +1033,7 @@ public abstract class TextToSpeechService extends Service {
mSynthesisRequest = new SynthesisRequest(mText, mParams);
mDefaultLocale = getSettingsLocale();
setRequestParams(mSynthesisRequest);
- mEventLogger = new EventLogger(mSynthesisRequest, callerUid, callerPid,
+ mEventLogger = new EventLoggerV1(mSynthesisRequest, callerUid, callerPid,
mPackageName);
}
@@ -589,25 +1055,30 @@ public abstract class TextToSpeechService extends Service {
}
@Override
- protected int playImpl() {
+ protected void playImpl() {
AbstractSynthesisCallback synthesisCallback;
mEventLogger.onRequestProcessingStart();
synchronized (this) {
// stop() might have been called before we enter this
// synchronized block.
if (isStopped()) {
- return TextToSpeech.ERROR;
+ return;
}
mSynthesisCallback = createSynthesisCallback();
synthesisCallback = mSynthesisCallback;
}
+
TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
- return synthesisCallback.isDone() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR;
+
+ // Fix for case where client called .start() & .error(), but did not called .done()
+ if (synthesisCallback.hasStarted() && !synthesisCallback.hasFinished()) {
+ synthesisCallback.done();
+ }
}
protected AbstractSynthesisCallback createSynthesisCallback() {
return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
- mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger);
+ mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger, false);
}
private void setRequestParams(SynthesisRequest request) {
@@ -632,37 +1103,25 @@ public abstract class TextToSpeechService extends Service {
}
}
- public String getLanguage() {
- return getStringParam(Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
- }
-
- private boolean hasLanguage() {
- return !TextUtils.isEmpty(getStringParam(Engine.KEY_PARAM_LANGUAGE, null));
- }
-
private String getCountry() {
if (!hasLanguage()) return mDefaultLocale[1];
- return getStringParam(Engine.KEY_PARAM_COUNTRY, "");
+ return getStringParam(mParams, Engine.KEY_PARAM_COUNTRY, "");
}
private String getVariant() {
if (!hasLanguage()) return mDefaultLocale[2];
- return getStringParam(Engine.KEY_PARAM_VARIANT, "");
+ return getStringParam(mParams, Engine.KEY_PARAM_VARIANT, "");
}
- private int getSpeechRate() {
- return getIntParam(Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
- }
-
- private int getPitch() {
- return getIntParam(Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH);
+ public String getLanguage() {
+ return getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
}
}
- private class SynthesisToFileOutputStreamSpeechItem extends SynthesisSpeechItem {
+ private class SynthesisToFileOutputStreamSpeechItemV1 extends SynthesisSpeechItemV1 {
private final FileOutputStream mFileOutputStream;
- public SynthesisToFileOutputStreamSpeechItem(Object callerIdentity, int callerUid,
+ public SynthesisToFileOutputStreamSpeechItemV1(Object callerIdentity, int callerUid,
int callerPid, Bundle params, String text, FileOutputStream fileOutputStream) {
super(callerIdentity, callerUid, callerPid, params, text);
mFileOutputStream = fileOutputStream;
@@ -670,30 +1129,26 @@ public abstract class TextToSpeechService extends Service {
@Override
protected AbstractSynthesisCallback createSynthesisCallback() {
- return new FileSynthesisCallback(mFileOutputStream.getChannel());
+ return new FileSynthesisCallback(mFileOutputStream.getChannel(),
+ this, getCallerIdentity(), false);
}
@Override
- protected int playImpl() {
+ protected void playImpl() {
dispatchOnStart();
- int status = super.playImpl();
- if (status == TextToSpeech.SUCCESS) {
- dispatchOnDone();
- } else {
- dispatchOnError();
- }
+ super.playImpl();
try {
mFileOutputStream.close();
} catch(IOException e) {
Log.w(TAG, "Failed to close output file", e);
}
- return status;
}
}
- private class AudioSpeechItem extends UtteranceSpeechItem {
+ private class AudioSpeechItemV1 extends SpeechItemV1 {
private final AudioPlaybackQueueItem mItem;
- public AudioSpeechItem(Object callerIdentity, int callerUid, int callerPid,
+
+ public AudioSpeechItemV1(Object callerIdentity, int callerUid, int callerPid,
Bundle params, Uri uri) {
super(callerIdentity, callerUid, callerPid, params);
mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
@@ -706,23 +1161,29 @@ public abstract class TextToSpeechService extends Service {
}
@Override
- protected int playImpl() {
+ protected void playImpl() {
mAudioPlaybackHandler.enqueue(mItem);
- return TextToSpeech.SUCCESS;
}
@Override
protected void stopImpl() {
// Do nothing.
}
+
+ @Override
+ public String getUtteranceId() {
+ return getStringParam(mParams, Engine.KEY_PARAM_UTTERANCE_ID, null);
+ }
}
private class SilenceSpeechItem extends UtteranceSpeechItem {
private final long mDuration;
+ private final String mUtteranceId;
public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
- Bundle params, long duration) {
- super(callerIdentity, callerUid, callerPid, params);
+ String utteranceId, long duration) {
+ super(callerIdentity, callerUid, callerPid);
+ mUtteranceId = utteranceId;
mDuration = duration;
}
@@ -732,26 +1193,57 @@ public abstract class TextToSpeechService extends Service {
}
@Override
- protected int playImpl() {
+ protected void playImpl() {
mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem(
this, getCallerIdentity(), mDuration));
- return TextToSpeech.SUCCESS;
}
@Override
protected void stopImpl() {
- // Do nothing, handled by AudioPlaybackHandler#stopForApp
+
+ }
+
+ @Override
+ public String getUtteranceId() {
+ return mUtteranceId;
+ }
+ }
+
+ /**
+ * Call {@link TextToSpeechService#onVoicesInfoChange} on synthesis thread.
+ */
+ private class VoicesInfoChangeItem extends SpeechItem {
+ public VoicesInfoChangeItem() {
+ super(null, 0, 0); // It's never initiated by an user
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ protected void playImpl() {
+ TextToSpeechService.this.onVoicesInfoChange();
+ }
+
+ @Override
+ protected void stopImpl() {
+ // No-op
}
}
+ /**
+ * Call {@link TextToSpeechService#onLoadLanguage} on synth thread.
+ */
private class LoadLanguageItem extends SpeechItem {
private final String mLanguage;
private final String mCountry;
private final String mVariant;
public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid,
- Bundle params, String language, String country, String variant) {
- super(callerIdentity, callerUid, callerPid, params);
+ String language, String country, String variant) {
+ super(callerIdentity, callerUid, callerPid);
mLanguage = language;
mCountry = country;
mVariant = variant;
@@ -763,14 +1255,8 @@ public abstract class TextToSpeechService extends Service {
}
@Override
- protected int playImpl() {
- int result = TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant);
- if (result == TextToSpeech.LANG_AVAILABLE ||
- result == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
- result == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
- return TextToSpeech.SUCCESS;
- }
- return TextToSpeech.ERROR;
+ protected void playImpl() {
+ TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant);
}
@Override
@@ -800,7 +1286,7 @@ public abstract class TextToSpeechService extends Service {
return TextToSpeech.ERROR;
}
- SpeechItem item = new SynthesisSpeechItem(caller,
+ SpeechItem item = new SynthesisSpeechItemV1(caller,
Binder.getCallingUid(), Binder.getCallingPid(), params, text);
return mSynthHandler.enqueueSpeechItem(queueMode, item);
}
@@ -818,7 +1304,7 @@ public abstract class TextToSpeechService extends Service {
final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd(
fileDescriptor.detachFd());
- SpeechItem item = new SynthesisToFileOutputStreamSpeechItem(caller,
+ SpeechItem item = new SynthesisToFileOutputStreamSpeechItemV1(caller,
Binder.getCallingUid(), Binder.getCallingPid(), params, text,
new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor));
return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
@@ -830,19 +1316,19 @@ public abstract class TextToSpeechService extends Service {
return TextToSpeech.ERROR;
}
- SpeechItem item = new AudioSpeechItem(caller,
+ SpeechItem item = new AudioSpeechItemV1(caller,
Binder.getCallingUid(), Binder.getCallingPid(), params, audioUri);
return mSynthHandler.enqueueSpeechItem(queueMode, item);
}
@Override
- public int playSilence(IBinder caller, long duration, int queueMode, Bundle params) {
- if (!checkNonNull(caller, params)) {
+ public int playSilence(IBinder caller, long duration, int queueMode, String utteranceId) {
+ if (!checkNonNull(caller)) {
return TextToSpeech.ERROR;
}
SpeechItem item = new SilenceSpeechItem(caller,
- Binder.getCallingUid(), Binder.getCallingPid(), params, duration);
+ Binder.getCallingUid(), Binder.getCallingPid(), utteranceId, duration);
return mSynthHandler.enqueueSpeechItem(queueMode, item);
}
@@ -912,7 +1398,7 @@ public abstract class TextToSpeechService extends Service {
retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
SpeechItem item = new LoadLanguageItem(caller, Binder.getCallingUid(),
- Binder.getCallingPid(), null, lang, country, variant);
+ Binder.getCallingPid(), lang, country, variant);
if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item) !=
TextToSpeech.SUCCESS) {
@@ -943,6 +1429,58 @@ public abstract class TextToSpeechService extends Service {
}
return true;
}
+
+ @Override
+ public List<VoiceInfo> getVoicesInfo() {
+ return TextToSpeechService.this.getVoicesInfo();
+ }
+
+ @Override
+ public int speakV2(IBinder callingInstance,
+ SynthesisRequestV2 request) {
+ if (!checkNonNull(callingInstance, request)) {
+ return TextToSpeech.ERROR;
+ }
+
+ SpeechItem item = new SynthesisSpeechItemV2(callingInstance,
+ Binder.getCallingUid(), Binder.getCallingPid(), request);
+ return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
+ }
+
+ @Override
+ public int synthesizeToFileDescriptorV2(IBinder callingInstance,
+ ParcelFileDescriptor fileDescriptor,
+ SynthesisRequestV2 request) {
+ if (!checkNonNull(callingInstance, request, fileDescriptor)) {
+ return TextToSpeech.ERROR;
+ }
+
+ // In test env, ParcelFileDescriptor instance may be EXACTLY the same
+ // one that is used by client. And it will be closed by a client, thus
+ // preventing us from writing anything to it.
+ final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd(
+ fileDescriptor.detachFd());
+
+ SpeechItem item = new SynthesisToFileOutputStreamSpeechItemV2(callingInstance,
+ Binder.getCallingUid(), Binder.getCallingPid(), request,
+ new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor));
+ return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
+
+ }
+
+ @Override
+ public int playAudioV2(
+ IBinder callingInstance, Uri audioUri, String utteranceId,
+ Bundle systemParameters) {
+ if (!checkNonNull(callingInstance, audioUri, systemParameters)) {
+ return TextToSpeech.ERROR;
+ }
+
+ SpeechItem item = new AudioSpeechItemV2(callingInstance,
+ Binder.getCallingUid(), Binder.getCallingPid(), utteranceId, systemParameters,
+ audioUri);
+ return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
+ }
};
private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
@@ -964,11 +1502,31 @@ public abstract class TextToSpeechService extends Service {
}
}
- public void dispatchOnDone(Object callerIdentity, String utteranceId) {
+ public void dispatchOnFallback(Object callerIdentity, String utteranceId) {
+ ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
+ if (cb == null) return;
+ try {
+ cb.onFallback(utteranceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback onFallback failed: " + e);
+ }
+ }
+
+ public void dispatchOnStop(Object callerIdentity, String utteranceId) {
+ ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
+ if (cb == null) return;
+ try {
+ cb.onStop(utteranceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback onStop failed: " + e);
+ }
+ }
+
+ public void dispatchOnSuccess(Object callerIdentity, String utteranceId) {
ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
if (cb == null) return;
try {
- cb.onDone(utteranceId);
+ cb.onSuccess(utteranceId);
} catch (RemoteException e) {
Log.e(TAG, "Callback onDone failed: " + e);
}
@@ -985,11 +1543,12 @@ public abstract class TextToSpeechService extends Service {
}
- public void dispatchOnError(Object callerIdentity, String utteranceId) {
+ public void dispatchOnError(Object callerIdentity, String utteranceId,
+ int errorCode) {
ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
if (cb == null) return;
try {
- cb.onError(utteranceId);
+ cb.onError(utteranceId, errorCode);
} catch (RemoteException e) {
Log.e(TAG, "Callback onError failed: " + e);
}
@@ -1001,7 +1560,7 @@ public abstract class TextToSpeechService extends Service {
synchronized (mCallerToCallback) {
mCallerToCallback.remove(caller);
}
- mSynthHandler.stopForApp(caller);
+ //mSynthHandler.stopForApp(caller);
}
@Override
@@ -1012,6 +1571,18 @@ public abstract class TextToSpeechService extends Service {
}
}
+ public void dispatchVoicesInfoChange(List<VoiceInfo> voicesInfo) {
+ synchronized (mCallerToCallback) {
+ for (ITextToSpeechCallback callback : mCallerToCallback.values()) {
+ try {
+ callback.onVoicesInfoChange(voicesInfo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to request reconnect", e);
+ }
+ }
+ }
+ }
+
private ITextToSpeechCallback getCallbackFor(Object caller) {
ITextToSpeechCallback cb;
IBinder asBinder = (IBinder) caller;
@@ -1021,7 +1592,5 @@ public abstract class TextToSpeechService extends Service {
return cb;
}
-
}
-
}
diff --git a/core/java/android/speech/tts/VoiceInfo.aidl b/core/java/android/speech/tts/VoiceInfo.aidl
new file mode 100644
index 0000000..4005f8b
--- /dev/null
+++ b/core/java/android/speech/tts/VoiceInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.speech.tts;
+
+parcelable VoiceInfo; \ No newline at end of file
diff --git a/core/java/android/speech/tts/VoiceInfo.java b/core/java/android/speech/tts/VoiceInfo.java
new file mode 100644
index 0000000..16b9a97
--- /dev/null
+++ b/core/java/android/speech/tts/VoiceInfo.java
@@ -0,0 +1,325 @@
+package android.speech.tts;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Locale;
+
+/**
+ * Characteristics and features of a Text-To-Speech Voice. Each TTS Engine can expose
+ * multiple voices for multiple locales, with different set of features.
+ *
+ * Each VoiceInfo has an unique name. This name can be obtained using the {@link #getName()} method
+ * and will persist until the client is asked to re-evaluate the list of available voices in the
+ * {@link TextToSpeechClient.ConnectionCallbacks#onEngineStatusChange(android.speech.tts.TextToSpeechClient.EngineStatus)}
+ * callback. The name can be used to reference a VoiceInfo in an instance of {@link RequestConfig};
+ * the {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME} voice parameter is an example of this.
+ * It is recommended that the voice name never change during the TTS service lifetime.
+ */
+public final class VoiceInfo implements Parcelable {
+ /** Very low, but still intelligible quality of speech synthesis */
+ public static final int QUALITY_VERY_LOW = 100;
+
+ /** Low, not human-like quality of speech synthesis */
+ public static final int QUALITY_LOW = 200;
+
+ /** Normal quality of speech synthesis */
+ public static final int QUALITY_NORMAL = 300;
+
+ /** High, human-like quality of speech synthesis */
+ public static final int QUALITY_HIGH = 400;
+
+ /** Very high, almost human-indistinguishable quality of speech synthesis */
+ public static final int QUALITY_VERY_HIGH = 500;
+
+ /** Very low expected synthesizer latency (< 20ms) */
+ public static final int LATENCY_VERY_LOW = 100;
+
+ /** Low expected synthesizer latency (~20ms) */
+ public static final int LATENCY_LOW = 200;
+
+ /** Normal expected synthesizer latency (~50ms) */
+ public static final int LATENCY_NORMAL = 300;
+
+ /** Network based expected synthesizer latency (~200ms) */
+ public static final int LATENCY_HIGH = 400;
+
+ /** Very slow network based expected synthesizer latency (> 200ms) */
+ public static final int LATENCY_VERY_HIGH = 500;
+
+ /** Additional feature key, with string value, gender of the speaker */
+ public static final String FEATURE_SPEAKER_GENDER = "speakerGender";
+
+ /** Additional feature key, with integer value, speaking speed in words per minute
+ * when {@link TextToSpeechClient.Params#SPEECH_SPEED} parameter is set to {@code 1.0} */
+ public static final String FEATURE_WORDS_PER_MINUTE = "wordsPerMinute";
+
+ /**
+ * Additional feature key, with boolean value, that indicates that voice may need to
+ * download additional data if used for synthesis.
+ *
+ * Making a request with a voice that has this feature may result in a
+ * {@link TextToSpeechClient.Status#ERROR_DOWNLOADING_ADDITIONAL_DATA} error. It's recommended
+ * to set the {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME} voice parameter to reference
+ * a fully installed voice (or network voice) that can serve as replacement.
+ *
+ * Note: It's a good practice for a TTS engine to provide a sensible fallback voice as the
+ * default value for {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME} parameter if this
+ * feature is present.
+ */
+ public static final String FEATURE_MAY_AUTOINSTALL = "mayAutoInstall";
+
+ private final String mName;
+ private final Locale mLocale;
+ private final int mQuality;
+ private final int mLatency;
+ private final boolean mRequiresNetworkConnection;
+ private final Bundle mParams;
+ private final Bundle mAdditionalFeatures;
+
+ private VoiceInfo(Parcel in) {
+ this.mName = in.readString();
+ String[] localesData = new String[3];
+ in.readStringArray(localesData);
+ this.mLocale = new Locale(localesData[0], localesData[1], localesData[2]);
+
+ this.mQuality = in.readInt();
+ this.mLatency = in.readInt();
+ this.mRequiresNetworkConnection = (in.readByte() == 1);
+
+ this.mParams = in.readBundle();
+ this.mAdditionalFeatures = in.readBundle();
+ }
+
+ private VoiceInfo(String name,
+ Locale locale,
+ int quality,
+ int latency,
+ boolean requiresNetworkConnection,
+ Bundle params,
+ Bundle additionalFeatures) {
+ this.mName = name;
+ this.mLocale = locale;
+ this.mQuality = quality;
+ this.mLatency = latency;
+ this.mRequiresNetworkConnection = requiresNetworkConnection;
+ this.mParams = params;
+ this.mAdditionalFeatures = additionalFeatures;
+ }
+
+ /** Builder, allows TTS engines to create VoiceInfo instances. */
+ public static final class Builder {
+ private String name;
+ private Locale locale;
+ private int quality = VoiceInfo.QUALITY_NORMAL;
+ private int latency = VoiceInfo.LATENCY_NORMAL;
+ private boolean requiresNetworkConnection;
+ private Bundle params;
+ private Bundle additionalFeatures;
+
+ public Builder() {
+
+ }
+
+ /**
+ * Copy fields from given VoiceInfo instance.
+ */
+ public Builder(VoiceInfo voiceInfo) {
+ this.name = voiceInfo.mName;
+ this.locale = voiceInfo.mLocale;
+ this.quality = voiceInfo.mQuality;
+ this.latency = voiceInfo.mLatency;
+ this.requiresNetworkConnection = voiceInfo.mRequiresNetworkConnection;
+ this.params = (Bundle)voiceInfo.mParams.clone();
+ this.additionalFeatures = (Bundle) voiceInfo.mAdditionalFeatures.clone();
+ }
+
+ /**
+ * Sets the voice's unique name. It will be used by clients to reference the voice used by a
+ * request.
+ *
+ * It's recommended that each voice use the same consistent name during the TTS service
+ * lifetime.
+ */
+ public Builder setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Sets voice locale. This has to be a valid locale, built from ISO 639-1 and ISO 3166-1
+ * two letter codes.
+ */
+ public Builder setLocale(Locale locale) {
+ this.locale = locale;
+ return this;
+ }
+
+ /**
+ * Sets map of all available request parameters with their default values.
+ * Some common parameter names can be found in {@link TextToSpeechClient.Params} static
+ * members.
+ */
+ public Builder setParamsWithDefaults(Bundle params) {
+ this.params = params;
+ return this;
+ }
+
+ /**
+ * Sets map of additional voice features. Some common feature names can be found in
+ * {@link VoiceInfo} static members.
+ */
+ public Builder setAdditionalFeatures(Bundle additionalFeatures) {
+ this.additionalFeatures = additionalFeatures;
+ return this;
+ }
+
+ /**
+ * Sets the voice quality (higher is better).
+ */
+ public Builder setQuality(int quality) {
+ this.quality = quality;
+ return this;
+ }
+
+ /**
+ * Sets the voice latency (lower is better).
+ */
+ public Builder setLatency(int latency) {
+ this.latency = latency;
+ return this;
+ }
+
+ /**
+ * Sets whether the voice requires network connection to work properly.
+ */
+ public Builder setRequiresNetworkConnection(boolean requiresNetworkConnection) {
+ this.requiresNetworkConnection = requiresNetworkConnection;
+ return this;
+ }
+
+ /**
+ * @return The built VoiceInfo instance.
+ */
+ public VoiceInfo build() {
+ if (name == null || name.isEmpty()) {
+ throw new IllegalStateException("Name can't be null or empty");
+ }
+ if (locale == null) {
+ throw new IllegalStateException("Locale can't be null");
+ }
+
+ return new VoiceInfo(name, locale, quality, latency,
+ requiresNetworkConnection,
+ ((params == null) ? new Bundle() :
+ (Bundle)params.clone()),
+ ((additionalFeatures == null) ? new Bundle() :
+ (Bundle)additionalFeatures.clone()));
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mName);
+ String[] localesData = new String[]{mLocale.getLanguage(), mLocale.getCountry(), mLocale.getVariant()};
+ dest.writeStringArray(localesData);
+ dest.writeInt(mQuality);
+ dest.writeInt(mLatency);
+ dest.writeByte((byte) (mRequiresNetworkConnection ? 1 : 0));
+ dest.writeBundle(mParams);
+ dest.writeBundle(mAdditionalFeatures);
+ }
+
+ /**
+ * @hide
+ */
+ public static final Parcelable.Creator<VoiceInfo> CREATOR = new Parcelable.Creator<VoiceInfo>() {
+ @Override
+ public VoiceInfo createFromParcel(Parcel in) {
+ return new VoiceInfo(in);
+ }
+
+ @Override
+ public VoiceInfo[] newArray(int size) {
+ return new VoiceInfo[size];
+ }
+ };
+
+ /**
+ * @return The voice's locale
+ */
+ public Locale getLocale() {
+ return mLocale;
+ }
+
+ /**
+ * @return The voice's quality (higher is better)
+ */
+ public int getQuality() {
+ return mQuality;
+ }
+
+ /**
+ * @return The voice's latency (lower is better)
+ */
+ public int getLatency() {
+ return mLatency;
+ }
+
+ /**
+ * @return Does the Voice require a network connection to work.
+ */
+ public boolean getRequiresNetworkConnection() {
+ return mRequiresNetworkConnection;
+ }
+
+ /**
+ * @return Bundle of all available parameters with their default values.
+ */
+ public Bundle getParamsWithDefaults() {
+ return mParams;
+ }
+
+ /**
+ * @return Unique voice name.
+ *
+ * Each VoiceInfo has an unique name, that persists until client is asked to re-evaluate the
+ * set of the available languages in the {@link TextToSpeechClient.ConnectionCallbacks#onEngineStatusChange(android.speech.tts.TextToSpeechClient.EngineStatus)}
+ * callback (Voice may disappear from the set if voice was removed by the user).
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * @return Additional features of the voice.
+ */
+ public Bundle getAdditionalFeatures() {
+ return mAdditionalFeatures;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(64);
+ return builder.append("VoiceInfo[Name: ").append(mName)
+ .append(" ,locale: ").append(mLocale)
+ .append(" ,quality: ").append(mQuality)
+ .append(" ,latency: ").append(mLatency)
+ .append(" ,requiresNetwork: ").append(mRequiresNetworkConnection)
+ .append(" ,paramsWithDefaults: ").append(mParams.toString())
+ .append(" ,additionalFeatures: ").append(mAdditionalFeatures.toString())
+ .append("]").toString();
+ }
+}
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index f839d52..c80321c 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -48,11 +48,8 @@ import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
-import com.android.internal.util.XmlUtils;
-
import java.io.IOException;
import java.io.StringReader;
-import java.util.HashMap;
/**
* This class processes HTML strings into displayable styled text.
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index 22675b4..d0ed871 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -28,7 +28,6 @@ import java.util.Date;
import java.util.Formatter;
import java.util.GregorianCalendar;
import java.util.Locale;
-import java.util.TimeZone;
import libcore.icu.DateIntervalFormat;
import libcore.icu.LocaleData;
diff --git a/core/java/android/text/method/HideReturnsTransformationMethod.java b/core/java/android/text/method/HideReturnsTransformationMethod.java
index ce18692..c6a90ca 100644
--- a/core/java/android/text/method/HideReturnsTransformationMethod.java
+++ b/core/java/android/text/method/HideReturnsTransformationMethod.java
@@ -16,13 +16,6 @@
package android.text.method;
-import android.graphics.Rect;
-import android.text.GetChars;
-import android.text.Spanned;
-import android.text.SpannedString;
-import android.text.TextUtils;
-import android.view.View;
-
/**
* This transformation method causes any carriage return characters (\r)
* to be hidden by displaying them as zero-width non-breaking space
diff --git a/core/java/android/text/method/PasswordTransformationMethod.java b/core/java/android/text/method/PasswordTransformationMethod.java
index b769b76..88a69b9 100644
--- a/core/java/android/text/method/PasswordTransformationMethod.java
+++ b/core/java/android/text/method/PasswordTransformationMethod.java
@@ -25,7 +25,6 @@ import android.text.GetChars;
import android.text.NoCopySpan;
import android.text.TextUtils;
import android.text.TextWatcher;
-import android.text.Selection;
import android.text.Spanned;
import android.text.Spannable;
import android.text.style.UpdateLayout;
diff --git a/core/java/android/text/method/SingleLineTransformationMethod.java b/core/java/android/text/method/SingleLineTransformationMethod.java
index 6a05fe4..818526a 100644
--- a/core/java/android/text/method/SingleLineTransformationMethod.java
+++ b/core/java/android/text/method/SingleLineTransformationMethod.java
@@ -16,15 +16,6 @@
package android.text.method;
-import android.graphics.Rect;
-import android.text.Editable;
-import android.text.GetChars;
-import android.text.Spannable;
-import android.text.Spanned;
-import android.text.SpannedString;
-import android.text.TextUtils;
-import android.view.View;
-
/**
* This transformation method causes any newline characters (\n) to be
* displayed as spaces instead of causing line breaks, and causes
diff --git a/core/java/android/text/style/DrawableMarginSpan.java b/core/java/android/text/style/DrawableMarginSpan.java
index c2564d5..20b6886 100644
--- a/core/java/android/text/style/DrawableMarginSpan.java
+++ b/core/java/android/text/style/DrawableMarginSpan.java
@@ -19,7 +19,6 @@ package android.text.style;
import android.graphics.drawable.Drawable;
import android.graphics.Paint;
import android.graphics.Canvas;
-import android.graphics.RectF;
import android.text.Spanned;
import android.text.Layout;
diff --git a/core/java/android/text/style/DynamicDrawableSpan.java b/core/java/android/text/style/DynamicDrawableSpan.java
index 89dc45b..5b8a6dd 100644
--- a/core/java/android/text/style/DynamicDrawableSpan.java
+++ b/core/java/android/text/style/DynamicDrawableSpan.java
@@ -17,12 +17,9 @@
package android.text.style;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
-import android.graphics.Paint.Style;
import android.graphics.drawable.Drawable;
-import android.util.Log;
import java.lang.ref.WeakReference;
diff --git a/core/java/android/text/style/IconMarginSpan.java b/core/java/android/text/style/IconMarginSpan.java
index c786a17..cf9a705 100644
--- a/core/java/android/text/style/IconMarginSpan.java
+++ b/core/java/android/text/style/IconMarginSpan.java
@@ -19,7 +19,6 @@ package android.text.style;
import android.graphics.Paint;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.RectF;
import android.text.Spanned;
import android.text.Layout;
diff --git a/core/java/android/text/style/LineHeightSpan.java b/core/java/android/text/style/LineHeightSpan.java
index 44a1706..1ebee82 100644
--- a/core/java/android/text/style/LineHeightSpan.java
+++ b/core/java/android/text/style/LineHeightSpan.java
@@ -17,8 +17,6 @@
package android.text.style;
import android.graphics.Paint;
-import android.graphics.Canvas;
-import android.text.Layout;
import android.text.TextPaint;
public interface LineHeightSpan
diff --git a/core/java/android/text/style/MetricAffectingSpan.java b/core/java/android/text/style/MetricAffectingSpan.java
index 92558eb..a02b276 100644
--- a/core/java/android/text/style/MetricAffectingSpan.java
+++ b/core/java/android/text/style/MetricAffectingSpan.java
@@ -16,7 +16,6 @@
package android.text.style;
-import android.graphics.Paint;
import android.text.TextPaint;
/**
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 0ec7e84..8b40953 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -166,25 +166,25 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
return;
}
- int defStyle = com.android.internal.R.attr.textAppearanceMisspelledSuggestion;
+ int defStyleAttr = com.android.internal.R.attr.textAppearanceMisspelledSuggestion;
TypedArray typedArray = context.obtainStyledAttributes(
- null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0);
+ null, com.android.internal.R.styleable.SuggestionSpan, defStyleAttr, 0);
mMisspelledUnderlineThickness = typedArray.getDimension(
com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
mMisspelledUnderlineColor = typedArray.getColor(
com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
- defStyle = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion;
+ defStyleAttr = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion;
typedArray = context.obtainStyledAttributes(
- null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0);
+ null, com.android.internal.R.styleable.SuggestionSpan, defStyleAttr, 0);
mEasyCorrectUnderlineThickness = typedArray.getDimension(
com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
mEasyCorrectUnderlineColor = typedArray.getColor(
com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
- defStyle = com.android.internal.R.attr.textAppearanceAutoCorrectionSuggestion;
+ defStyleAttr = com.android.internal.R.attr.textAppearanceAutoCorrectionSuggestion;
typedArray = context.obtainStyledAttributes(
- null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0);
+ null, com.android.internal.R.styleable.SuggestionSpan, defStyleAttr, 0);
mAutoCorrectionUnderlineThickness = typedArray.getDimension(
com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
mAutoCorrectionUnderlineColor = typedArray.getColor(
diff --git a/core/java/android/transition/Recolor.java b/core/java/android/transition/Recolor.java
index 70111d1..1638f67 100644
--- a/core/java/android/transition/Recolor.java
+++ b/core/java/android/transition/Recolor.java
@@ -17,7 +17,6 @@
package android.transition;
import android.animation.Animator;
-import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -75,8 +74,8 @@ public class Recolor extends Transition {
if (startColor.getColor() != endColor.getColor()) {
endColor.setColor(startColor.getColor());
changed = true;
- return ObjectAnimator.ofObject(endBackground, "color",
- new ArgbEvaluator(), startColor.getColor(), endColor.getColor());
+ return ObjectAnimator.ofArgb(endBackground, "color", startColor.getColor(),
+ endColor.getColor());
}
}
if (view instanceof TextView) {
@@ -86,8 +85,7 @@ public class Recolor extends Transition {
if (start != end) {
textView.setTextColor(end);
changed = true;
- return ObjectAnimator.ofObject(textView, "textColor",
- new ArgbEvaluator(), start, end);
+ return ObjectAnimator.ofArgb(textView, "textColor", start, end);
}
}
return null;
diff --git a/core/java/android/transition/Scene.java b/core/java/android/transition/Scene.java
index e1f1896..4267a65 100644
--- a/core/java/android/transition/Scene.java
+++ b/core/java/android/transition/Scene.java
@@ -34,7 +34,7 @@ public final class Scene {
private Context mContext;
private int mLayoutId = -1;
private ViewGroup mSceneRoot;
- private ViewGroup mLayout; // alternative to layoutId
+ private View mLayout; // alternative to layoutId
Runnable mEnterAction, mExitAction;
/**
@@ -114,6 +114,15 @@ public final class Scene {
* @param layout The view hierarchy of this scene, added as a child
* of sceneRoot when this scene is entered.
*/
+ public Scene(ViewGroup sceneRoot, View layout) {
+ mSceneRoot = sceneRoot;
+ mLayout = layout;
+ }
+
+ /**
+ * @deprecated use {@link #Scene(ViewGroup, View)}.
+ */
+ @Deprecated
public Scene(ViewGroup sceneRoot, ViewGroup layout) {
mSceneRoot = sceneRoot;
mLayout = layout;
diff --git a/core/java/android/util/EventLogTags.java b/core/java/android/util/EventLogTags.java
index 8c18417..f4ce4fd 100644
--- a/core/java/android/util/EventLogTags.java
+++ b/core/java/android/util/EventLogTags.java
@@ -16,14 +16,8 @@
package android.util;
-import android.util.Log;
-
import java.io.BufferedReader;
-import java.io.FileReader;
import java.io.IOException;
-import java.util.HashMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* @deprecated This class is no longer functional.
diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java
index 641d1b4..eeb6d58 100644
--- a/core/java/android/util/LocalLog.java
+++ b/core/java/android/util/LocalLog.java
@@ -20,7 +20,6 @@ import android.text.format.Time;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.io.StringWriter;
import java.util.Iterator;
import java.util.LinkedList;
diff --git a/core/java/android/util/LongArray.java b/core/java/android/util/LongArray.java
new file mode 100644
index 0000000..7d42063
--- /dev/null
+++ b/core/java/android/util/LongArray.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2013 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.util;
+
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * Implements a growing array of long primitives.
+ *
+ * @hide
+ */
+public class LongArray implements Cloneable {
+ private static final int MIN_CAPACITY_INCREMENT = 12;
+
+ private long[] mValues;
+ private int mSize;
+
+ /**
+ * Creates an empty LongArray with the default initial capacity.
+ */
+ public LongArray() {
+ this(10);
+ }
+
+ /**
+ * Creates an empty LongArray with the specified initial capacity.
+ */
+ public LongArray(int initialCapacity) {
+ if (initialCapacity == 0) {
+ mValues = ContainerHelpers.EMPTY_LONGS;
+ } else {
+ initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
+ mValues = new long[initialCapacity];
+ }
+ mSize = 0;
+ }
+
+ /**
+ * Appends the specified value to the end of this array.
+ */
+ public void add(long value) {
+ add(mSize, value);
+ }
+
+ /**
+ * Inserts a value at the specified position in this array.
+ *
+ * @throws IndexOutOfBoundsException when index &lt; 0 || index &gt; size()
+ */
+ public void add(int index, long value) {
+ if (index < 0 || index > mSize) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ ensureCapacity(1);
+
+ if (mSize - index != 0) {
+ System.arraycopy(mValues, index, mValues, index + 1, mSize - index);
+ }
+
+ mValues[index] = value;
+ mSize++;
+ }
+
+ /**
+ * Adds the values in the specified array to this array.
+ */
+ public void addAll(LongArray values) {
+ final int count = values.mSize;
+ ensureCapacity(count);
+
+ System.arraycopy(mValues, mSize, values.mValues, 0, count);
+ mSize += count;
+ }
+
+ /**
+ * Ensures capacity to append at least <code>count</code> values.
+ */
+ private void ensureCapacity(int count) {
+ final int currentSize = mSize;
+ final int minCapacity = currentSize + count;
+ if (minCapacity >= mValues.length) {
+ final int targetCap = currentSize + (currentSize < (MIN_CAPACITY_INCREMENT / 2) ?
+ MIN_CAPACITY_INCREMENT : currentSize >> 1);
+ final int newCapacity = targetCap > minCapacity ? targetCap : minCapacity;
+ final long[] newValues = new long[ArrayUtils.idealLongArraySize(newCapacity)];
+ System.arraycopy(mValues, 0, newValues, 0, currentSize);
+ mValues = newValues;
+ }
+ }
+
+ /**
+ * Removes all values from this array.
+ */
+ public void clear() {
+ mSize = 0;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public LongArray clone() {
+ LongArray clone = null;
+ try {
+ clone = (LongArray) super.clone();
+ clone.mValues = mValues.clone();
+ } catch (CloneNotSupportedException cnse) {
+ /* ignore */
+ }
+ return clone;
+ }
+
+ /**
+ * Returns the value at the specified position in this array.
+ */
+ public long get(int index) {
+ return mValues[index];
+ }
+
+ /**
+ * Returns the index of the first occurrence of the specified value in this
+ * array, or -1 if this array does not contain the value.
+ */
+ public int indexOf(long value) {
+ final int n = mSize;
+ for (int i = 0; i < n; i++) {
+ if (mValues[i] == value) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Removes the value at the specified index from this array.
+ */
+ public void remove(int index) {
+ System.arraycopy(mValues, index, mValues, index + 1, mSize - index);
+ }
+
+ /**
+ * Returns the number of values in this array.
+ */
+ public int size() {
+ return mSize;
+ }
+}
diff --git a/core/java/android/util/LongSparseLongArray.java b/core/java/android/util/LongSparseLongArray.java
index 6654899..b8073dd 100644
--- a/core/java/android/util/LongSparseLongArray.java
+++ b/core/java/android/util/LongSparseLongArray.java
@@ -18,8 +18,6 @@ package android.util;
import com.android.internal.util.ArrayUtils;
-import java.util.Arrays;
-
/**
* Map of {@code long} to {@code long}. Unlike a normal array of longs, there
* can be gaps in the indices. It is intended to be more memory efficient than using a
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index 70795bb..b25d80f 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -16,11 +16,6 @@
package android.util;
-import com.android.internal.os.RuntimeInit;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
/**
* @hide
*/
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 41d3700..3859ad4 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -24,7 +24,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
-import android.util.SparseLongArray;
import android.view.View.AttachInfo;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -881,13 +880,12 @@ final class AccessibilityInteractionController {
AccessibilityNodeInfo parent =
provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
if (parent != null) {
- SparseLongArray childNodeIds = parent.getChildNodeIds();
- final int childCount = childNodeIds.size();
+ final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
return;
}
- final long childNodeId = childNodeIds.get(i);
+ final long childNodeId = parent.getChildId(i);
if (childNodeId != current.getSourceNodeId()) {
final int childVirtualDescendantId =
AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
@@ -906,14 +904,13 @@ final class AccessibilityInteractionController {
private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
- SparseLongArray childNodeIds = root.getChildNodeIds();
final int initialOutInfosSize = outInfos.size();
- final int childCount = childNodeIds.size();
+ final int childCount = root.getChildCount();
for (int i = 0; i < childCount; i++) {
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
return;
}
- final long childNodeId = childNodeIds.get(i);
+ final long childNodeId = root.getChildId(i);
AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
if (child != null) {
diff --git a/core/java/android/view/AccessibilityIterators.java b/core/java/android/view/AccessibilityIterators.java
index 17ce4f6..e59937d 100644
--- a/core/java/android/view/AccessibilityIterators.java
+++ b/core/java/android/view/AccessibilityIterators.java
@@ -17,8 +17,6 @@
package android.view;
import android.content.ComponentCallbacks;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import java.text.BreakIterator;
diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java
index 6c733f9..1de9c35 100644
--- a/core/java/android/view/ContextThemeWrapper.java
+++ b/core/java/android/view/ContextThemeWrapper.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.os.Build;
/**
* A ContextWrapper that allows you to modify the theme from what is in the
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 7d310a2..d3f63b4 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -528,6 +528,7 @@ public final class Display {
* 90 degrees clockwise and thus the returned value here will be
* {@link Surface#ROTATION_90 Surface.ROTATION_90}.
*/
+ @Surface.Rotation
public int getRotation() {
synchronized (this) {
updateDisplayInfoLocked();
@@ -540,6 +541,7 @@ public final class Display {
* @return orientation of this display.
*/
@Deprecated
+ @Surface.Rotation
public int getOrientation() {
return getRotation();
}
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 8944207..7fd7b83 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -20,7 +20,6 @@ import android.content.res.CompatibilityInfo;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.Process;
import android.util.DisplayMetrics;
import libcore.util.Objects;
@@ -144,6 +143,7 @@ public final class DisplayInfo implements Parcelable {
* more than one physical display.
* </p>
*/
+ @Surface.Rotation
public int rotation;
/**
diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java
index 43fd628..623472a 100644
--- a/core/java/android/view/DisplayList.java
+++ b/core/java/android/view/DisplayList.java
@@ -86,18 +86,15 @@ import android.graphics.Matrix;
*
* <pre class="prettyprint">
* private void createDisplayList() {
- * HardwareRenderer renderer = getHardwareRenderer();
- * if (renderer != null) {
- * mDisplayList = renderer.createDisplayList();
- * HardwareCanvas canvas = mDisplayList.start(width, height);
- * try {
- * for (Bitmap b : mBitmaps) {
- * canvas.drawBitmap(b, 0.0f, 0.0f, null);
- * canvas.translate(0.0f, b.getHeight());
- * }
- * } finally {
- * displayList.end();
+ * mDisplayList = DisplayList.create("MyDisplayList");
+ * HardwareCanvas canvas = mDisplayList.start(width, height);
+ * try {
+ * for (Bitmap b : mBitmaps) {
+ * canvas.drawBitmap(b, 0.0f, 0.0f, null);
+ * canvas.translate(0.0f, b.getHeight());
* }
+ * } finally {
+ * displayList.end();
* }
* }
*
@@ -177,6 +174,20 @@ public abstract class DisplayList {
public static final int STATUS_DREW = 0x4;
/**
+ * Creates a new display list that can be used to record batches of
+ * drawing operations.
+ *
+ * @param name The name of the display list, used for debugging purpose. May be null.
+ *
+ * @return A new display list.
+ *
+ * @hide
+ */
+ public static DisplayList create(String name) {
+ return new GLES20DisplayList(name);
+ }
+
+ /**
* Starts recording the display list. All operations performed on the
* returned canvas are recorded and stored in this display list.
*
diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java
new file mode 100644
index 0000000..a195231
--- /dev/null
+++ b/core/java/android/view/GLRenderer.java
@@ -0,0 +1,1610 @@
+/*
+ * Copyright (C) 2013 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 static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_SIZE;
+import static javax.microedition.khronos.egl.EGL10.EGL_BAD_NATIVE_WINDOW;
+import static javax.microedition.khronos.egl.EGL10.EGL_BLUE_SIZE;
+import static javax.microedition.khronos.egl.EGL10.EGL_CONFIG_CAVEAT;
+import static javax.microedition.khronos.egl.EGL10.EGL_DEFAULT_DISPLAY;
+import static javax.microedition.khronos.egl.EGL10.EGL_DEPTH_SIZE;
+import static javax.microedition.khronos.egl.EGL10.EGL_DRAW;
+import static javax.microedition.khronos.egl.EGL10.EGL_GREEN_SIZE;
+import static javax.microedition.khronos.egl.EGL10.EGL_HEIGHT;
+import static javax.microedition.khronos.egl.EGL10.EGL_NONE;
+import static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT;
+import static javax.microedition.khronos.egl.EGL10.EGL_NO_DISPLAY;
+import static javax.microedition.khronos.egl.EGL10.EGL_NO_SURFACE;
+import static javax.microedition.khronos.egl.EGL10.EGL_RED_SIZE;
+import static javax.microedition.khronos.egl.EGL10.EGL_RENDERABLE_TYPE;
+import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLES;
+import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLE_BUFFERS;
+import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE;
+import static javax.microedition.khronos.egl.EGL10.EGL_SUCCESS;
+import static javax.microedition.khronos.egl.EGL10.EGL_SURFACE_TYPE;
+import static javax.microedition.khronos.egl.EGL10.EGL_WIDTH;
+import static javax.microedition.khronos.egl.EGL10.EGL_WINDOW_BIT;
+
+import android.content.ComponentCallbacks2;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.opengl.EGL14;
+import android.opengl.GLUtils;
+import android.opengl.ManagedEGLContext;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Surface.OutOfResourcesException;
+
+import com.google.android.gles_jni.EGLImpl;
+
+import java.io.PrintWriter;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGL11;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL;
+
+/**
+ * Hardware renderer using OpenGL
+ *
+ * @hide
+ */
+public class GLRenderer extends HardwareRenderer {
+ static final int SURFACE_STATE_ERROR = 0;
+ static final int SURFACE_STATE_SUCCESS = 1;
+ static final int SURFACE_STATE_UPDATED = 2;
+
+ static final int FUNCTOR_PROCESS_DELAY = 4;
+
+ /**
+ * Number of frames to profile.
+ */
+ private static final int PROFILE_MAX_FRAMES = 128;
+
+ /**
+ * Number of floats per profiled frame.
+ */
+ private static final int PROFILE_FRAME_DATA_COUNT = 3;
+
+ private static final int PROFILE_DRAW_MARGIN = 0;
+ private static final int PROFILE_DRAW_WIDTH = 3;
+ private static final int[] PROFILE_DRAW_COLORS = { 0xcf3e66cc, 0xcfdc3912, 0xcfe69800 };
+ private static final int PROFILE_DRAW_CURRENT_FRAME_COLOR = 0xcf5faa4d;
+ private static final int PROFILE_DRAW_THRESHOLD_COLOR = 0xff5faa4d;
+ private static final int PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2;
+ private static final int PROFILE_DRAW_DP_PER_MS = 7;
+
+ private static final String[] VISUALIZERS = {
+ PROFILE_PROPERTY_VISUALIZE_BARS,
+ PROFILE_PROPERTY_VISUALIZE_LINES
+ };
+
+ private static final String[] OVERDRAW = {
+ OVERDRAW_PROPERTY_SHOW,
+ OVERDRAW_PROPERTY_COUNT
+ };
+ private static final int OVERDRAW_TYPE_COUNT = 1;
+ private static final int GL_VERSION = 2;
+
+ static EGL10 sEgl;
+ static EGLDisplay sEglDisplay;
+ static EGLConfig sEglConfig;
+ static final Object[] sEglLock = new Object[0];
+ int mWidth = -1, mHeight = -1;
+
+ static final ThreadLocal<ManagedEGLContext> sEglContextStorage
+ = new ThreadLocal<ManagedEGLContext>();
+
+ EGLContext mEglContext;
+ Thread mEglThread;
+
+ EGLSurface mEglSurface;
+
+ GL mGl;
+ HardwareCanvas mCanvas;
+
+ String mName;
+
+ long mFrameCount;
+ Paint mDebugPaint;
+
+ static boolean sDirtyRegions;
+ static final boolean sDirtyRegionsRequested;
+ static {
+ String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true");
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ sDirtyRegions = "true".equalsIgnoreCase(dirtyProperty);
+ sDirtyRegionsRequested = sDirtyRegions;
+ }
+
+ boolean mDirtyRegionsEnabled;
+ boolean mUpdateDirtyRegions;
+
+ boolean mProfileEnabled;
+ int mProfileVisualizerType = -1;
+ float[] mProfileData;
+ ReentrantLock mProfileLock;
+ int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
+
+ GraphDataProvider mDebugDataProvider;
+ float[][] mProfileShapes;
+ Paint mProfilePaint;
+
+ boolean mDebugDirtyRegions;
+ int mDebugOverdraw = -1;
+ HardwareLayer mDebugOverdrawLayer;
+ Paint mDebugOverdrawPaint;
+
+ final boolean mTranslucent;
+
+ private boolean mDestroyed;
+
+ private final Rect mRedrawClip = new Rect();
+
+ private final int[] mSurfaceSize = new int[2];
+ private final FunctorsRunnable mFunctorsRunnable = new FunctorsRunnable();
+
+ private long mDrawDelta = Long.MAX_VALUE;
+
+ private GLES20Canvas mGlCanvas;
+
+ private DisplayMetrics mDisplayMetrics;
+
+ private static EGLSurface sPbuffer;
+ private static final Object[] sPbufferLock = new Object[0];
+
+ private static class GLRendererEglContext extends ManagedEGLContext {
+ final Handler mHandler = new Handler();
+
+ public GLRendererEglContext(EGLContext context) {
+ super(context);
+ }
+
+ @Override
+ public void onTerminate(final EGLContext eglContext) {
+ // Make sure we do this on the correct thread.
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onTerminate(eglContext);
+ }
+ });
+ return;
+ }
+
+ synchronized (sEglLock) {
+ if (sEgl == null) return;
+
+ if (EGLImpl.getInitCount(sEglDisplay) == 1) {
+ usePbufferSurface(eglContext);
+ GLES20Canvas.terminateCaches();
+
+ sEgl.eglDestroyContext(sEglDisplay, eglContext);
+ sEglContextStorage.set(null);
+ sEglContextStorage.remove();
+
+ sEgl.eglDestroySurface(sEglDisplay, sPbuffer);
+ sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
+ EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+ sEgl.eglReleaseThread();
+ sEgl.eglTerminate(sEglDisplay);
+
+ sEgl = null;
+ sEglDisplay = null;
+ sEglConfig = null;
+ sPbuffer = null;
+ }
+ }
+ }
+ }
+
+ HardwareCanvas createCanvas() {
+ return mGlCanvas = new GLES20Canvas(mTranslucent);
+ }
+
+ ManagedEGLContext createManagedContext(EGLContext eglContext) {
+ return new GLRendererEglContext(mEglContext);
+ }
+
+ int[] getConfig(boolean dirtyRegions) {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ final int stencilSize = GLES20Canvas.getStencilSize();
+ final int swapBehavior = dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
+
+ return new int[] {
+ EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_DEPTH_SIZE, 0,
+ EGL_CONFIG_CAVEAT, EGL_NONE,
+ EGL_STENCIL_SIZE, stencilSize,
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior,
+ EGL_NONE
+ };
+ }
+
+ void initCaches() {
+ if (GLES20Canvas.initCaches()) {
+ // Caches were (re)initialized, rebind atlas
+ initAtlas();
+ }
+ }
+
+ void initAtlas() {
+ IBinder binder = ServiceManager.getService("assetatlas");
+ if (binder == null) return;
+
+ IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder);
+ try {
+ if (atlas.isCompatible(android.os.Process.myPpid())) {
+ GraphicBuffer buffer = atlas.getBuffer();
+ if (buffer != null) {
+ int[] map = atlas.getMap();
+ if (map != null) {
+ GLES20Canvas.initAtlas(buffer, map);
+ }
+ // If IAssetAtlas is not the same class as the IBinder
+ // we are using a remote service and we can safely
+ // destroy the graphic buffer
+ if (atlas.getClass() != binder.getClass()) {
+ buffer.destroy();
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Could not acquire atlas", e);
+ }
+ }
+
+ boolean canDraw() {
+ return mGl != null && mCanvas != null && mGlCanvas != null;
+ }
+
+ int onPreDraw(Rect dirty) {
+ return mGlCanvas.onPreDraw(dirty);
+ }
+
+ void onPostDraw() {
+ mGlCanvas.onPostDraw();
+ }
+
+ void drawProfileData(View.AttachInfo attachInfo) {
+ if (mDebugDataProvider != null) {
+ final GraphDataProvider provider = mDebugDataProvider;
+ initProfileDrawData(attachInfo, provider);
+
+ final int height = provider.getVerticalUnitSize();
+ final int margin = provider.getHorizontaUnitMargin();
+ final int width = provider.getHorizontalUnitSize();
+
+ int x = 0;
+ int count = 0;
+ int current = 0;
+
+ final float[] data = provider.getData();
+ final int elementCount = provider.getElementCount();
+ final int graphType = provider.getGraphType();
+
+ int totalCount = provider.getFrameCount() * elementCount;
+ if (graphType == GraphDataProvider.GRAPH_TYPE_LINES) {
+ totalCount -= elementCount;
+ }
+
+ for (int i = 0; i < totalCount; i += elementCount) {
+ if (data[i] < 0.0f) break;
+
+ int index = count * 4;
+ if (i == provider.getCurrentFrame() * elementCount) current = index;
+
+ x += margin;
+ int x2 = x + width;
+
+ int y2 = mHeight;
+ int y1 = (int) (y2 - data[i] * height);
+
+ switch (graphType) {
+ case GraphDataProvider.GRAPH_TYPE_BARS: {
+ for (int j = 0; j < elementCount; j++) {
+ //noinspection MismatchedReadAndWriteOfArray
+ final float[] r = mProfileShapes[j];
+ r[index] = x;
+ r[index + 1] = y1;
+ r[index + 2] = x2;
+ r[index + 3] = y2;
+
+ y2 = y1;
+ if (j < elementCount - 1) {
+ y1 = (int) (y2 - data[i + j + 1] * height);
+ }
+ }
+ } break;
+ case GraphDataProvider.GRAPH_TYPE_LINES: {
+ for (int j = 0; j < elementCount; j++) {
+ //noinspection MismatchedReadAndWriteOfArray
+ final float[] r = mProfileShapes[j];
+ r[index] = (x + x2) * 0.5f;
+ r[index + 1] = index == 0 ? y1 : r[index - 1];
+ r[index + 2] = r[index] + width;
+ r[index + 3] = y1;
+
+ y2 = y1;
+ if (j < elementCount - 1) {
+ y1 = (int) (y2 - data[i + j + 1] * height);
+ }
+ }
+ } break;
+ }
+
+
+ x += width;
+ count++;
+ }
+
+ x += margin;
+
+ drawGraph(graphType, count);
+ drawCurrentFrame(graphType, current);
+ drawThreshold(x, height);
+ }
+ }
+
+ private void drawGraph(int graphType, int count) {
+ for (int i = 0; i < mProfileShapes.length; i++) {
+ mDebugDataProvider.setupGraphPaint(mProfilePaint, i);
+ switch (graphType) {
+ case GraphDataProvider.GRAPH_TYPE_BARS:
+ mGlCanvas.drawRects(mProfileShapes[i], count * 4, mProfilePaint);
+ break;
+ case GraphDataProvider.GRAPH_TYPE_LINES:
+ mGlCanvas.drawLines(mProfileShapes[i], 0, count * 4, mProfilePaint);
+ break;
+ }
+ }
+ }
+
+ private void drawCurrentFrame(int graphType, int index) {
+ if (index >= 0) {
+ mDebugDataProvider.setupCurrentFramePaint(mProfilePaint);
+ switch (graphType) {
+ case GraphDataProvider.GRAPH_TYPE_BARS:
+ mGlCanvas.drawRect(mProfileShapes[2][index], mProfileShapes[2][index + 1],
+ mProfileShapes[2][index + 2], mProfileShapes[0][index + 3],
+ mProfilePaint);
+ break;
+ case GraphDataProvider.GRAPH_TYPE_LINES:
+ mGlCanvas.drawLine(mProfileShapes[2][index], mProfileShapes[2][index + 1],
+ mProfileShapes[2][index], mHeight, mProfilePaint);
+ break;
+ }
+ }
+ }
+
+ private void drawThreshold(int x, int height) {
+ float threshold = mDebugDataProvider.getThreshold();
+ if (threshold > 0.0f) {
+ mDebugDataProvider.setupThresholdPaint(mProfilePaint);
+ int y = (int) (mHeight - threshold * height);
+ mGlCanvas.drawLine(0.0f, y, x, y, mProfilePaint);
+ }
+ }
+
+ private void initProfileDrawData(View.AttachInfo attachInfo, GraphDataProvider provider) {
+ if (mProfileShapes == null) {
+ final int elementCount = provider.getElementCount();
+ final int frameCount = provider.getFrameCount();
+
+ mProfileShapes = new float[elementCount][];
+ for (int i = 0; i < elementCount; i++) {
+ mProfileShapes[i] = new float[frameCount * 4];
+ }
+
+ mProfilePaint = new Paint();
+ }
+
+ mProfilePaint.reset();
+ if (provider.getGraphType() == GraphDataProvider.GRAPH_TYPE_LINES) {
+ mProfilePaint.setAntiAlias(true);
+ }
+
+ if (mDisplayMetrics == null) {
+ mDisplayMetrics = new DisplayMetrics();
+ }
+
+ attachInfo.mDisplay.getMetrics(mDisplayMetrics);
+ provider.prepare(mDisplayMetrics);
+ }
+
+ @Override
+ void destroy(boolean full) {
+ try {
+ if (full && mCanvas != null) {
+ mCanvas = null;
+ }
+
+ if (!isEnabled() || mDestroyed) {
+ setEnabled(false);
+ return;
+ }
+
+ destroySurface();
+ setEnabled(false);
+
+ mDestroyed = true;
+ mGl = null;
+ } finally {
+ if (full && mGlCanvas != null) {
+ mGlCanvas = null;
+ }
+ }
+ }
+
+ @Override
+ void pushLayerUpdate(HardwareLayer layer) {
+ mGlCanvas.pushLayerUpdate(layer);
+ }
+
+ @Override
+ void cancelLayerUpdate(HardwareLayer layer) {
+ mGlCanvas.cancelLayerUpdate(layer);
+ }
+
+ @Override
+ void flushLayerUpdates() {
+ mGlCanvas.flushLayerUpdates();
+ }
+
+ @Override
+ HardwareLayer createHardwareLayer(boolean isOpaque) {
+ return new GLES20TextureLayer(isOpaque);
+ }
+
+ @Override
+ public HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
+ return new GLES20RenderLayer(width, height, isOpaque);
+ }
+
+ void countOverdraw(HardwareCanvas canvas) {
+ ((GLES20Canvas) canvas).setCountOverdrawEnabled(true);
+ }
+
+ float getOverdraw(HardwareCanvas canvas) {
+ return ((GLES20Canvas) canvas).getOverdraw();
+ }
+
+ @Override
+ public SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
+ return ((GLES20TextureLayer) layer).getSurfaceTexture();
+ }
+
+ @Override
+ void setSurfaceTexture(HardwareLayer layer, SurfaceTexture surfaceTexture) {
+ ((GLES20TextureLayer) layer).setSurfaceTexture(surfaceTexture);
+ }
+
+ @Override
+ boolean safelyRun(Runnable action) {
+ boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR;
+
+ if (needsContext) {
+ GLRendererEglContext managedContext =
+ (GLRendererEglContext) sEglContextStorage.get();
+ if (managedContext == null) return false;
+ usePbufferSurface(managedContext.getContext());
+ }
+
+ try {
+ action.run();
+ } finally {
+ if (needsContext) {
+ sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
+ EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ void destroyLayers(final View view) {
+ if (view != null) {
+ safelyRun(new Runnable() {
+ @Override
+ public void run() {
+ if (mCanvas != null) {
+ mCanvas.clearLayerUpdates();
+ }
+ destroyHardwareLayer(view);
+ GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
+ }
+ });
+ }
+ }
+
+ private static void destroyHardwareLayer(View view) {
+ view.destroyLayer(true);
+
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+
+ int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ destroyHardwareLayer(group.getChildAt(i));
+ }
+ }
+ }
+
+ @Override
+ void destroyHardwareResources(final View view) {
+ if (view != null) {
+ safelyRun(new Runnable() {
+ @Override
+ public void run() {
+ if (mCanvas != null) {
+ mCanvas.clearLayerUpdates();
+ }
+ destroyResources(view);
+ GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
+ }
+ });
+ }
+ }
+
+ private static void destroyResources(View view) {
+ view.destroyHardwareResources();
+
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+
+ int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ destroyResources(group.getChildAt(i));
+ }
+ }
+ }
+
+ static void startTrimMemory(int level) {
+ if (sEgl == null || sEglConfig == null) return;
+
+ GLRendererEglContext managedContext =
+ (GLRendererEglContext) sEglContextStorage.get();
+ // We do not have OpenGL objects
+ if (managedContext == null) {
+ return;
+ } else {
+ usePbufferSurface(managedContext.getContext());
+ }
+
+ if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
+ GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL);
+ } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
+ GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE);
+ }
+ }
+
+ static void endTrimMemory() {
+ if (sEgl != null && sEglDisplay != null) {
+ sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ }
+ }
+
+ private static void usePbufferSurface(EGLContext eglContext) {
+ synchronized (sPbufferLock) {
+ // Create a temporary 1x1 pbuffer so we have a context
+ // to clear our OpenGL objects
+ if (sPbuffer == null) {
+ sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] {
+ EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE
+ });
+ }
+ }
+ sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext);
+ }
+
+ GLRenderer(boolean translucent) {
+ mTranslucent = translucent;
+
+ loadSystemProperties(null);
+ }
+
+ @Override
+ boolean loadSystemProperties(Surface surface) {
+ boolean value;
+ boolean changed = false;
+
+ String profiling = SystemProperties.get(PROFILE_PROPERTY);
+ int graphType = search(VISUALIZERS, profiling);
+ value = graphType >= 0;
+
+ if (graphType != mProfileVisualizerType) {
+ changed = true;
+ mProfileVisualizerType = graphType;
+
+ mProfileShapes = null;
+ mProfilePaint = null;
+
+ if (value) {
+ mDebugDataProvider = new DrawPerformanceDataProvider(graphType);
+ } else {
+ mDebugDataProvider = null;
+ }
+ }
+
+ // If on-screen profiling is not enabled, we need to check whether
+ // console profiling only is enabled
+ if (!value) {
+ value = Boolean.parseBoolean(profiling);
+ }
+
+ if (value != mProfileEnabled) {
+ changed = true;
+ mProfileEnabled = value;
+
+ if (mProfileEnabled) {
+ Log.d(LOG_TAG, "Profiling hardware renderer");
+
+ int maxProfileFrames = SystemProperties.getInt(PROFILE_MAXFRAMES_PROPERTY,
+ PROFILE_MAX_FRAMES);
+ mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT];
+ for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) {
+ mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1;
+ }
+
+ mProfileLock = new ReentrantLock();
+ } else {
+ mProfileData = null;
+ mProfileLock = null;
+ mProfileVisualizerType = -1;
+ }
+
+ mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
+ }
+
+ value = SystemProperties.getBoolean(DEBUG_DIRTY_REGIONS_PROPERTY, false);
+ if (value != mDebugDirtyRegions) {
+ changed = true;
+ mDebugDirtyRegions = value;
+
+ if (mDebugDirtyRegions) {
+ Log.d(LOG_TAG, "Debugging dirty regions");
+ }
+ }
+
+ String overdraw = SystemProperties.get(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY);
+ int debugOverdraw = search(OVERDRAW, overdraw);
+ if (debugOverdraw != mDebugOverdraw) {
+ changed = true;
+ mDebugOverdraw = debugOverdraw;
+
+ if (mDebugOverdraw != OVERDRAW_TYPE_COUNT) {
+ if (mDebugOverdrawLayer != null) {
+ mDebugOverdrawLayer.destroy();
+ mDebugOverdrawLayer = null;
+ mDebugOverdrawPaint = null;
+ }
+ }
+ }
+
+ if (loadProperties()) {
+ changed = true;
+ }
+
+ return changed;
+ }
+
+ private static int search(String[] values, String value) {
+ for (int i = 0; i < values.length; i++) {
+ if (values[i].equals(value)) return i;
+ }
+ return -1;
+ }
+
+ @Override
+ void dumpGfxInfo(PrintWriter pw) {
+ if (mProfileEnabled) {
+ pw.printf("\n\tDraw\tProcess\tExecute\n");
+
+ mProfileLock.lock();
+ try {
+ for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) {
+ if (mProfileData[i] < 0) {
+ break;
+ }
+ pw.printf("\t%3.2f\t%3.2f\t%3.2f\n", mProfileData[i], mProfileData[i + 1],
+ mProfileData[i + 2]);
+ mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1;
+ }
+ mProfileCurrentFrame = mProfileData.length;
+ } finally {
+ mProfileLock.unlock();
+ }
+ }
+ }
+
+ @Override
+ long getFrameCount() {
+ return mFrameCount;
+ }
+
+ /**
+ * Indicates whether this renderer instance can track and update dirty regions.
+ */
+ boolean hasDirtyRegions() {
+ return mDirtyRegionsEnabled;
+ }
+
+ /**
+ * Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)}
+ * is invoked and the requested flag is turned off. The error code is
+ * also logged as a warning.
+ */
+ void checkEglErrors() {
+ if (isEnabled()) {
+ checkEglErrorsForced();
+ }
+ }
+
+ private void checkEglErrorsForced() {
+ int error = sEgl.eglGetError();
+ if (error != EGL_SUCCESS) {
+ // something bad has happened revert to
+ // normal rendering.
+ Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error));
+ fallback(error != EGL11.EGL_CONTEXT_LOST);
+ }
+ }
+
+ private void fallback(boolean fallback) {
+ destroy(true);
+ if (fallback) {
+ // we'll try again if it was context lost
+ setRequested(false);
+ Log.w(LOG_TAG, "Mountain View, we've had a problem here. "
+ + "Switching back to software rendering.");
+ }
+ }
+
+ @Override
+ boolean initialize(Surface surface) throws OutOfResourcesException {
+ if (isRequested() && !isEnabled()) {
+ boolean contextCreated = initializeEgl();
+ mGl = createEglSurface(surface);
+ mDestroyed = false;
+
+ if (mGl != null) {
+ int err = sEgl.eglGetError();
+ if (err != EGL_SUCCESS) {
+ destroy(true);
+ setRequested(false);
+ } else {
+ if (mCanvas == null) {
+ mCanvas = createCanvas();
+ mCanvas.setName(mName);
+ }
+ setEnabled(true);
+
+ if (contextCreated) {
+ initAtlas();
+ }
+ }
+
+ return mCanvas != null;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ void updateSurface(Surface surface) throws OutOfResourcesException {
+ if (isRequested() && isEnabled()) {
+ createEglSurface(surface);
+ }
+ }
+
+ boolean initializeEgl() {
+ synchronized (sEglLock) {
+ if (sEgl == null && sEglConfig == null) {
+ sEgl = (EGL10) EGLContext.getEGL();
+
+ // Get to the default display.
+ sEglDisplay = sEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
+
+ if (sEglDisplay == EGL_NO_DISPLAY) {
+ throw new RuntimeException("eglGetDisplay failed "
+ + GLUtils.getEGLErrorString(sEgl.eglGetError()));
+ }
+
+ // We can now initialize EGL for that display
+ int[] version = new int[2];
+ if (!sEgl.eglInitialize(sEglDisplay, version)) {
+ throw new RuntimeException("eglInitialize failed " +
+ GLUtils.getEGLErrorString(sEgl.eglGetError()));
+ }
+
+ checkEglErrorsForced();
+
+ sEglConfig = loadEglConfig();
+ }
+ }
+
+ ManagedEGLContext managedContext = sEglContextStorage.get();
+ mEglContext = managedContext != null ? managedContext.getContext() : null;
+ mEglThread = Thread.currentThread();
+
+ if (mEglContext == null) {
+ mEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
+ sEglContextStorage.set(createManagedContext(mEglContext));
+ return true;
+ }
+
+ return false;
+ }
+
+ private EGLConfig loadEglConfig() {
+ EGLConfig eglConfig = chooseEglConfig();
+ if (eglConfig == null) {
+ // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without
+ if (sDirtyRegions) {
+ sDirtyRegions = false;
+ eglConfig = chooseEglConfig();
+ if (eglConfig == null) {
+ throw new RuntimeException("eglConfig not initialized");
+ }
+ } else {
+ throw new RuntimeException("eglConfig not initialized");
+ }
+ }
+ return eglConfig;
+ }
+
+ private EGLConfig chooseEglConfig() {
+ EGLConfig[] configs = new EGLConfig[1];
+ int[] configsCount = new int[1];
+ int[] configSpec = getConfig(sDirtyRegions);
+
+ // Debug
+ final String debug = SystemProperties.get(PRINT_CONFIG_PROPERTY, "");
+ if ("all".equalsIgnoreCase(debug)) {
+ sEgl.eglChooseConfig(sEglDisplay, configSpec, null, 0, configsCount);
+
+ EGLConfig[] debugConfigs = new EGLConfig[configsCount[0]];
+ sEgl.eglChooseConfig(sEglDisplay, configSpec, debugConfigs,
+ configsCount[0], configsCount);
+
+ for (EGLConfig config : debugConfigs) {
+ printConfig(config);
+ }
+ }
+
+ if (!sEgl.eglChooseConfig(sEglDisplay, configSpec, configs, 1, configsCount)) {
+ throw new IllegalArgumentException("eglChooseConfig failed " +
+ GLUtils.getEGLErrorString(sEgl.eglGetError()));
+ } else if (configsCount[0] > 0) {
+ if ("choice".equalsIgnoreCase(debug)) {
+ printConfig(configs[0]);
+ }
+ return configs[0];
+ }
+
+ return null;
+ }
+
+ private static void printConfig(EGLConfig config) {
+ int[] value = new int[1];
+
+ Log.d(LOG_TAG, "EGL configuration " + config + ":");
+
+ sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_RED_SIZE, value);
+ Log.d(LOG_TAG, " RED_SIZE = " + value[0]);
+
+ sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_GREEN_SIZE, value);
+ Log.d(LOG_TAG, " GREEN_SIZE = " + value[0]);
+
+ sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_BLUE_SIZE, value);
+ Log.d(LOG_TAG, " BLUE_SIZE = " + value[0]);
+
+ sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_ALPHA_SIZE, value);
+ Log.d(LOG_TAG, " ALPHA_SIZE = " + value[0]);
+
+ sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_DEPTH_SIZE, value);
+ Log.d(LOG_TAG, " DEPTH_SIZE = " + value[0]);
+
+ sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_STENCIL_SIZE, value);
+ Log.d(LOG_TAG, " STENCIL_SIZE = " + value[0]);
+
+ sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLE_BUFFERS, value);
+ Log.d(LOG_TAG, " SAMPLE_BUFFERS = " + value[0]);
+
+ sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLES, value);
+ Log.d(LOG_TAG, " SAMPLES = " + value[0]);
+
+ sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SURFACE_TYPE, value);
+ Log.d(LOG_TAG, " SURFACE_TYPE = 0x" + Integer.toHexString(value[0]));
+
+ sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_CONFIG_CAVEAT, value);
+ Log.d(LOG_TAG, " CONFIG_CAVEAT = 0x" + Integer.toHexString(value[0]));
+ }
+
+ GL createEglSurface(Surface surface) throws OutOfResourcesException {
+ // Check preconditions.
+ if (sEgl == null) {
+ throw new RuntimeException("egl not initialized");
+ }
+ if (sEglDisplay == null) {
+ throw new RuntimeException("eglDisplay not initialized");
+ }
+ if (sEglConfig == null) {
+ throw new RuntimeException("eglConfig not initialized");
+ }
+ if (Thread.currentThread() != mEglThread) {
+ throw new IllegalStateException("HardwareRenderer cannot be used "
+ + "from multiple threads");
+ }
+
+ // In case we need to destroy an existing surface
+ destroySurface();
+
+ // Create an EGL surface we can render into.
+ if (!createSurface(surface)) {
+ return null;
+ }
+
+ initCaches();
+
+ return mEglContext.getGL();
+ }
+
+ private void enableDirtyRegions() {
+ // If mDirtyRegions is set, this means we have an EGL configuration
+ // with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set
+ if (sDirtyRegions) {
+ if (!(mDirtyRegionsEnabled = preserveBackBuffer())) {
+ Log.w(LOG_TAG, "Backbuffer cannot be preserved");
+ }
+ } else if (sDirtyRegionsRequested) {
+ // If mDirtyRegions is not set, our EGL configuration does not
+ // have EGL_SWAP_BEHAVIOR_PRESERVED_BIT; however, the default
+ // swap behavior might be EGL_BUFFER_PRESERVED, which means we
+ // want to set mDirtyRegions. We try to do this only if dirty
+ // regions were initially requested as part of the device
+ // configuration (see RENDER_DIRTY_REGIONS)
+ mDirtyRegionsEnabled = isBackBufferPreserved();
+ }
+ }
+
+ EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
+ final int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, GL_VERSION, EGL_NONE };
+
+ EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT,
+ attribs);
+ if (context == null || context == EGL_NO_CONTEXT) {
+ //noinspection ConstantConditions
+ throw new IllegalStateException(
+ "Could not create an EGL context. eglCreateContext failed with error: " +
+ GLUtils.getEGLErrorString(sEgl.eglGetError()));
+ }
+
+ return context;
+ }
+
+ void destroySurface() {
+ if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
+ if (mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) {
+ sEgl.eglMakeCurrent(sEglDisplay,
+ EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ }
+ sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
+ mEglSurface = null;
+ }
+ }
+
+ @Override
+ void invalidate(Surface surface) {
+ // Cancels any existing buffer to ensure we'll get a buffer
+ // of the right size before we call eglSwapBuffers
+ sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+ if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
+ sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
+ mEglSurface = null;
+ setEnabled(false);
+ }
+
+ if (surface.isValid()) {
+ if (!createSurface(surface)) {
+ return;
+ }
+
+ mUpdateDirtyRegions = true;
+
+ if (mCanvas != null) {
+ setEnabled(true);
+ }
+ }
+ }
+
+ private boolean createSurface(Surface surface) {
+ mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, surface, null);
+
+ if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
+ int error = sEgl.eglGetError();
+ if (error == EGL_BAD_NATIVE_WINDOW) {
+ Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
+ return false;
+ }
+ throw new RuntimeException("createWindowSurface failed "
+ + GLUtils.getEGLErrorString(error));
+ }
+
+ if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+ throw new IllegalStateException("eglMakeCurrent failed " +
+ GLUtils.getEGLErrorString(sEgl.eglGetError()));
+ }
+
+ enableDirtyRegions();
+
+ return true;
+ }
+
+ @Override
+ boolean validate() {
+ return checkRenderContext() != SURFACE_STATE_ERROR;
+ }
+
+ @Override
+ void setup(int width, int height) {
+ if (validate()) {
+ mCanvas.setViewport(width, height);
+ mWidth = width;
+ mHeight = height;
+ }
+ }
+
+ @Override
+ int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ HardwareCanvas getCanvas() {
+ return mCanvas;
+ }
+
+ @Override
+ void setName(String name) {
+ mName = name;
+ }
+
+ class FunctorsRunnable implements Runnable {
+ View.AttachInfo attachInfo;
+
+ @Override
+ public void run() {
+ final HardwareRenderer renderer = attachInfo.mHardwareRenderer;
+ if (renderer == null || !renderer.isEnabled() || renderer != GLRenderer.this) {
+ return;
+ }
+
+ if (checkRenderContext() != SURFACE_STATE_ERROR) {
+ int status = mCanvas.invokeFunctors(mRedrawClip);
+ handleFunctorStatus(attachInfo, status);
+ }
+ }
+ }
+
+ @Override
+ void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
+ Rect dirty) {
+ if (canDraw()) {
+ if (!hasDirtyRegions()) {
+ dirty = null;
+ }
+ attachInfo.mIgnoreDirtyState = true;
+ attachInfo.mDrawingTime = SystemClock.uptimeMillis();
+
+ view.mPrivateFlags |= View.PFLAG_DRAWN;
+
+ // We are already on the correct thread
+ final int surfaceState = checkRenderContextUnsafe();
+ if (surfaceState != SURFACE_STATE_ERROR) {
+ HardwareCanvas canvas = mCanvas;
+ attachInfo.mHardwareCanvas = canvas;
+
+ if (mProfileEnabled) {
+ mProfileLock.lock();
+ }
+
+ dirty = beginFrame(canvas, dirty, surfaceState);
+
+ DisplayList displayList = buildDisplayList(view, canvas);
+
+ // buildDisplayList() calls into user code which can cause
+ // an eglMakeCurrent to happen with a different surface/context.
+ // We must therefore check again here.
+ if (checkRenderContextUnsafe() == SURFACE_STATE_ERROR) {
+ return;
+ }
+
+ int saveCount = 0;
+ int status = DisplayList.STATUS_DONE;
+
+ long start = getSystemTime();
+ try {
+ status = prepareFrame(dirty);
+
+ saveCount = canvas.save();
+ callbacks.onHardwarePreDraw(canvas);
+
+ if (displayList != null) {
+ status |= drawDisplayList(attachInfo, canvas, displayList, status);
+ } else {
+ // Shouldn't reach here
+ view.draw(canvas);
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "An error has occurred while drawing:", e);
+ } finally {
+ callbacks.onHardwarePostDraw(canvas);
+ canvas.restoreToCount(saveCount);
+ view.mRecreateDisplayList = false;
+
+ mDrawDelta = getSystemTime() - start;
+
+ if (mDrawDelta > 0) {
+ mFrameCount++;
+
+ debugOverdraw(attachInfo, dirty, canvas, displayList);
+ debugDirtyRegions(dirty, canvas);
+ drawProfileData(attachInfo);
+ }
+ }
+
+ onPostDraw();
+
+ swapBuffers(status);
+
+ if (mProfileEnabled) {
+ mProfileLock.unlock();
+ }
+
+ attachInfo.mIgnoreDirtyState = false;
+ }
+ }
+ }
+
+ private void debugOverdraw(View.AttachInfo attachInfo, Rect dirty,
+ HardwareCanvas canvas, DisplayList displayList) {
+
+ if (mDebugOverdraw == OVERDRAW_TYPE_COUNT) {
+ if (mDebugOverdrawLayer == null) {
+ mDebugOverdrawLayer = createHardwareLayer(mWidth, mHeight, true);
+ } else if (mDebugOverdrawLayer.getWidth() != mWidth ||
+ mDebugOverdrawLayer.getHeight() != mHeight) {
+ mDebugOverdrawLayer.resize(mWidth, mHeight);
+ }
+
+ if (!mDebugOverdrawLayer.isValid()) {
+ mDebugOverdraw = -1;
+ return;
+ }
+
+ HardwareCanvas layerCanvas = mDebugOverdrawLayer.start(canvas, dirty);
+ countOverdraw(layerCanvas);
+ final int restoreCount = layerCanvas.save();
+ layerCanvas.drawDisplayList(displayList, null, DisplayList.FLAG_CLIP_CHILDREN);
+ layerCanvas.restoreToCount(restoreCount);
+ mDebugOverdrawLayer.end(canvas);
+
+ float overdraw = getOverdraw(layerCanvas);
+ DisplayMetrics metrics = attachInfo.mRootView.getResources().getDisplayMetrics();
+
+ drawOverdrawCounter(canvas, overdraw, metrics.density);
+ }
+ }
+
+ private void drawOverdrawCounter(HardwareCanvas canvas, float overdraw, float density) {
+ final String text = String.format("%.2fx", overdraw);
+ final Paint paint = setupPaint(density);
+ // HSBtoColor will clamp the values in the 0..1 range
+ paint.setColor(Color.HSBtoColor(0.28f - 0.28f * overdraw / 3.5f, 0.8f, 1.0f));
+
+ canvas.drawText(text, density * 4.0f, mHeight - paint.getFontMetrics().bottom, paint);
+ }
+
+ private Paint setupPaint(float density) {
+ if (mDebugOverdrawPaint == null) {
+ mDebugOverdrawPaint = new Paint();
+ mDebugOverdrawPaint.setAntiAlias(true);
+ mDebugOverdrawPaint.setShadowLayer(density * 3.0f, 0.0f, 0.0f, 0xff000000);
+ mDebugOverdrawPaint.setTextSize(density * 20.0f);
+ }
+ return mDebugOverdrawPaint;
+ }
+
+ private DisplayList buildDisplayList(View view, HardwareCanvas canvas) {
+ if (mDrawDelta <= 0) {
+ return view.mDisplayList;
+ }
+
+ view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
+ == View.PFLAG_INVALIDATED;
+ view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
+
+ long buildDisplayListStartTime = startBuildDisplayListProfiling();
+ canvas.clearLayerUpdates();
+
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
+ DisplayList displayList = view.getDisplayList();
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+
+ endBuildDisplayListProfiling(buildDisplayListStartTime);
+
+ return displayList;
+ }
+
+ private Rect beginFrame(HardwareCanvas canvas, Rect dirty, int surfaceState) {
+ // We had to change the current surface and/or context, redraw everything
+ if (surfaceState == SURFACE_STATE_UPDATED) {
+ dirty = null;
+ beginFrame(null);
+ } else {
+ int[] size = mSurfaceSize;
+ beginFrame(size);
+
+ if (size[1] != mHeight || size[0] != mWidth) {
+ mWidth = size[0];
+ mHeight = size[1];
+
+ canvas.setViewport(mWidth, mHeight);
+
+ dirty = null;
+ }
+ }
+
+ if (mDebugDataProvider != null) dirty = null;
+
+ return dirty;
+ }
+
+ private long startBuildDisplayListProfiling() {
+ if (mProfileEnabled) {
+ mProfileCurrentFrame += PROFILE_FRAME_DATA_COUNT;
+ if (mProfileCurrentFrame >= mProfileData.length) {
+ mProfileCurrentFrame = 0;
+ }
+
+ return System.nanoTime();
+ }
+ return 0;
+ }
+
+ private void endBuildDisplayListProfiling(long getDisplayListStartTime) {
+ if (mProfileEnabled) {
+ long now = System.nanoTime();
+ float total = (now - getDisplayListStartTime) * 0.000001f;
+ //noinspection PointlessArithmeticExpression
+ mProfileData[mProfileCurrentFrame] = total;
+ }
+ }
+
+ private int prepareFrame(Rect dirty) {
+ int status;
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareFrame");
+ try {
+ status = onPreDraw(dirty);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ return status;
+ }
+
+ private int drawDisplayList(View.AttachInfo attachInfo, HardwareCanvas canvas,
+ DisplayList displayList, int status) {
+
+ long drawDisplayListStartTime = 0;
+ if (mProfileEnabled) {
+ drawDisplayListStartTime = System.nanoTime();
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList");
+ try {
+ status |= canvas.drawDisplayList(displayList, mRedrawClip,
+ DisplayList.FLAG_CLIP_CHILDREN);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+
+ if (mProfileEnabled) {
+ long now = System.nanoTime();
+ float total = (now - drawDisplayListStartTime) * 0.000001f;
+ mProfileData[mProfileCurrentFrame + 1] = total;
+ }
+
+ handleFunctorStatus(attachInfo, status);
+ return status;
+ }
+
+ private void swapBuffers(int status) {
+ if ((status & DisplayList.STATUS_DREW) == DisplayList.STATUS_DREW) {
+ long eglSwapBuffersStartTime = 0;
+ if (mProfileEnabled) {
+ eglSwapBuffersStartTime = System.nanoTime();
+ }
+
+ sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
+
+ if (mProfileEnabled) {
+ long now = System.nanoTime();
+ float total = (now - eglSwapBuffersStartTime) * 0.000001f;
+ mProfileData[mProfileCurrentFrame + 2] = total;
+ }
+
+ checkEglErrors();
+ }
+ }
+
+ private void debugDirtyRegions(Rect dirty, HardwareCanvas canvas) {
+ if (mDebugDirtyRegions) {
+ if (mDebugPaint == null) {
+ mDebugPaint = new Paint();
+ mDebugPaint.setColor(0x7fff0000);
+ }
+
+ if (dirty != null && (mFrameCount & 1) == 0) {
+ canvas.drawRect(dirty, mDebugPaint);
+ }
+ }
+ }
+
+ private void handleFunctorStatus(View.AttachInfo attachInfo, int status) {
+ // If the draw flag is set, functors will be invoked while executing
+ // the tree of display lists
+ if ((status & DisplayList.STATUS_DRAW) != 0) {
+ if (mRedrawClip.isEmpty()) {
+ attachInfo.mViewRootImpl.invalidate();
+ } else {
+ attachInfo.mViewRootImpl.invalidateChildInParent(null, mRedrawClip);
+ mRedrawClip.setEmpty();
+ }
+ }
+
+ if ((status & DisplayList.STATUS_INVOKE) != 0 ||
+ attachInfo.mHandler.hasCallbacks(mFunctorsRunnable)) {
+ attachInfo.mHandler.removeCallbacks(mFunctorsRunnable);
+ mFunctorsRunnable.attachInfo = attachInfo;
+ attachInfo.mHandler.postDelayed(mFunctorsRunnable, FUNCTOR_PROCESS_DELAY);
+ }
+ }
+
+ @Override
+ void detachFunctor(int functor) {
+ if (mCanvas != null) {
+ mCanvas.detachFunctor(functor);
+ }
+ }
+
+ @Override
+ boolean attachFunctor(View.AttachInfo attachInfo, int functor) {
+ if (mCanvas != null) {
+ mCanvas.attachFunctor(functor);
+ mFunctorsRunnable.attachInfo = attachInfo;
+ attachInfo.mHandler.removeCallbacks(mFunctorsRunnable);
+ attachInfo.mHandler.postDelayed(mFunctorsRunnable, 0);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Ensures the current EGL context and surface are the ones we expect.
+ * This method throws an IllegalStateException if invoked from a thread
+ * that did not initialize EGL.
+ *
+ * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current,
+ * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or
+ * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one
+ *
+ * @see #checkRenderContextUnsafe()
+ */
+ int checkRenderContext() {
+ if (mEglThread != Thread.currentThread()) {
+ throw new IllegalStateException("Hardware acceleration can only be used with a " +
+ "single UI thread.\nOriginal thread: " + mEglThread + "\n" +
+ "Current thread: " + Thread.currentThread());
+ }
+
+ return checkRenderContextUnsafe();
+ }
+
+ /**
+ * Ensures the current EGL context and surface are the ones we expect.
+ * This method does not check the current thread.
+ *
+ * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current,
+ * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or
+ * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one
+ *
+ * @see #checkRenderContext()
+ */
+ private int checkRenderContextUnsafe() {
+ if (!mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW)) ||
+ !mEglContext.equals(sEgl.eglGetCurrentContext())) {
+ if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+ Log.e(LOG_TAG, "eglMakeCurrent failed " +
+ GLUtils.getEGLErrorString(sEgl.eglGetError()));
+ fallback(true);
+ return SURFACE_STATE_ERROR;
+ } else {
+ if (mUpdateDirtyRegions) {
+ enableDirtyRegions();
+ mUpdateDirtyRegions = false;
+ }
+ return SURFACE_STATE_UPDATED;
+ }
+ }
+ return SURFACE_STATE_SUCCESS;
+ }
+
+ private static int dpToPx(int dp, float density) {
+ return (int) (dp * density + 0.5f);
+ }
+
+ private static native boolean loadProperties();
+
+ static native void setupShadersDiskCache(String cacheFile);
+
+ /**
+ * Notifies EGL that the frame is about to be rendered.
+ * @param size
+ */
+ private static native void beginFrame(int[] size);
+
+ /**
+ * Returns the current system time according to the renderer.
+ * This method is used for debugging only and should not be used
+ * as a clock.
+ */
+ private static native long getSystemTime();
+
+ /**
+ * Preserves the back buffer of the current surface after a buffer swap.
+ * Calling this method sets the EGL_SWAP_BEHAVIOR attribute of the current
+ * surface to EGL_BUFFER_PRESERVED. Calling this method requires an EGL
+ * config that supports EGL_SWAP_BEHAVIOR_PRESERVED_BIT.
+ *
+ * @return True if the swap behavior was successfully changed,
+ * false otherwise.
+ */
+ private static native boolean preserveBackBuffer();
+
+ /**
+ * Indicates whether the current surface preserves its back buffer
+ * after a buffer swap.
+ *
+ * @return True, if the surface's EGL_SWAP_BEHAVIOR is EGL_BUFFER_PRESERVED,
+ * false otherwise
+ */
+ private static native boolean isBackBufferPreserved();
+
+ class DrawPerformanceDataProvider extends GraphDataProvider {
+ private final int mGraphType;
+
+ private int mVerticalUnit;
+ private int mHorizontalUnit;
+ private int mHorizontalMargin;
+ private int mThresholdStroke;
+
+ DrawPerformanceDataProvider(int graphType) {
+ mGraphType = graphType;
+ }
+
+ @Override
+ void prepare(DisplayMetrics metrics) {
+ final float density = metrics.density;
+
+ mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density);
+ mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density);
+ mHorizontalMargin = dpToPx(PROFILE_DRAW_MARGIN, density);
+ mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density);
+ }
+
+ @Override
+ int getGraphType() {
+ return mGraphType;
+ }
+
+ @Override
+ int getVerticalUnitSize() {
+ return mVerticalUnit;
+ }
+
+ @Override
+ int getHorizontalUnitSize() {
+ return mHorizontalUnit;
+ }
+
+ @Override
+ int getHorizontaUnitMargin() {
+ return mHorizontalMargin;
+ }
+
+ @Override
+ float[] getData() {
+ return mProfileData;
+ }
+
+ @Override
+ float getThreshold() {
+ return 16;
+ }
+
+ @Override
+ int getFrameCount() {
+ return mProfileData.length / PROFILE_FRAME_DATA_COUNT;
+ }
+
+ @Override
+ int getElementCount() {
+ return PROFILE_FRAME_DATA_COUNT;
+ }
+
+ @Override
+ int getCurrentFrame() {
+ return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT;
+ }
+
+ @Override
+ void setupGraphPaint(Paint paint, int elementIndex) {
+ paint.setColor(PROFILE_DRAW_COLORS[elementIndex]);
+ if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke);
+ }
+
+ @Override
+ void setupThresholdPaint(Paint paint) {
+ paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR);
+ paint.setStrokeWidth(mThresholdStroke);
+ }
+
+ @Override
+ void setupCurrentFramePaint(Paint paint) {
+ paint.setColor(PROFILE_DRAW_CURRENT_FRAME_COLOR);
+ if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke);
+ }
+ }
+}
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 8f40260..26f47f9 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -41,6 +41,11 @@ public class HapticFeedbackConstants {
public static final int KEYBOARD_TAP = 3;
/**
+ * The user has pressed either an hour or minute tick of a Clock.
+ */
+ public static final int CLOCK_TICK = 4;
+
+ /**
* This is a private constant. Feel free to renumber as desired.
* @hide
*/
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index f215189..5c0be4a 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -16,41 +16,14 @@
package android.view;
-import android.content.ComponentCallbacks2;
-import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
-import android.opengl.EGL14;
-import android.opengl.GLUtils;
-import android.opengl.ManagedEGLContext;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.Trace;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.view.Surface.OutOfResourcesException;
-import com.google.android.gles_jni.EGLImpl;
-
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGL11;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.egl.EGLSurface;
-import javax.microedition.khronos.opengles.GL;
-
import java.io.File;
import java.io.PrintWriter;
-import java.util.concurrent.locks.ReentrantLock;
-
-import static javax.microedition.khronos.egl.EGL10.*;
/**
* Interface for rendering a view hierarchy using hardware acceleration.
@@ -66,13 +39,6 @@ public abstract class HardwareRenderer {
private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache";
/**
- * Turn on to only refresh the parts of the screen that need updating.
- * When turned on the property defined by {@link #RENDER_DIRTY_REGIONS_PROPERTY}
- * must also have the value "true".
- */
- static final boolean RENDER_DIRTY_REGIONS = true;
-
- /**
* System property used to enable or disable dirty regions invalidation.
* This property is only queried if {@link #RENDER_DIRTY_REGIONS} is true.
* The default value of this property is assumed to be true.
@@ -222,16 +188,6 @@ public abstract class HardwareRenderer {
*/
public static boolean sSystemRendererDisabled = false;
- /**
- * Number of frames to profile.
- */
- private static final int PROFILE_MAX_FRAMES = 128;
-
- /**
- * Number of floats per profiled frame.
- */
- private static final int PROFILE_FRAME_DATA_COUNT = 3;
-
private boolean mEnabled;
private boolean mRequested = true;
@@ -381,8 +337,6 @@ public abstract class HardwareRenderer {
*/
abstract boolean loadSystemProperties(Surface surface);
- private static native boolean nLoadProperties();
-
/**
* Sets the directory to use as a persistent storage for hardware rendering
* resources.
@@ -392,60 +346,9 @@ public abstract class HardwareRenderer {
* @hide
*/
public static void setupDiskCache(File cacheDir) {
- nSetupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
- }
-
- private static native void nSetupShadersDiskCache(String cacheFile);
-
- /**
- * Notifies EGL that the frame is about to be rendered.
- * @param size
- */
- static void beginFrame(int[] size) {
- nBeginFrame(size);
+ GLRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
}
- private static native void nBeginFrame(int[] size);
-
- /**
- * Returns the current system time according to the renderer.
- * This method is used for debugging only and should not be used
- * as a clock.
- */
- static long getSystemTime() {
- return nGetSystemTime();
- }
-
- private static native long nGetSystemTime();
-
- /**
- * Preserves the back buffer of the current surface after a buffer swap.
- * Calling this method sets the EGL_SWAP_BEHAVIOR attribute of the current
- * surface to EGL_BUFFER_PRESERVED. Calling this method requires an EGL
- * config that supports EGL_SWAP_BEHAVIOR_PRESERVED_BIT.
- *
- * @return True if the swap behavior was successfully changed,
- * false otherwise.
- */
- static boolean preserveBackBuffer() {
- return nPreserveBackBuffer();
- }
-
- private static native boolean nPreserveBackBuffer();
-
- /**
- * Indicates whether the current surface preserves its back buffer
- * after a buffer swap.
- *
- * @return True, if the surface's EGL_SWAP_BEHAVIOR is EGL_BUFFER_PRESERVED,
- * false otherwise
- */
- static boolean isBackBufferPreserved() {
- return nIsBackBufferPreserved();
- }
-
- private static native boolean nIsBackBufferPreserved();
-
/**
* Indicates that the specified hardware layer needs to be updated
* as soon as possible.
@@ -509,18 +412,6 @@ public abstract class HardwareRenderer {
Rect dirty);
/**
- * Creates a new display list that can be used to record batches of
- * drawing operations.
- *
- * @param name The name of the display list, used for debugging purpose. May be null.
- *
- * @return A new display list.
- *
- * @hide
- */
- public abstract DisplayList createDisplayList(String name);
-
- /**
* Creates a new hardware layer. A hardware layer built by calling this
* method will be treated as a texture layer, instead of as a render target.
*
@@ -621,17 +512,15 @@ public abstract class HardwareRenderer {
/**
* Creates a hardware renderer using OpenGL.
*
- * @param glVersion The version of OpenGL to use (1 for OpenGL 1, 11 for OpenGL 1.1, etc.)
* @param translucent True if the surface is translucent, false otherwise
*
* @return A hardware renderer backed by OpenGL.
*/
- static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) {
- switch (glVersion) {
- case 2:
- return Gl20Renderer.create(translucent);
+ static HardwareRenderer create(boolean translucent) {
+ if (GLES20Canvas.isAvailable()) {
+ return new GLRenderer(translucent);
}
- throw new IllegalArgumentException("Unknown GL version: " + glVersion);
+ return null;
}
/**
@@ -656,7 +545,7 @@ public abstract class HardwareRenderer {
* see {@link android.content.ComponentCallbacks}
*/
static void startTrimMemory(int level) {
- Gl20Renderer.startTrimMemory(level);
+ GLRenderer.startTrimMemory(level);
}
/**
@@ -664,7 +553,7 @@ public abstract class HardwareRenderer {
* cleanup special resources used by the memory trimming process.
*/
static void endTrimMemory() {
- Gl20Renderer.endTrimMemory();
+ GLRenderer.endTrimMemory();
}
/**
@@ -798,1553 +687,4 @@ public abstract class HardwareRenderer {
*/
abstract void setupCurrentFramePaint(Paint paint);
}
-
- @SuppressWarnings({"deprecation"})
- static abstract class GlRenderer extends HardwareRenderer {
- static final int SURFACE_STATE_ERROR = 0;
- static final int SURFACE_STATE_SUCCESS = 1;
- static final int SURFACE_STATE_UPDATED = 2;
-
- static final int FUNCTOR_PROCESS_DELAY = 4;
-
- private static final int PROFILE_DRAW_MARGIN = 0;
- private static final int PROFILE_DRAW_WIDTH = 3;
- private static final int[] PROFILE_DRAW_COLORS = { 0xcf3e66cc, 0xcfdc3912, 0xcfe69800 };
- private static final int PROFILE_DRAW_CURRENT_FRAME_COLOR = 0xcf5faa4d;
- private static final int PROFILE_DRAW_THRESHOLD_COLOR = 0xff5faa4d;
- private static final int PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2;
- private static final int PROFILE_DRAW_DP_PER_MS = 7;
-
- private static final String[] VISUALIZERS = {
- PROFILE_PROPERTY_VISUALIZE_BARS,
- PROFILE_PROPERTY_VISUALIZE_LINES
- };
-
- private static final String[] OVERDRAW = {
- OVERDRAW_PROPERTY_SHOW,
- OVERDRAW_PROPERTY_COUNT
- };
- private static final int OVERDRAW_TYPE_COUNT = 1;
-
- static EGL10 sEgl;
- static EGLDisplay sEglDisplay;
- static EGLConfig sEglConfig;
- static final Object[] sEglLock = new Object[0];
- int mWidth = -1, mHeight = -1;
-
- static final ThreadLocal<ManagedEGLContext> sEglContextStorage
- = new ThreadLocal<ManagedEGLContext>();
-
- EGLContext mEglContext;
- Thread mEglThread;
-
- EGLSurface mEglSurface;
-
- GL mGl;
- HardwareCanvas mCanvas;
-
- String mName;
-
- long mFrameCount;
- Paint mDebugPaint;
-
- static boolean sDirtyRegions;
- static final boolean sDirtyRegionsRequested;
- static {
- String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true");
- //noinspection PointlessBooleanExpression,ConstantConditions
- sDirtyRegions = RENDER_DIRTY_REGIONS && "true".equalsIgnoreCase(dirtyProperty);
- sDirtyRegionsRequested = sDirtyRegions;
- }
-
- boolean mDirtyRegionsEnabled;
- boolean mUpdateDirtyRegions;
-
- boolean mProfileEnabled;
- int mProfileVisualizerType = -1;
- float[] mProfileData;
- ReentrantLock mProfileLock;
- int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
-
- GraphDataProvider mDebugDataProvider;
- float[][] mProfileShapes;
- Paint mProfilePaint;
-
- boolean mDebugDirtyRegions;
- int mDebugOverdraw = -1;
- HardwareLayer mDebugOverdrawLayer;
- Paint mDebugOverdrawPaint;
-
- final int mGlVersion;
- final boolean mTranslucent;
-
- private boolean mDestroyed;
-
- private final Rect mRedrawClip = new Rect();
-
- private final int[] mSurfaceSize = new int[2];
- private final FunctorsRunnable mFunctorsRunnable = new FunctorsRunnable();
-
- private long mDrawDelta = Long.MAX_VALUE;
-
- GlRenderer(int glVersion, boolean translucent) {
- mGlVersion = glVersion;
- mTranslucent = translucent;
-
- loadSystemProperties(null);
- }
-
- @Override
- boolean loadSystemProperties(Surface surface) {
- boolean value;
- boolean changed = false;
-
- String profiling = SystemProperties.get(PROFILE_PROPERTY);
- int graphType = search(VISUALIZERS, profiling);
- value = graphType >= 0;
-
- if (graphType != mProfileVisualizerType) {
- changed = true;
- mProfileVisualizerType = graphType;
-
- mProfileShapes = null;
- mProfilePaint = null;
-
- if (value) {
- mDebugDataProvider = new DrawPerformanceDataProvider(graphType);
- } else {
- mDebugDataProvider = null;
- }
- }
-
- // If on-screen profiling is not enabled, we need to check whether
- // console profiling only is enabled
- if (!value) {
- value = Boolean.parseBoolean(profiling);
- }
-
- if (value != mProfileEnabled) {
- changed = true;
- mProfileEnabled = value;
-
- if (mProfileEnabled) {
- Log.d(LOG_TAG, "Profiling hardware renderer");
-
- int maxProfileFrames = SystemProperties.getInt(PROFILE_MAXFRAMES_PROPERTY,
- PROFILE_MAX_FRAMES);
- mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT];
- for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) {
- mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1;
- }
-
- mProfileLock = new ReentrantLock();
- } else {
- mProfileData = null;
- mProfileLock = null;
- mProfileVisualizerType = -1;
- }
-
- mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
- }
-
- value = SystemProperties.getBoolean(DEBUG_DIRTY_REGIONS_PROPERTY, false);
- if (value != mDebugDirtyRegions) {
- changed = true;
- mDebugDirtyRegions = value;
-
- if (mDebugDirtyRegions) {
- Log.d(LOG_TAG, "Debugging dirty regions");
- }
- }
-
- String overdraw = SystemProperties.get(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY);
- int debugOverdraw = search(OVERDRAW, overdraw);
- if (debugOverdraw != mDebugOverdraw) {
- changed = true;
- mDebugOverdraw = debugOverdraw;
-
- if (mDebugOverdraw != OVERDRAW_TYPE_COUNT) {
- if (mDebugOverdrawLayer != null) {
- mDebugOverdrawLayer.destroy();
- mDebugOverdrawLayer = null;
- mDebugOverdrawPaint = null;
- }
- }
- }
-
- if (nLoadProperties()) {
- changed = true;
- }
-
- return changed;
- }
-
- private static int search(String[] values, String value) {
- for (int i = 0; i < values.length; i++) {
- if (values[i].equals(value)) return i;
- }
- return -1;
- }
-
- @Override
- void dumpGfxInfo(PrintWriter pw) {
- if (mProfileEnabled) {
- pw.printf("\n\tDraw\tProcess\tExecute\n");
-
- mProfileLock.lock();
- try {
- for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) {
- if (mProfileData[i] < 0) {
- break;
- }
- pw.printf("\t%3.2f\t%3.2f\t%3.2f\n", mProfileData[i], mProfileData[i + 1],
- mProfileData[i + 2]);
- mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1;
- }
- mProfileCurrentFrame = mProfileData.length;
- } finally {
- mProfileLock.unlock();
- }
- }
- }
-
- @Override
- long getFrameCount() {
- return mFrameCount;
- }
-
- /**
- * Indicates whether this renderer instance can track and update dirty regions.
- */
- boolean hasDirtyRegions() {
- return mDirtyRegionsEnabled;
- }
-
- /**
- * Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)}
- * is invoked and the requested flag is turned off. The error code is
- * also logged as a warning.
- */
- void checkEglErrors() {
- if (isEnabled()) {
- checkEglErrorsForced();
- }
- }
-
- private void checkEglErrorsForced() {
- int error = sEgl.eglGetError();
- if (error != EGL_SUCCESS) {
- // something bad has happened revert to
- // normal rendering.
- Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error));
- fallback(error != EGL11.EGL_CONTEXT_LOST);
- }
- }
-
- private void fallback(boolean fallback) {
- destroy(true);
- if (fallback) {
- // we'll try again if it was context lost
- setRequested(false);
- Log.w(LOG_TAG, "Mountain View, we've had a problem here. "
- + "Switching back to software rendering.");
- }
- }
-
- @Override
- boolean initialize(Surface surface) throws OutOfResourcesException {
- if (isRequested() && !isEnabled()) {
- boolean contextCreated = initializeEgl();
- mGl = createEglSurface(surface);
- mDestroyed = false;
-
- if (mGl != null) {
- int err = sEgl.eglGetError();
- if (err != EGL_SUCCESS) {
- destroy(true);
- setRequested(false);
- } else {
- if (mCanvas == null) {
- mCanvas = createCanvas();
- mCanvas.setName(mName);
- }
- setEnabled(true);
-
- if (contextCreated) {
- initAtlas();
- }
- }
-
- return mCanvas != null;
- }
- }
- return false;
- }
-
- @Override
- void updateSurface(Surface surface) throws OutOfResourcesException {
- if (isRequested() && isEnabled()) {
- createEglSurface(surface);
- }
- }
-
- abstract HardwareCanvas createCanvas();
-
- abstract int[] getConfig(boolean dirtyRegions);
-
- boolean initializeEgl() {
- synchronized (sEglLock) {
- if (sEgl == null && sEglConfig == null) {
- sEgl = (EGL10) EGLContext.getEGL();
-
- // Get to the default display.
- sEglDisplay = sEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
-
- if (sEglDisplay == EGL_NO_DISPLAY) {
- throw new RuntimeException("eglGetDisplay failed "
- + GLUtils.getEGLErrorString(sEgl.eglGetError()));
- }
-
- // We can now initialize EGL for that display
- int[] version = new int[2];
- if (!sEgl.eglInitialize(sEglDisplay, version)) {
- throw new RuntimeException("eglInitialize failed " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- }
-
- checkEglErrorsForced();
-
- sEglConfig = loadEglConfig();
- }
- }
-
- ManagedEGLContext managedContext = sEglContextStorage.get();
- mEglContext = managedContext != null ? managedContext.getContext() : null;
- mEglThread = Thread.currentThread();
-
- if (mEglContext == null) {
- mEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
- sEglContextStorage.set(createManagedContext(mEglContext));
- return true;
- }
-
- return false;
- }
-
- private EGLConfig loadEglConfig() {
- EGLConfig eglConfig = chooseEglConfig();
- if (eglConfig == null) {
- // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without
- if (sDirtyRegions) {
- sDirtyRegions = false;
- eglConfig = chooseEglConfig();
- if (eglConfig == null) {
- throw new RuntimeException("eglConfig not initialized");
- }
- } else {
- throw new RuntimeException("eglConfig not initialized");
- }
- }
- return eglConfig;
- }
-
- abstract ManagedEGLContext createManagedContext(EGLContext eglContext);
-
- private EGLConfig chooseEglConfig() {
- EGLConfig[] configs = new EGLConfig[1];
- int[] configsCount = new int[1];
- int[] configSpec = getConfig(sDirtyRegions);
-
- // Debug
- final String debug = SystemProperties.get(PRINT_CONFIG_PROPERTY, "");
- if ("all".equalsIgnoreCase(debug)) {
- sEgl.eglChooseConfig(sEglDisplay, configSpec, null, 0, configsCount);
-
- EGLConfig[] debugConfigs = new EGLConfig[configsCount[0]];
- sEgl.eglChooseConfig(sEglDisplay, configSpec, debugConfigs,
- configsCount[0], configsCount);
-
- for (EGLConfig config : debugConfigs) {
- printConfig(config);
- }
- }
-
- if (!sEgl.eglChooseConfig(sEglDisplay, configSpec, configs, 1, configsCount)) {
- throw new IllegalArgumentException("eglChooseConfig failed " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- } else if (configsCount[0] > 0) {
- if ("choice".equalsIgnoreCase(debug)) {
- printConfig(configs[0]);
- }
- return configs[0];
- }
-
- return null;
- }
-
- private static void printConfig(EGLConfig config) {
- int[] value = new int[1];
-
- Log.d(LOG_TAG, "EGL configuration " + config + ":");
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_RED_SIZE, value);
- Log.d(LOG_TAG, " RED_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_GREEN_SIZE, value);
- Log.d(LOG_TAG, " GREEN_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_BLUE_SIZE, value);
- Log.d(LOG_TAG, " BLUE_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_ALPHA_SIZE, value);
- Log.d(LOG_TAG, " ALPHA_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_DEPTH_SIZE, value);
- Log.d(LOG_TAG, " DEPTH_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_STENCIL_SIZE, value);
- Log.d(LOG_TAG, " STENCIL_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLE_BUFFERS, value);
- Log.d(LOG_TAG, " SAMPLE_BUFFERS = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLES, value);
- Log.d(LOG_TAG, " SAMPLES = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SURFACE_TYPE, value);
- Log.d(LOG_TAG, " SURFACE_TYPE = 0x" + Integer.toHexString(value[0]));
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_CONFIG_CAVEAT, value);
- Log.d(LOG_TAG, " CONFIG_CAVEAT = 0x" + Integer.toHexString(value[0]));
- }
-
- GL createEglSurface(Surface surface) throws OutOfResourcesException {
- // Check preconditions.
- if (sEgl == null) {
- throw new RuntimeException("egl not initialized");
- }
- if (sEglDisplay == null) {
- throw new RuntimeException("eglDisplay not initialized");
- }
- if (sEglConfig == null) {
- throw new RuntimeException("eglConfig not initialized");
- }
- if (Thread.currentThread() != mEglThread) {
- throw new IllegalStateException("HardwareRenderer cannot be used "
- + "from multiple threads");
- }
-
- // In case we need to destroy an existing surface
- destroySurface();
-
- // Create an EGL surface we can render into.
- if (!createSurface(surface)) {
- return null;
- }
-
- initCaches();
-
- return mEglContext.getGL();
- }
-
- private void enableDirtyRegions() {
- // If mDirtyRegions is set, this means we have an EGL configuration
- // with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set
- if (sDirtyRegions) {
- if (!(mDirtyRegionsEnabled = preserveBackBuffer())) {
- Log.w(LOG_TAG, "Backbuffer cannot be preserved");
- }
- } else if (sDirtyRegionsRequested) {
- // If mDirtyRegions is not set, our EGL configuration does not
- // have EGL_SWAP_BEHAVIOR_PRESERVED_BIT; however, the default
- // swap behavior might be EGL_BUFFER_PRESERVED, which means we
- // want to set mDirtyRegions. We try to do this only if dirty
- // regions were initially requested as part of the device
- // configuration (see RENDER_DIRTY_REGIONS)
- mDirtyRegionsEnabled = isBackBufferPreserved();
- }
- }
-
- abstract void initCaches();
- abstract void initAtlas();
-
- EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
- int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE };
-
- EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT,
- mGlVersion != 0 ? attribs : null);
- if (context == null || context == EGL_NO_CONTEXT) {
- //noinspection ConstantConditions
- throw new IllegalStateException(
- "Could not create an EGL context. eglCreateContext failed with error: " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- }
-
- return context;
- }
-
- @Override
- void destroy(boolean full) {
- if (full && mCanvas != null) {
- mCanvas = null;
- }
-
- if (!isEnabled() || mDestroyed) {
- setEnabled(false);
- return;
- }
-
- destroySurface();
- setEnabled(false);
-
- mDestroyed = true;
- mGl = null;
- }
-
- void destroySurface() {
- if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
- if (mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) {
- sEgl.eglMakeCurrent(sEglDisplay,
- EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- }
- sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
- mEglSurface = null;
- }
- }
-
- @Override
- void invalidate(Surface surface) {
- // Cancels any existing buffer to ensure we'll get a buffer
- // of the right size before we call eglSwapBuffers
- sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-
- if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
- sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
- mEglSurface = null;
- setEnabled(false);
- }
-
- if (surface.isValid()) {
- if (!createSurface(surface)) {
- return;
- }
-
- mUpdateDirtyRegions = true;
-
- if (mCanvas != null) {
- setEnabled(true);
- }
- }
- }
-
- private boolean createSurface(Surface surface) {
- mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, surface, null);
-
- if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
- int error = sEgl.eglGetError();
- if (error == EGL_BAD_NATIVE_WINDOW) {
- Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
- return false;
- }
- throw new RuntimeException("createWindowSurface failed "
- + GLUtils.getEGLErrorString(error));
- }
-
- if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
- throw new IllegalStateException("eglMakeCurrent failed " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- }
-
- enableDirtyRegions();
-
- return true;
- }
-
- @Override
- boolean validate() {
- return checkRenderContext() != SURFACE_STATE_ERROR;
- }
-
- @Override
- void setup(int width, int height) {
- if (validate()) {
- mCanvas.setViewport(width, height);
- mWidth = width;
- mHeight = height;
- }
- }
-
- @Override
- int getWidth() {
- return mWidth;
- }
-
- @Override
- int getHeight() {
- return mHeight;
- }
-
- @Override
- HardwareCanvas getCanvas() {
- return mCanvas;
- }
-
- @Override
- void setName(String name) {
- mName = name;
- }
-
- boolean canDraw() {
- return mGl != null && mCanvas != null;
- }
-
- int onPreDraw(Rect dirty) {
- return DisplayList.STATUS_DONE;
- }
-
- void onPostDraw() {
- }
-
- class FunctorsRunnable implements Runnable {
- View.AttachInfo attachInfo;
-
- @Override
- public void run() {
- final HardwareRenderer renderer = attachInfo.mHardwareRenderer;
- if (renderer == null || !renderer.isEnabled() || renderer != GlRenderer.this) {
- return;
- }
-
- if (checkRenderContext() != SURFACE_STATE_ERROR) {
- int status = mCanvas.invokeFunctors(mRedrawClip);
- handleFunctorStatus(attachInfo, status);
- }
- }
- }
-
- @Override
- void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
- Rect dirty) {
- if (canDraw()) {
- if (!hasDirtyRegions()) {
- dirty = null;
- }
- attachInfo.mIgnoreDirtyState = true;
- attachInfo.mDrawingTime = SystemClock.uptimeMillis();
-
- view.mPrivateFlags |= View.PFLAG_DRAWN;
-
- // We are already on the correct thread
- final int surfaceState = checkRenderContextUnsafe();
- if (surfaceState != SURFACE_STATE_ERROR) {
- HardwareCanvas canvas = mCanvas;
- attachInfo.mHardwareCanvas = canvas;
-
- if (mProfileEnabled) {
- mProfileLock.lock();
- }
-
- dirty = beginFrame(canvas, dirty, surfaceState);
-
- DisplayList displayList = buildDisplayList(view, canvas);
-
- // buildDisplayList() calls into user code which can cause
- // an eglMakeCurrent to happen with a different surface/context.
- // We must therefore check again here.
- if (checkRenderContextUnsafe() == SURFACE_STATE_ERROR) {
- return;
- }
-
- int saveCount = 0;
- int status = DisplayList.STATUS_DONE;
-
- long start = getSystemTime();
- try {
- status = prepareFrame(dirty);
-
- saveCount = canvas.save();
- callbacks.onHardwarePreDraw(canvas);
-
- if (displayList != null) {
- status |= drawDisplayList(attachInfo, canvas, displayList, status);
- } else {
- // Shouldn't reach here
- view.draw(canvas);
- }
- } catch (Exception e) {
- Log.e(LOG_TAG, "An error has occurred while drawing:", e);
- } finally {
- callbacks.onHardwarePostDraw(canvas);
- canvas.restoreToCount(saveCount);
- view.mRecreateDisplayList = false;
-
- mDrawDelta = getSystemTime() - start;
-
- if (mDrawDelta > 0) {
- mFrameCount++;
-
- debugOverdraw(attachInfo, dirty, canvas, displayList);
- debugDirtyRegions(dirty, canvas);
- drawProfileData(attachInfo);
- }
- }
-
- onPostDraw();
-
- swapBuffers(status);
-
- if (mProfileEnabled) {
- mProfileLock.unlock();
- }
-
- attachInfo.mIgnoreDirtyState = false;
- }
- }
- }
-
- abstract void countOverdraw(HardwareCanvas canvas);
- abstract float getOverdraw(HardwareCanvas canvas);
-
- private void debugOverdraw(View.AttachInfo attachInfo, Rect dirty,
- HardwareCanvas canvas, DisplayList displayList) {
-
- if (mDebugOverdraw == OVERDRAW_TYPE_COUNT) {
- if (mDebugOverdrawLayer == null) {
- mDebugOverdrawLayer = createHardwareLayer(mWidth, mHeight, true);
- } else if (mDebugOverdrawLayer.getWidth() != mWidth ||
- mDebugOverdrawLayer.getHeight() != mHeight) {
- mDebugOverdrawLayer.resize(mWidth, mHeight);
- }
-
- if (!mDebugOverdrawLayer.isValid()) {
- mDebugOverdraw = -1;
- return;
- }
-
- HardwareCanvas layerCanvas = mDebugOverdrawLayer.start(canvas, dirty);
- countOverdraw(layerCanvas);
- final int restoreCount = layerCanvas.save();
- layerCanvas.drawDisplayList(displayList, null, DisplayList.FLAG_CLIP_CHILDREN);
- layerCanvas.restoreToCount(restoreCount);
- mDebugOverdrawLayer.end(canvas);
-
- float overdraw = getOverdraw(layerCanvas);
- DisplayMetrics metrics = attachInfo.mRootView.getResources().getDisplayMetrics();
-
- drawOverdrawCounter(canvas, overdraw, metrics.density);
- }
- }
-
- private void drawOverdrawCounter(HardwareCanvas canvas, float overdraw, float density) {
- final String text = String.format("%.2fx", overdraw);
- final Paint paint = setupPaint(density);
- // HSBtoColor will clamp the values in the 0..1 range
- paint.setColor(Color.HSBtoColor(0.28f - 0.28f * overdraw / 3.5f, 0.8f, 1.0f));
-
- canvas.drawText(text, density * 4.0f, mHeight - paint.getFontMetrics().bottom, paint);
- }
-
- private Paint setupPaint(float density) {
- if (mDebugOverdrawPaint == null) {
- mDebugOverdrawPaint = new Paint();
- mDebugOverdrawPaint.setAntiAlias(true);
- mDebugOverdrawPaint.setShadowLayer(density * 3.0f, 0.0f, 0.0f, 0xff000000);
- mDebugOverdrawPaint.setTextSize(density * 20.0f);
- }
- return mDebugOverdrawPaint;
- }
-
- private DisplayList buildDisplayList(View view, HardwareCanvas canvas) {
- if (mDrawDelta <= 0) {
- return view.mDisplayList;
- }
-
- view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
- == View.PFLAG_INVALIDATED;
- view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
-
- long buildDisplayListStartTime = startBuildDisplayListProfiling();
- canvas.clearLayerUpdates();
-
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
- DisplayList displayList = view.getDisplayList();
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
-
- endBuildDisplayListProfiling(buildDisplayListStartTime);
-
- return displayList;
- }
-
- abstract void drawProfileData(View.AttachInfo attachInfo);
-
- private Rect beginFrame(HardwareCanvas canvas, Rect dirty, int surfaceState) {
- // We had to change the current surface and/or context, redraw everything
- if (surfaceState == SURFACE_STATE_UPDATED) {
- dirty = null;
- beginFrame(null);
- } else {
- int[] size = mSurfaceSize;
- beginFrame(size);
-
- if (size[1] != mHeight || size[0] != mWidth) {
- mWidth = size[0];
- mHeight = size[1];
-
- canvas.setViewport(mWidth, mHeight);
-
- dirty = null;
- }
- }
-
- if (mDebugDataProvider != null) dirty = null;
-
- return dirty;
- }
-
- private long startBuildDisplayListProfiling() {
- if (mProfileEnabled) {
- mProfileCurrentFrame += PROFILE_FRAME_DATA_COUNT;
- if (mProfileCurrentFrame >= mProfileData.length) {
- mProfileCurrentFrame = 0;
- }
-
- return System.nanoTime();
- }
- return 0;
- }
-
- private void endBuildDisplayListProfiling(long getDisplayListStartTime) {
- if (mProfileEnabled) {
- long now = System.nanoTime();
- float total = (now - getDisplayListStartTime) * 0.000001f;
- //noinspection PointlessArithmeticExpression
- mProfileData[mProfileCurrentFrame] = total;
- }
- }
-
- private int prepareFrame(Rect dirty) {
- int status;
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareFrame");
- try {
- status = onPreDraw(dirty);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
- return status;
- }
-
- private int drawDisplayList(View.AttachInfo attachInfo, HardwareCanvas canvas,
- DisplayList displayList, int status) {
-
- long drawDisplayListStartTime = 0;
- if (mProfileEnabled) {
- drawDisplayListStartTime = System.nanoTime();
- }
-
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList");
- try {
- status |= canvas.drawDisplayList(displayList, mRedrawClip,
- DisplayList.FLAG_CLIP_CHILDREN);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
-
- if (mProfileEnabled) {
- long now = System.nanoTime();
- float total = (now - drawDisplayListStartTime) * 0.000001f;
- mProfileData[mProfileCurrentFrame + 1] = total;
- }
-
- handleFunctorStatus(attachInfo, status);
- return status;
- }
-
- private void swapBuffers(int status) {
- if ((status & DisplayList.STATUS_DREW) == DisplayList.STATUS_DREW) {
- long eglSwapBuffersStartTime = 0;
- if (mProfileEnabled) {
- eglSwapBuffersStartTime = System.nanoTime();
- }
-
- sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
-
- if (mProfileEnabled) {
- long now = System.nanoTime();
- float total = (now - eglSwapBuffersStartTime) * 0.000001f;
- mProfileData[mProfileCurrentFrame + 2] = total;
- }
-
- checkEglErrors();
- }
- }
-
- private void debugDirtyRegions(Rect dirty, HardwareCanvas canvas) {
- if (mDebugDirtyRegions) {
- if (mDebugPaint == null) {
- mDebugPaint = new Paint();
- mDebugPaint.setColor(0x7fff0000);
- }
-
- if (dirty != null && (mFrameCount & 1) == 0) {
- canvas.drawRect(dirty, mDebugPaint);
- }
- }
- }
-
- private void handleFunctorStatus(View.AttachInfo attachInfo, int status) {
- // If the draw flag is set, functors will be invoked while executing
- // the tree of display lists
- if ((status & DisplayList.STATUS_DRAW) != 0) {
- if (mRedrawClip.isEmpty()) {
- attachInfo.mViewRootImpl.invalidate();
- } else {
- attachInfo.mViewRootImpl.invalidateChildInParent(null, mRedrawClip);
- mRedrawClip.setEmpty();
- }
- }
-
- if ((status & DisplayList.STATUS_INVOKE) != 0 ||
- attachInfo.mHandler.hasCallbacks(mFunctorsRunnable)) {
- attachInfo.mHandler.removeCallbacks(mFunctorsRunnable);
- mFunctorsRunnable.attachInfo = attachInfo;
- attachInfo.mHandler.postDelayed(mFunctorsRunnable, FUNCTOR_PROCESS_DELAY);
- }
- }
-
- @Override
- void detachFunctor(int functor) {
- if (mCanvas != null) {
- mCanvas.detachFunctor(functor);
- }
- }
-
- @Override
- boolean attachFunctor(View.AttachInfo attachInfo, int functor) {
- if (mCanvas != null) {
- mCanvas.attachFunctor(functor);
- mFunctorsRunnable.attachInfo = attachInfo;
- attachInfo.mHandler.removeCallbacks(mFunctorsRunnable);
- attachInfo.mHandler.postDelayed(mFunctorsRunnable, 0);
- return true;
- }
- return false;
- }
-
- /**
- * Ensures the current EGL context and surface are the ones we expect.
- * This method throws an IllegalStateException if invoked from a thread
- * that did not initialize EGL.
- *
- * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current,
- * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or
- * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one
- *
- * @see #checkRenderContextUnsafe()
- */
- int checkRenderContext() {
- if (mEglThread != Thread.currentThread()) {
- throw new IllegalStateException("Hardware acceleration can only be used with a " +
- "single UI thread.\nOriginal thread: " + mEglThread + "\n" +
- "Current thread: " + Thread.currentThread());
- }
-
- return checkRenderContextUnsafe();
- }
-
- /**
- * Ensures the current EGL context and surface are the ones we expect.
- * This method does not check the current thread.
- *
- * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current,
- * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or
- * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one
- *
- * @see #checkRenderContext()
- */
- private int checkRenderContextUnsafe() {
- if (!mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW)) ||
- !mEglContext.equals(sEgl.eglGetCurrentContext())) {
- if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
- Log.e(LOG_TAG, "eglMakeCurrent failed " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- fallback(true);
- return SURFACE_STATE_ERROR;
- } else {
- if (mUpdateDirtyRegions) {
- enableDirtyRegions();
- mUpdateDirtyRegions = false;
- }
- return SURFACE_STATE_UPDATED;
- }
- }
- return SURFACE_STATE_SUCCESS;
- }
-
- private static int dpToPx(int dp, float density) {
- return (int) (dp * density + 0.5f);
- }
-
- class DrawPerformanceDataProvider extends GraphDataProvider {
- private final int mGraphType;
-
- private int mVerticalUnit;
- private int mHorizontalUnit;
- private int mHorizontalMargin;
- private int mThresholdStroke;
-
- DrawPerformanceDataProvider(int graphType) {
- mGraphType = graphType;
- }
-
- @Override
- void prepare(DisplayMetrics metrics) {
- final float density = metrics.density;
-
- mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density);
- mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density);
- mHorizontalMargin = dpToPx(PROFILE_DRAW_MARGIN, density);
- mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density);
- }
-
- @Override
- int getGraphType() {
- return mGraphType;
- }
-
- @Override
- int getVerticalUnitSize() {
- return mVerticalUnit;
- }
-
- @Override
- int getHorizontalUnitSize() {
- return mHorizontalUnit;
- }
-
- @Override
- int getHorizontaUnitMargin() {
- return mHorizontalMargin;
- }
-
- @Override
- float[] getData() {
- return mProfileData;
- }
-
- @Override
- float getThreshold() {
- return 16;
- }
-
- @Override
- int getFrameCount() {
- return mProfileData.length / PROFILE_FRAME_DATA_COUNT;
- }
-
- @Override
- int getElementCount() {
- return PROFILE_FRAME_DATA_COUNT;
- }
-
- @Override
- int getCurrentFrame() {
- return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT;
- }
-
- @Override
- void setupGraphPaint(Paint paint, int elementIndex) {
- paint.setColor(PROFILE_DRAW_COLORS[elementIndex]);
- if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke);
- }
-
- @Override
- void setupThresholdPaint(Paint paint) {
- paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR);
- paint.setStrokeWidth(mThresholdStroke);
- }
-
- @Override
- void setupCurrentFramePaint(Paint paint) {
- paint.setColor(PROFILE_DRAW_CURRENT_FRAME_COLOR);
- if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke);
- }
- }
- }
-
- /**
- * Hardware renderer using OpenGL ES 2.0.
- */
- static class Gl20Renderer extends GlRenderer {
- private GLES20Canvas mGlCanvas;
-
- private DisplayMetrics mDisplayMetrics;
-
- private static EGLSurface sPbuffer;
- private static final Object[] sPbufferLock = new Object[0];
-
- static class Gl20RendererEglContext extends ManagedEGLContext {
- final Handler mHandler = new Handler();
-
- public Gl20RendererEglContext(EGLContext context) {
- super(context);
- }
-
- @Override
- public void onTerminate(final EGLContext eglContext) {
- // Make sure we do this on the correct thread.
- if (mHandler.getLooper() != Looper.myLooper()) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- onTerminate(eglContext);
- }
- });
- return;
- }
-
- synchronized (sEglLock) {
- if (sEgl == null) return;
-
- if (EGLImpl.getInitCount(sEglDisplay) == 1) {
- usePbufferSurface(eglContext);
- GLES20Canvas.terminateCaches();
-
- sEgl.eglDestroyContext(sEglDisplay, eglContext);
- sEglContextStorage.set(null);
- sEglContextStorage.remove();
-
- sEgl.eglDestroySurface(sEglDisplay, sPbuffer);
- sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
- EGL_NO_SURFACE, EGL_NO_CONTEXT);
-
- sEgl.eglReleaseThread();
- sEgl.eglTerminate(sEglDisplay);
-
- sEgl = null;
- sEglDisplay = null;
- sEglConfig = null;
- sPbuffer = null;
- }
- }
- }
- }
-
- Gl20Renderer(boolean translucent) {
- super(2, translucent);
- }
-
- @Override
- HardwareCanvas createCanvas() {
- return mGlCanvas = new GLES20Canvas(mTranslucent);
- }
-
- @Override
- ManagedEGLContext createManagedContext(EGLContext eglContext) {
- return new Gl20Renderer.Gl20RendererEglContext(mEglContext);
- }
-
- @Override
- int[] getConfig(boolean dirtyRegions) {
- //noinspection PointlessBooleanExpression,ConstantConditions
- final int stencilSize = GLES20Canvas.getStencilSize();
- final int swapBehavior = dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
-
- return new int[] {
- EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
- EGL_RED_SIZE, 8,
- EGL_GREEN_SIZE, 8,
- EGL_BLUE_SIZE, 8,
- EGL_ALPHA_SIZE, 8,
- EGL_DEPTH_SIZE, 0,
- EGL_CONFIG_CAVEAT, EGL_NONE,
- EGL_STENCIL_SIZE, stencilSize,
- EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior,
- EGL_NONE
- };
- }
-
- @Override
- void initCaches() {
- if (GLES20Canvas.initCaches()) {
- // Caches were (re)initialized, rebind atlas
- initAtlas();
- }
- }
-
- @Override
- void initAtlas() {
- IBinder binder = ServiceManager.getService("assetatlas");
- if (binder == null) return;
-
- IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder);
- try {
- if (atlas.isCompatible(android.os.Process.myPpid())) {
- GraphicBuffer buffer = atlas.getBuffer();
- if (buffer != null) {
- int[] map = atlas.getMap();
- if (map != null) {
- GLES20Canvas.initAtlas(buffer, map);
- }
- // If IAssetAtlas is not the same class as the IBinder
- // we are using a remote service and we can safely
- // destroy the graphic buffer
- if (atlas.getClass() != binder.getClass()) {
- buffer.destroy();
- }
- }
- }
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "Could not acquire atlas", e);
- }
- }
-
- @Override
- boolean canDraw() {
- return super.canDraw() && mGlCanvas != null;
- }
-
- @Override
- int onPreDraw(Rect dirty) {
- return mGlCanvas.onPreDraw(dirty);
- }
-
- @Override
- void onPostDraw() {
- mGlCanvas.onPostDraw();
- }
-
- @Override
- void drawProfileData(View.AttachInfo attachInfo) {
- if (mDebugDataProvider != null) {
- final GraphDataProvider provider = mDebugDataProvider;
- initProfileDrawData(attachInfo, provider);
-
- final int height = provider.getVerticalUnitSize();
- final int margin = provider.getHorizontaUnitMargin();
- final int width = provider.getHorizontalUnitSize();
-
- int x = 0;
- int count = 0;
- int current = 0;
-
- final float[] data = provider.getData();
- final int elementCount = provider.getElementCount();
- final int graphType = provider.getGraphType();
-
- int totalCount = provider.getFrameCount() * elementCount;
- if (graphType == GraphDataProvider.GRAPH_TYPE_LINES) {
- totalCount -= elementCount;
- }
-
- for (int i = 0; i < totalCount; i += elementCount) {
- if (data[i] < 0.0f) break;
-
- int index = count * 4;
- if (i == provider.getCurrentFrame() * elementCount) current = index;
-
- x += margin;
- int x2 = x + width;
-
- int y2 = mHeight;
- int y1 = (int) (y2 - data[i] * height);
-
- switch (graphType) {
- case GraphDataProvider.GRAPH_TYPE_BARS: {
- for (int j = 0; j < elementCount; j++) {
- //noinspection MismatchedReadAndWriteOfArray
- final float[] r = mProfileShapes[j];
- r[index] = x;
- r[index + 1] = y1;
- r[index + 2] = x2;
- r[index + 3] = y2;
-
- y2 = y1;
- if (j < elementCount - 1) {
- y1 = (int) (y2 - data[i + j + 1] * height);
- }
- }
- } break;
- case GraphDataProvider.GRAPH_TYPE_LINES: {
- for (int j = 0; j < elementCount; j++) {
- //noinspection MismatchedReadAndWriteOfArray
- final float[] r = mProfileShapes[j];
- r[index] = (x + x2) * 0.5f;
- r[index + 1] = index == 0 ? y1 : r[index - 1];
- r[index + 2] = r[index] + width;
- r[index + 3] = y1;
-
- y2 = y1;
- if (j < elementCount - 1) {
- y1 = (int) (y2 - data[i + j + 1] * height);
- }
- }
- } break;
- }
-
-
- x += width;
- count++;
- }
-
- x += margin;
-
- drawGraph(graphType, count);
- drawCurrentFrame(graphType, current);
- drawThreshold(x, height);
- }
- }
-
- private void drawGraph(int graphType, int count) {
- for (int i = 0; i < mProfileShapes.length; i++) {
- mDebugDataProvider.setupGraphPaint(mProfilePaint, i);
- switch (graphType) {
- case GraphDataProvider.GRAPH_TYPE_BARS:
- mGlCanvas.drawRects(mProfileShapes[i], count * 4, mProfilePaint);
- break;
- case GraphDataProvider.GRAPH_TYPE_LINES:
- mGlCanvas.drawLines(mProfileShapes[i], 0, count * 4, mProfilePaint);
- break;
- }
- }
- }
-
- private void drawCurrentFrame(int graphType, int index) {
- if (index >= 0) {
- mDebugDataProvider.setupCurrentFramePaint(mProfilePaint);
- switch (graphType) {
- case GraphDataProvider.GRAPH_TYPE_BARS:
- mGlCanvas.drawRect(mProfileShapes[2][index], mProfileShapes[2][index + 1],
- mProfileShapes[2][index + 2], mProfileShapes[0][index + 3],
- mProfilePaint);
- break;
- case GraphDataProvider.GRAPH_TYPE_LINES:
- mGlCanvas.drawLine(mProfileShapes[2][index], mProfileShapes[2][index + 1],
- mProfileShapes[2][index], mHeight, mProfilePaint);
- break;
- }
- }
- }
-
- private void drawThreshold(int x, int height) {
- float threshold = mDebugDataProvider.getThreshold();
- if (threshold > 0.0f) {
- mDebugDataProvider.setupThresholdPaint(mProfilePaint);
- int y = (int) (mHeight - threshold * height);
- mGlCanvas.drawLine(0.0f, y, x, y, mProfilePaint);
- }
- }
-
- private void initProfileDrawData(View.AttachInfo attachInfo, GraphDataProvider provider) {
- if (mProfileShapes == null) {
- final int elementCount = provider.getElementCount();
- final int frameCount = provider.getFrameCount();
-
- mProfileShapes = new float[elementCount][];
- for (int i = 0; i < elementCount; i++) {
- mProfileShapes[i] = new float[frameCount * 4];
- }
-
- mProfilePaint = new Paint();
- }
-
- mProfilePaint.reset();
- if (provider.getGraphType() == GraphDataProvider.GRAPH_TYPE_LINES) {
- mProfilePaint.setAntiAlias(true);
- }
-
- if (mDisplayMetrics == null) {
- mDisplayMetrics = new DisplayMetrics();
- }
-
- attachInfo.mDisplay.getMetrics(mDisplayMetrics);
- provider.prepare(mDisplayMetrics);
- }
-
- @Override
- void destroy(boolean full) {
- try {
- super.destroy(full);
- } finally {
- if (full && mGlCanvas != null) {
- mGlCanvas = null;
- }
- }
- }
-
- @Override
- void pushLayerUpdate(HardwareLayer layer) {
- mGlCanvas.pushLayerUpdate(layer);
- }
-
- @Override
- void cancelLayerUpdate(HardwareLayer layer) {
- mGlCanvas.cancelLayerUpdate(layer);
- }
-
- @Override
- void flushLayerUpdates() {
- mGlCanvas.flushLayerUpdates();
- }
-
- @Override
- public DisplayList createDisplayList(String name) {
- return new GLES20DisplayList(name);
- }
-
- @Override
- HardwareLayer createHardwareLayer(boolean isOpaque) {
- return new GLES20TextureLayer(isOpaque);
- }
-
- @Override
- public HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
- return new GLES20RenderLayer(width, height, isOpaque);
- }
-
- @Override
- void countOverdraw(HardwareCanvas canvas) {
- ((GLES20Canvas) canvas).setCountOverdrawEnabled(true);
- }
-
- @Override
- float getOverdraw(HardwareCanvas canvas) {
- return ((GLES20Canvas) canvas).getOverdraw();
- }
-
- @Override
- public SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
- return ((GLES20TextureLayer) layer).getSurfaceTexture();
- }
-
- @Override
- void setSurfaceTexture(HardwareLayer layer, SurfaceTexture surfaceTexture) {
- ((GLES20TextureLayer) layer).setSurfaceTexture(surfaceTexture);
- }
-
- @Override
- boolean safelyRun(Runnable action) {
- boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR;
-
- if (needsContext) {
- Gl20RendererEglContext managedContext =
- (Gl20RendererEglContext) sEglContextStorage.get();
- if (managedContext == null) return false;
- usePbufferSurface(managedContext.getContext());
- }
-
- try {
- action.run();
- } finally {
- if (needsContext) {
- sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
- EGL_NO_SURFACE, EGL_NO_CONTEXT);
- }
- }
-
- return true;
- }
-
- @Override
- void destroyLayers(final View view) {
- if (view != null) {
- safelyRun(new Runnable() {
- @Override
- public void run() {
- if (mCanvas != null) {
- mCanvas.clearLayerUpdates();
- }
- destroyHardwareLayer(view);
- GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
- }
- });
- }
- }
-
- private static void destroyHardwareLayer(View view) {
- view.destroyLayer(true);
-
- if (view instanceof ViewGroup) {
- ViewGroup group = (ViewGroup) view;
-
- int count = group.getChildCount();
- for (int i = 0; i < count; i++) {
- destroyHardwareLayer(group.getChildAt(i));
- }
- }
- }
-
- @Override
- void destroyHardwareResources(final View view) {
- if (view != null) {
- safelyRun(new Runnable() {
- @Override
- public void run() {
- if (mCanvas != null) {
- mCanvas.clearLayerUpdates();
- }
- destroyResources(view);
- GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
- }
- });
- }
- }
-
- private static void destroyResources(View view) {
- view.destroyHardwareResources();
-
- if (view instanceof ViewGroup) {
- ViewGroup group = (ViewGroup) view;
-
- int count = group.getChildCount();
- for (int i = 0; i < count; i++) {
- destroyResources(group.getChildAt(i));
- }
- }
- }
-
- static HardwareRenderer create(boolean translucent) {
- if (GLES20Canvas.isAvailable()) {
- return new Gl20Renderer(translucent);
- }
- return null;
- }
-
- static void startTrimMemory(int level) {
- if (sEgl == null || sEglConfig == null) return;
-
- Gl20RendererEglContext managedContext =
- (Gl20RendererEglContext) sEglContextStorage.get();
- // We do not have OpenGL objects
- if (managedContext == null) {
- return;
- } else {
- usePbufferSurface(managedContext.getContext());
- }
-
- if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
- GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL);
- } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
- GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE);
- }
- }
-
- static void endTrimMemory() {
- if (sEgl != null && sEglDisplay != null) {
- sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- }
- }
-
- private static void usePbufferSurface(EGLContext eglContext) {
- synchronized (sPbufferLock) {
- // Create a temporary 1x1 pbuffer so we have a context
- // to clear our OpenGL objects
- if (sPbuffer == null) {
- sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] {
- EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE
- });
- }
- }
- sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext);
- }
- }
}
diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java
index e3de89d..b552c20 100644
--- a/core/java/android/view/InputQueue.java
+++ b/core/java/android/view/InputQueue.java
@@ -18,7 +18,6 @@ package android.view;
import dalvik.system.CloseGuard;
-import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Pools.Pool;
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 5a5fc10..30b1e52 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -20,7 +20,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.method.MetaKeyKeyListener;
import android.util.Log;
-import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.KeyCharacterMap;
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 1bfda2d..a2775d4 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.IntDef;
import android.content.res.CompatibilityInfo.Translator;
import android.graphics.Canvas;
import android.graphics.Matrix;
@@ -24,6 +25,10 @@ import android.graphics.SurfaceTexture;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
import dalvik.system.CloseGuard;
/**
@@ -80,6 +85,11 @@ public class Surface implements Parcelable {
// non compatibility mode.
private Matrix mCompatibleMatrix;
+ /** @hide */
+ @IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Rotation {}
+
/**
* Rotation constant: 0 degree rotation (natural orientation)
*/
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b22d5cf..914a5ca 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -22,7 +22,6 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.view.Surface;
import android.os.IBinder;
-import android.os.SystemProperties;
import android.util.Log;
import android.view.Surface.OutOfResourcesException;
@@ -79,9 +78,6 @@ public class SurfaceControl {
private final String mName;
int mNativeObject; // package visibility only for Surface.java access
- private static final boolean HEADLESS = "1".equals(
- SystemProperties.get("ro.config.headless", "0"));
-
/* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */
/**
@@ -232,8 +228,6 @@ public class SurfaceControl {
new Throwable());
}
- checkHeadless();
-
mName = name;
mNativeObject = nativeCreate(session, name, w, h, format, flags);
if (mNativeObject == 0) {
@@ -619,10 +613,4 @@ public class SurfaceControl {
}
nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers);
}
-
- private static void checkHeadless() {
- if (HEADLESS) {
- throw new UnsupportedOperationException("Device is headless");
- }
- }
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 22d4c9b..65d3f6d 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -188,8 +188,13 @@ public class SurfaceView extends View {
init();
}
- public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
init();
}
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 47f7628..bf81811 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -156,14 +156,32 @@ public class TextureView extends View {
*
* @param context The context to associate this view with.
* @param attrs The attributes of the XML tag that is inflating the view.
- * @param defStyle The default style to apply to this view. If 0, no style
- * will be applied (beyond what is included in the theme). This may
- * either be an attribute resource, whose value will be retrieved
- * from the current theme, or an explicit style resource.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
*/
@SuppressWarnings({"UnusedDeclaration"})
- public TextureView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public TextureView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ /**
+ * Creates a new TextureView.
+ *
+ * @param context The context to associate this view with.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ public TextureView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
init();
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b0bae46..2734abc 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,9 @@
package android.view;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ClipData;
import android.content.Context;
import android.content.res.Configuration;
@@ -86,6 +89,8 @@ import com.android.internal.view.menu.MenuBuilder;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -729,6 +734,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
private static final int FITS_SYSTEM_WINDOWS = 0x00000002;
+ /** @hide */
+ @IntDef({VISIBLE, INVISIBLE, GONE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Visibility {}
+
/**
* This view is visible.
* Use with {@link #setVisibility} and <a href="#attr_android:visibility">{@code
@@ -896,6 +906,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
static final int FOCUSABLE_IN_TOUCH_MODE = 0x00040000;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({DRAWING_CACHE_QUALITY_LOW, DRAWING_CACHE_QUALITY_HIGH, DRAWING_CACHE_QUALITY_AUTO})
+ public @interface DrawingCacheQuality {}
+
/**
* <p>Enables low quality mode for the drawing cache.</p>
*/
@@ -940,6 +955,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
static final int DUPLICATE_PARENT_STATE = 0x00400000;
+ /** @hide */
+ @IntDef({
+ SCROLLBARS_INSIDE_OVERLAY,
+ SCROLLBARS_INSIDE_INSET,
+ SCROLLBARS_OUTSIDE_OVERLAY,
+ SCROLLBARS_OUTSIDE_INSET
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScrollBarStyle {}
+
/**
* The scrollbar style to display the scrollbars inside the content area,
* without increasing the padding. The scrollbars will be overlaid with
@@ -1020,6 +1045,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
static final int PARENT_SAVE_DISABLED_MASK = 0x20000000;
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ FOCUSABLES_ALL,
+ FOCUSABLES_TOUCH_MODE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusableMode {}
+
/**
* View flag indicating whether {@link #addFocusables(ArrayList, int, int)}
* should add all focusable Views regardless if they are focusable in touch mode.
@@ -1032,6 +1066,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public static final int FOCUSABLES_TOUCH_MODE = 0x00000001;
+ /** @hide */
+ @IntDef({
+ FOCUS_BACKWARD,
+ FOCUS_FORWARD,
+ FOCUS_LEFT,
+ FOCUS_UP,
+ FOCUS_RIGHT,
+ FOCUS_DOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusDirection {}
+
+ /** @hide */
+ @IntDef({
+ FOCUS_LEFT,
+ FOCUS_UP,
+ FOCUS_RIGHT,
+ FOCUS_DOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusRealDirection {} // Like @FocusDirection, but without forward/backward
+
/**
* Use with {@link #focusSearch(int)}. Move focus to the previous selectable
* item.
@@ -1804,6 +1860,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
static final int PFLAG2_DRAG_HOVERED = 0x00000002;
+ /** @hide */
+ @IntDef({
+ LAYOUT_DIRECTION_LTR,
+ LAYOUT_DIRECTION_RTL,
+ LAYOUT_DIRECTION_INHERIT,
+ LAYOUT_DIRECTION_LOCALE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ // Not called LayoutDirection to avoid conflict with android.util.LayoutDirection
+ public @interface LayoutDir {}
+
+ /** @hide */
+ @IntDef({
+ LAYOUT_DIRECTION_LTR,
+ LAYOUT_DIRECTION_RTL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResolvedLayoutDir {}
+
/**
* Horizontal layout direction of this view is from Left to Right.
* Use with {@link #setLayoutDirection}.
@@ -1982,7 +2057,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
static final int PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT =
TEXT_DIRECTION_RESOLVED_DEFAULT << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
- /*
+ /** @hide */
+ @IntDef({
+ TEXT_ALIGNMENT_INHERIT,
+ TEXT_ALIGNMENT_GRAVITY,
+ TEXT_ALIGNMENT_CENTER,
+ TEXT_ALIGNMENT_TEXT_START,
+ TEXT_ALIGNMENT_TEXT_END,
+ TEXT_ALIGNMENT_VIEW_START,
+ TEXT_ALIGNMENT_VIEW_END
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TextAlignment {}
+
+ /**
* Default text alignment. The text alignment of this View is inherited from its parent.
* Use with {@link #setTextAlignment(int)}
*/
@@ -2664,6 +2752,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ /** @hide */
+ @IntDef(flag = true,
+ value = { FIND_VIEWS_WITH_TEXT, FIND_VIEWS_WITH_CONTENT_DESCRIPTION })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FindViewFlags {}
+
/**
* Find views that render the specified text.
*
@@ -2685,7 +2779,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* added and it is a responsibility of the client to call the APIs of
* the provider to determine whether the virtual tree rooted at this View
* contains the text, i.e. getting the list of {@link AccessibilityNodeInfo}s
- * represeting the virtual views with this text.
+ * representing the virtual views with this text.
*
* @see #findViewsWithText(ArrayList, CharSequence, int)
*
@@ -3485,27 +3579,64 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Perform inflation from XML and apply a class-specific base style. This
- * constructor of View allows subclasses to use their own base style when
- * they are inflating. For example, a Button class's constructor would call
- * this version of the super class constructor and supply
- * <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.
+ * Perform inflation from XML and apply a class-specific base style from a
+ * theme attribute. This constructor of View allows subclasses to use their
+ * own base style when they are inflating. For example, a Button class's
+ * constructor would call this version of the super class constructor and
+ * supply <code>R.attr.buttonStyle</code> for <var>defStyleAttr</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.
* @param defStyleAttr An attribute in the current theme that contains a
- * reference to a style resource to apply to this view. If 0, no
- * default style will be applied.
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
* @see #View(Context, AttributeSet)
*/
public View(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ /**
+ * Perform inflation from XML and apply a class-specific base style from a
+ * theme attribute or style resource. This constructor of View allows
+ * subclasses to use their own base style when they are inflating.
+ * <p>
+ * When determining the final value of a particular attribute, there are
+ * four inputs that come into play:
+ * <ol>
+ * <li>Any attribute values in the given AttributeSet.
+ * <li>The style resource specified in the AttributeSet (named "style").
+ * <li>The default style specified by <var>defStyleAttr</var>.
+ * <li>The default style specified by <var>defStyleRes</var>.
+ * <li>The base values in this theme.
+ * </ol>
+ * <p>
+ * Each of these inputs is considered in-order, with the first listed taking
+ * precedence over the following ones. In other words, if in the
+ * AttributeSet you have supplied <code>&lt;Button * textColor="#ff000000"&gt;</code>
+ * , then the button's text will <em>always</em> be black, regardless of
+ * what is specified in any of the styles.
+ *
+ * @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.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ * @see #View(Context, AttributeSet, int)
+ */
+ public View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
this(context);
- TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View,
- defStyleAttr, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
Drawable background = null;
@@ -4596,7 +4727,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param previouslyFocusedRect The rectangle of the view that had focus
* prior in this View's coordinate system.
*/
- void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) {
+ void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " requestFocus()");
}
@@ -4807,7 +4938,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* passed in as finer grained information about where the focus is coming
* from (in addition to direction). Will be <code>null</code> otherwise.
*/
- protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+ protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction,
+ @Nullable Rect previouslyFocusedRect) {
if (gainFocus) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
} else {
@@ -5667,6 +5799,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @attr ref android.R.styleable#View_drawingCacheQuality
*/
+ @DrawingCacheQuality
public int getDrawingCacheQuality() {
return mViewFlags & DRAWING_CACHE_QUALITY_MASK;
}
@@ -5684,7 +5817,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @attr ref android.R.styleable#View_drawingCacheQuality
*/
- public void setDrawingCacheQuality(int quality) {
+ public void setDrawingCacheQuality(@DrawingCacheQuality int quality) {
setFlags(quality, DRAWING_CACHE_QUALITY_MASK);
}
@@ -6021,6 +6154,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@ViewDebug.IntToString(from = INVISIBLE, to = "INVISIBLE"),
@ViewDebug.IntToString(from = GONE, to = "GONE")
})
+ @Visibility
public int getVisibility() {
return mViewFlags & VISIBILITY_MASK;
}
@@ -6032,7 +6166,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @attr ref android.R.styleable#View_visibility
*/
@RemotableViewMethod
- public void setVisibility(int visibility) {
+ public void setVisibility(@Visibility int visibility) {
setFlags(visibility, VISIBILITY_MASK);
if (mBackground != null) mBackground.setVisible(visibility == VISIBLE, false);
}
@@ -6191,6 +6325,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@ViewDebug.IntToString(from = LAYOUT_DIRECTION_INHERIT, to = "INHERIT"),
@ViewDebug.IntToString(from = LAYOUT_DIRECTION_LOCALE, to = "LOCALE")
})
+ @LayoutDir
public int getRawLayoutDirection() {
return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_MASK) >> PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
}
@@ -6213,7 +6348,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @attr ref android.R.styleable#View_layoutDirection
*/
@RemotableViewMethod
- public void setLayoutDirection(int layoutDirection) {
+ public void setLayoutDirection(@LayoutDir int layoutDirection) {
if (getRawLayoutDirection() != layoutDirection) {
// Reset the current layout direction and the resolved one
mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_MASK;
@@ -6243,6 +6378,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR, to = "RESOLVED_DIRECTION_LTR"),
@ViewDebug.IntToString(from = LAYOUT_DIRECTION_RTL, to = "RESOLVED_DIRECTION_RTL")
})
+ @ResolvedLayoutDir
public int getLayoutDirection() {
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
if (targetSdkVersion < JELLY_BEAN_MR1) {
@@ -6612,7 +6748,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return The nearest focusable in the specified direction, or null if none
* can be found.
*/
- public View focusSearch(int direction) {
+ public View focusSearch(@FocusRealDirection int direction) {
if (mParent != null) {
return mParent.focusSearch(this, direction);
} else {
@@ -6631,7 +6767,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT.
* @return True if the this view consumed this unhandled move.
*/
- public boolean dispatchUnhandledMove(View focused, int direction) {
+ public boolean dispatchUnhandledMove(View focused, @FocusRealDirection int direction) {
return false;
}
@@ -6643,7 +6779,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* or FOCUS_BACKWARD.
* @return The user specified next view, or null if there is none.
*/
- View findUserSetNextFocus(View root, int direction) {
+ View findUserSetNextFocus(View root, @FocusDirection int direction) {
switch (direction) {
case FOCUS_LEFT:
if (mNextFocusLeftId == View.NO_ID) return null;
@@ -6693,7 +6829,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param direction The direction of the focus
* @return A list of focusable views
*/
- public ArrayList<View> getFocusables(int direction) {
+ public ArrayList<View> getFocusables(@FocusDirection int direction) {
ArrayList<View> result = new ArrayList<View>(24);
addFocusables(result, direction);
return result;
@@ -6707,7 +6843,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param views Focusable views found so far
* @param direction The direction of the focus
*/
- public void addFocusables(ArrayList<View> views, int direction) {
+ public void addFocusables(ArrayList<View> views, @FocusDirection int direction) {
addFocusables(views, direction, FOCUSABLES_TOUCH_MODE);
}
@@ -6727,7 +6863,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #FOCUSABLES_ALL
* @see #FOCUSABLES_TOUCH_MODE
*/
- public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ public void addFocusables(ArrayList<View> views, @FocusDirection int direction,
+ @FocusableMode int focusableMode) {
if (views == null) {
return;
}
@@ -6756,7 +6893,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #FIND_VIEWS_WITH_CONTENT_DESCRIPTION
* @see #setContentDescription(CharSequence)
*/
- public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
+ public void findViewsWithText(ArrayList<View> outViews, CharSequence searched,
+ @FindViewFlags int flags) {
if (getAccessibilityNodeProvider() != null) {
if ((flags & FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS) != 0) {
outViews.add(this);
@@ -7247,7 +7385,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Returns whether the View has registered callbacks wich makes it
+ * Returns whether the View has registered callbacks which makes it
* important for accessibility.
*
* @return True if the view is actionable for accessibility.
@@ -7266,7 +7404,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* notification is at at most once every
* {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
* to avoid unnecessary load to the system. Also once a view has a pending
- * notifucation this method is a NOP until the notification has been sent.
+ * notification this method is a NOP until the notification has been sent.
*
* @hide
*/
@@ -7946,7 +8084,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param visibility The new visibility of changedView: {@link #VISIBLE},
* {@link #INVISIBLE} or {@link #GONE}.
*/
- protected void dispatchVisibilityChanged(View changedView, int visibility) {
+ protected void dispatchVisibilityChanged(@NonNull View changedView,
+ @Visibility int visibility) {
onVisibilityChanged(changedView, visibility);
}
@@ -7957,7 +8096,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param visibility The new visibility of changedView: {@link #VISIBLE},
* {@link #INVISIBLE} or {@link #GONE}.
*/
- protected void onVisibilityChanged(View changedView, int visibility) {
+ protected void onVisibilityChanged(@NonNull View changedView, @Visibility int visibility) {
if (visibility == VISIBLE) {
if (mAttachInfo != null) {
initialAwakenScrollBars();
@@ -7976,7 +8115,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param hint A hint about whether or not this view is displayed:
* {@link #VISIBLE} or {@link #INVISIBLE}.
*/
- public void dispatchDisplayHint(int hint) {
+ public void dispatchDisplayHint(@Visibility int hint) {
onDisplayHint(hint);
}
@@ -7989,7 +8128,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param hint A hint about whether or not this view is displayed:
* {@link #VISIBLE} or {@link #INVISIBLE}.
*/
- protected void onDisplayHint(int hint) {
+ protected void onDisplayHint(@Visibility int hint) {
}
/**
@@ -8000,7 +8139,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @see #onWindowVisibilityChanged(int)
*/
- public void dispatchWindowVisibilityChanged(int visibility) {
+ public void dispatchWindowVisibilityChanged(@Visibility int visibility) {
onWindowVisibilityChanged(visibility);
}
@@ -8014,7 +8153,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @param visibility The new visibility of the window.
*/
- protected void onWindowVisibilityChanged(int visibility) {
+ protected void onWindowVisibilityChanged(@Visibility int visibility) {
if (visibility == VISIBLE) {
initialAwakenScrollBars();
}
@@ -8026,6 +8165,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @return Returns the current visibility of the view's window.
*/
+ @Visibility
public int getWindowVisibility() {
return mAttachInfo != null ? mAttachInfo.mWindowVisibility : GONE;
}
@@ -10818,15 +10958,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPrivateFlags |= PFLAG_DIRTY;
final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
- //noinspection PointlessBooleanExpression,ConstantConditions
- if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
- if (p != null && ai != null && ai.mHardwareAccelerated) {
- // fast-track for GL-enabled applications; just invalidate the whole hierarchy
- // with a null dirty rect, which tells the ViewAncestor to redraw everything
- p.invalidateChild(this, null);
- return;
- }
- }
if (p != null && ai != null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
@@ -10861,15 +10992,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPrivateFlags |= PFLAG_DIRTY;
final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
- //noinspection PointlessBooleanExpression,ConstantConditions
- if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
- if (p != null && ai != null && ai.mHardwareAccelerated) {
- // fast-track for GL-enabled applications; just invalidate the whole hierarchy
- // with a null dirty rect, which tells the ViewAncestor to redraw everything
- p.invalidateChild(this, null);
- return;
- }
- }
if (p != null && ai != null && l < r && t < b) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
@@ -10917,15 +11039,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
- //noinspection PointlessBooleanExpression,ConstantConditions
- if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
- if (p != null && ai != null && ai.mHardwareAccelerated) {
- // fast-track for GL-enabled applications; just invalidate the whole hierarchy
- // with a null dirty rect, which tells the ViewAncestor to redraw everything
- p.invalidateChild(this, null);
- return;
- }
- }
if (p != null && ai != null) {
final Rect r = ai.mTmpInvalRect;
@@ -11212,10 +11325,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
attachInfo.mHandler.removeCallbacks(action);
attachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
Choreographer.CALLBACK_ANIMATION, action, null);
- } else {
- // Assume that post will succeed later
- ViewRootImpl.getRunQueue().removeCallbacks(action);
}
+ // Assume that post will succeed later
+ ViewRootImpl.getRunQueue().removeCallbacks(action);
}
return true;
}
@@ -11706,7 +11818,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @attr ref android.R.styleable#View_scrollbarStyle
*/
- public void setScrollBarStyle(int style) {
+ public void setScrollBarStyle(@ScrollBarStyle int style) {
if (style != (mViewFlags & SCROLLBARS_STYLE_MASK)) {
mViewFlags = (mViewFlags & ~SCROLLBARS_STYLE_MASK) | (style & SCROLLBARS_STYLE_MASK);
computeOpaqueFlags();
@@ -11730,6 +11842,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@ViewDebug.IntToString(from = SCROLLBARS_OUTSIDE_OVERLAY, to = "OUTSIDE_OVERLAY"),
@ViewDebug.IntToString(from = SCROLLBARS_OUTSIDE_INSET, to = "OUTSIDE_INSET")
})
+ @ScrollBarStyle
public int getScrollBarStyle() {
return mViewFlags & SCROLLBARS_STYLE_MASK;
}
@@ -12231,7 +12344,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #LAYOUT_DIRECTION_LTR
* @see #LAYOUT_DIRECTION_RTL
*/
- public void onRtlPropertiesChanged(int layoutDirection) {
+ public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) {
}
/**
@@ -13260,20 +13373,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * @return The {@link HardwareRenderer} associated with that view or null if
- * hardware rendering is not supported or this view is not attached
- * to a window.
- *
- * @hide
- */
- public HardwareRenderer getHardwareRenderer() {
- if (mAttachInfo != null) {
- return mAttachInfo.mHardwareRenderer;
- }
- return null;
- }
-
- /**
* Returns a DisplayList. If the incoming displayList is null, one will be created.
* Otherwise, the same display list will be returned (after having been rendered into
* along the way, depending on the invalidation state of the view).
@@ -13308,7 +13407,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mRecreateDisplayList = true;
}
if (displayList == null) {
- displayList = mAttachInfo.mHardwareRenderer.createDisplayList(getClass().getName());
+ displayList = DisplayList.create(getClass().getName());
// If we're creating a new display list, make sure our parent gets invalidated
// since they will need to recreate their display list to account for this
// new child display list.
@@ -15003,9 +15102,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
Choreographer.CALLBACK_ANIMATION, what, who);
- } else {
- ViewRootImpl.getRunQueue().removeCallbacks(what);
}
+ ViewRootImpl.getRunQueue().removeCallbacks(what);
}
}
@@ -15068,7 +15166,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @hide
*/
- public void onResolveDrawables(int layoutDirection) {
+ public void onResolveDrawables(@ResolvedLayoutDir int layoutDirection) {
}
/**
@@ -17870,6 +17968,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"),
@ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END")
})
+ @TextAlignment
public int getRawTextAlignment() {
return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_MASK) >> PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT;
}
@@ -17893,7 +17992,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @attr ref android.R.styleable#View_textAlignment
*/
- public void setTextAlignment(int textAlignment) {
+ public void setTextAlignment(@TextAlignment int textAlignment) {
if (textAlignment != getRawTextAlignment()) {
// Reset the current and resolved text alignment
mPrivateFlags2 &= ~PFLAG2_TEXT_ALIGNMENT_MASK;
@@ -17934,6 +18033,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"),
@ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END")
})
+ @TextAlignment
public int getTextAlignment() {
return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK) >>
PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
@@ -18746,7 +18846,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
View mRootView;
IBinder mPanelParentWindowToken;
- Surface mSurface;
boolean mHardwareAccelerated;
boolean mHardwareAccelerationRequested;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 9414237..a1b7ef6 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -38,7 +38,6 @@ import android.util.Log;
import android.util.Pools.SynchronizedPool;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -456,20 +455,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private int mChildCountWithTransientState = 0;
public ViewGroup(Context context) {
- super(context);
- initViewGroup();
+ this(context, null);
}
public ViewGroup(Context context, AttributeSet attrs) {
- super(context, attrs);
- initViewGroup();
- initFromAttributes(context, attrs);
+ this(context, attrs, 0);
+ }
+
+ public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
- public ViewGroup(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
initViewGroup();
- initFromAttributes(context, attrs);
+ initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
}
private boolean debugDraw() {
@@ -499,9 +499,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
}
- private void initFromAttributes(Context context, AttributeSet attrs) {
- TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.ViewGroup);
+ private void initFromAttributes(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewGroup);
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
@@ -2510,13 +2510,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
if (mAttachInfo != null) {
- ArrayList<View> childrenForAccessibility = mAttachInfo.mTempArrayList;
+ final ArrayList<View> childrenForAccessibility = mAttachInfo.mTempArrayList;
childrenForAccessibility.clear();
addChildrenForAccessibility(childrenForAccessibility);
final int childrenForAccessibilityCount = childrenForAccessibility.size();
for (int i = 0; i < childrenForAccessibilityCount; i++) {
- View child = childrenForAccessibility.get(i);
- info.addChild(child);
+ final View child = childrenForAccessibility.get(i);
+ info.addChildUnchecked(child);
}
childrenForAccessibility.clear();
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index bc0d7e3..a5f797e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -108,7 +108,6 @@ public final class ViewRootImpl implements ViewParent,
private static final boolean DEBUG_IMF = false || LOCAL_LOGV;
private static final boolean DEBUG_CONFIGURATION = false || LOCAL_LOGV;
private static final boolean DEBUG_FPS = false;
- private static final boolean DEBUG_INPUT_PROCESSING = false || LOCAL_LOGV;
/**
* Set this system property to true to force the view hierarchy to render
@@ -719,7 +718,7 @@ public final class ViewRootImpl implements ViewParent,
}
final boolean translucent = attrs.format != PixelFormat.OPAQUE;
- mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent);
+ mAttachInfo.mHardwareRenderer = HardwareRenderer.create(translucent);
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString());
mAttachInfo.mHardwareAccelerated =
@@ -1195,11 +1194,6 @@ public final class ViewRootImpl implements ViewParent,
desiredWindowHeight = packageMetrics.heightPixels;
}
- // For the very first time, tell the view hierarchy that it
- // 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.mSurface = mSurface;
// We used to use the following condition to choose 32 bits drawing caches:
// PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
// However, windows are now always 32 bits by default, so choose 32 bits
@@ -1548,7 +1542,7 @@ public final class ViewRootImpl implements ViewParent,
if (mAttachInfo.mHardwareRenderer != null) {
try {
hwInitialized = mAttachInfo.mHardwareRenderer.initialize(
- mHolder.getSurface());
+ mSurface);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
@@ -1575,7 +1569,7 @@ public final class ViewRootImpl implements ViewParent,
mSurfaceHolder == null && mAttachInfo.mHardwareRenderer != null) {
mFullRedrawNeeded = true;
try {
- mAttachInfo.mHardwareRenderer.updateSurface(mHolder.getSurface());
+ mAttachInfo.mHardwareRenderer.updateSurface(mSurface);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
@@ -1658,7 +1652,7 @@ public final class ViewRootImpl implements ViewParent,
mHeight != mAttachInfo.mHardwareRenderer.getHeight()) {
mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight);
if (!hwInitialized) {
- mAttachInfo.mHardwareRenderer.invalidate(mHolder.getSurface());
+ mAttachInfo.mHardwareRenderer.invalidate(mSurface);
mFullRedrawNeeded = true;
}
}
@@ -2395,7 +2389,7 @@ public final class ViewRootImpl implements ViewParent,
try {
attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
- mHolder.getSurface());
+ mSurface);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
@@ -2530,28 +2524,35 @@ public final class ViewRootImpl implements ViewParent,
* @param canvas The canvas on which to draw.
*/
private void drawAccessibilityFocusedDrawableIfNeeded(Canvas canvas) {
- AccessibilityManager manager = AccessibilityManager.getInstance(mView.mContext);
+ if (!mAttachInfo.mHasWindowFocus) {
+ return;
+ }
+
+ final AccessibilityManager manager = AccessibilityManager.getInstance(mView.mContext);
if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {
return;
}
- if (mAccessibilityFocusedHost == null || mAccessibilityFocusedHost.mAttachInfo == null) {
+
+ final View host = mAccessibilityFocusedHost;
+ if (host == null || host.mAttachInfo == null) {
return;
}
- Drawable drawable = getAccessibilityFocusedDrawable();
+
+ final Drawable drawable = getAccessibilityFocusedDrawable();
if (drawable == null) {
return;
}
- AccessibilityNodeProvider provider =
- mAccessibilityFocusedHost.getAccessibilityNodeProvider();
- Rect bounds = mView.mAttachInfo.mTmpInvalRect;
+
+ final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
+ final Rect bounds = mView.mAttachInfo.mTmpInvalRect;
if (provider == null) {
- mAccessibilityFocusedHost.getBoundsOnScreen(bounds);
- } else {
- if (mAccessibilityFocusedVirtualView == null) {
- return;
- }
+ host.getBoundsOnScreen(bounds);
+ } else if (mAccessibilityFocusedVirtualView != null) {
mAccessibilityFocusedVirtualView.getBoundsInScreen(bounds);
+ } else {
+ return;
}
+
bounds.offset(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop);
bounds.intersect(0, 0, mAttachInfo.mViewRootImpl.mWidth, mAttachInfo.mViewRootImpl.mHeight);
drawable.setBounds(bounds);
@@ -2850,7 +2851,6 @@ public final class ViewRootImpl implements ViewParent,
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
- mAttachInfo.mSurface = null;
mSurface.release();
@@ -3110,7 +3110,7 @@ public final class ViewRootImpl implements ViewParent,
mFullRedrawNeeded = true;
try {
mAttachInfo.mHardwareRenderer.initializeIfNeeded(
- mWidth, mHeight, mHolder.getSurface());
+ mWidth, mHeight, mSurface);
} catch (OutOfResourcesException e) {
Log.e(TAG, "OutOfResourcesException locking surface", e);
try {
@@ -3159,8 +3159,6 @@ public final class ViewRootImpl implements ViewParent,
mHasHadWindowFocus = true;
}
- setAccessibilityFocus(null, null);
-
if (mView != null && mAccessibilityManager.isEnabled()) {
if (hasWindowFocus) {
mView.sendAccessibilityEvent(
@@ -4506,8 +4504,7 @@ public final class ViewRootImpl implements ViewParent,
// The active pointer id, or -1 if none.
private int mActivePointerId = -1;
- // Time and location where tracking started.
- private long mStartTime;
+ // Location where tracking started.
private float mStartX;
private float mStartY;
@@ -4535,9 +4532,6 @@ public final class ViewRootImpl implements ViewParent,
private boolean mFlinging;
private float mFlingVelocity;
- // The last time a confirm key was pressed on the touch nav device
- private long mLastConfirmKeyTime = Long.MAX_VALUE;
-
public SyntheticTouchNavigationHandler() {
super(true);
}
@@ -4604,7 +4598,6 @@ public final class ViewRootImpl implements ViewParent,
mActivePointerId = event.getPointerId(0);
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(event);
- mStartTime = time;
mStartX = event.getX();
mStartY = event.getY();
mLastX = mStartX;
@@ -5388,7 +5381,7 @@ public final class ViewRootImpl implements ViewParent,
// Hardware rendering
if (mAttachInfo.mHardwareRenderer != null) {
- if (mAttachInfo.mHardwareRenderer.loadSystemProperties(mHolder.getSurface())) {
+ if (mAttachInfo.mHardwareRenderer.loadSystemProperties(mSurface)) {
invalidate();
}
}
@@ -6335,68 +6328,6 @@ public final class ViewRootImpl implements ViewParent,
}
}
- private final SurfaceHolder mHolder = new SurfaceHolder() {
- // we only need a SurfaceHolder for opengl. it would be nice
- // to implement everything else though, especially the callback
- // support (opengl doesn't make use of it right now, but eventually
- // will).
- @Override
- public Surface getSurface() {
- return mSurface;
- }
-
- @Override
- public boolean isCreating() {
- return false;
- }
-
- @Override
- public void addCallback(Callback callback) {
- }
-
- @Override
- public void removeCallback(Callback callback) {
- }
-
- @Override
- public void setFixedSize(int width, int height) {
- }
-
- @Override
- public void setSizeFromLayout() {
- }
-
- @Override
- public void setFormat(int format) {
- }
-
- @Override
- public void setType(int type) {
- }
-
- @Override
- public void setKeepScreenOn(boolean screenOn) {
- }
-
- @Override
- public Canvas lockCanvas() {
- return null;
- }
-
- @Override
- public Canvas lockCanvas(Rect dirty) {
- return null;
- }
-
- @Override
- public void unlockCanvasAndPost(Canvas canvas) {
- }
- @Override
- public Rect getSurfaceFrame() {
- return null;
- }
- };
-
static RunQueue getRunQueue() {
RunQueue rq = sRunQueues.get();
if (rq != null) {
diff --git a/core/java/android/view/ViewStub.java b/core/java/android/view/ViewStub.java
index a5dc3ae..d68a860 100644
--- a/core/java/android/view/ViewStub.java
+++ b/core/java/android/view/ViewStub.java
@@ -97,16 +97,21 @@ public final class ViewStub extends View {
}
@SuppressWarnings({"UnusedDeclaration"})
- public ViewStub(Context context, AttributeSet attrs, int defStyle) {
- TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewStub,
- defStyle, 0);
+ public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.ViewStub, defStyleAttr, defStyleRes);
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
a.recycle();
- a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, defStyle, 0);
+ a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
mID = a.getResourceId(R.styleable.View_id, NO_ID);
a.recycle();
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index b3a0699..11d8d36 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -16,6 +16,8 @@
package android.view;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -89,12 +91,21 @@ public abstract class Window {
* If overlay is enabled, the action mode UI will be allowed to cover existing window content.
*/
public static final int FEATURE_ACTION_MODE_OVERLAY = 10;
+ /**
+ * Flag for requesting that window content changes should be represented
+ * with scenes and transitions.
+ *
+ * TODO Add docs
+ *
+ * @see #setContentView
+ */
+ public static final int FEATURE_CONTENT_TRANSITIONS = 11;
/**
* Max value used as a feature ID
* @hide
*/
- public static final int FEATURE_MAX = FEATURE_ACTION_MODE_OVERLAY;
+ public static final int FEATURE_MAX = FEATURE_CONTENT_TRANSITIONS;
/** Flag for setting the progress bar's visibility to VISIBLE */
public static final int PROGRESS_VISIBILITY_ON = -1;
@@ -240,6 +251,7 @@ public abstract class Window {
*
* @see #onPreparePanel
*/
+ @Nullable
public View onCreatePanelView(int featureId);
/**
@@ -368,6 +380,7 @@ public abstract class Window {
* @param callback Callback to control the lifecycle of this action mode
* @return The ActionMode that was started, or null if the system should present it
*/
+ @Nullable
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback);
/**
@@ -969,6 +982,7 @@ public abstract class Window {
*
* @return View The current View with focus or null.
*/
+ @Nullable
public abstract View getCurrentFocus();
/**
@@ -977,10 +991,12 @@ public abstract class Window {
*
* @return LayoutInflater The shared LayoutInflater.
*/
+ @NonNull
public abstract LayoutInflater getLayoutInflater();
public abstract void setTitle(CharSequence title);
+ @Deprecated
public abstract void setTitleColor(int textColor);
public abstract void openPanel(int featureId, KeyEvent event);
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index c5a1b86..d9e140e 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -16,7 +16,9 @@
package android.view;
+import android.annotation.IntDef;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -27,6 +29,8 @@ import android.os.Looper;
import android.view.animation.Animation;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* This interface supplies all UI-specific behavior of the window manager. An
@@ -464,6 +468,11 @@ public interface WindowManagerPolicy {
/** Screen turned off because of proximity sensor */
public final int OFF_BECAUSE_OF_PROX_SENSOR = 4;
+ /** @hide */
+ @IntDef({USER_ROTATION_FREE, USER_ROTATION_LOCKED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserRotationMode {}
+
/** When not otherwise specified by the activity's screenOrientation, rotation should be
* determined by the system (that is, using sensors). */
public final int USER_ROTATION_FREE = 0;
@@ -1022,7 +1031,8 @@ public interface WindowManagerPolicy {
* @param lastRotation The most recently used rotation.
* @return The surface rotation to use.
*/
- public int rotationForOrientationLw(int orientation, int lastRotation);
+ public int rotationForOrientationLw(@ActivityInfo.ScreenOrientation int orientation,
+ int lastRotation);
/**
* Given an orientation constant and a rotation, returns true if the rotation
@@ -1037,7 +1047,8 @@ public interface WindowManagerPolicy {
* @param rotation The rotation to check.
* @return True if the rotation is compatible with the requested orientation.
*/
- public boolean rotationHasCompatibleMetricsLw(int orientation, int rotation);
+ public boolean rotationHasCompatibleMetricsLw(@ActivityInfo.ScreenOrientation int orientation,
+ int rotation);
/**
* Called by the window manager when the rotation changes.
@@ -1086,7 +1097,7 @@ public interface WindowManagerPolicy {
*/
public void enableScreenAfterBoot();
- public void setCurrentOrientationLw(int newOrientation);
+ public void setCurrentOrientationLw(@ActivityInfo.ScreenOrientation int newOrientation);
/**
* Call from application to perform haptic feedback on its window.
@@ -1113,6 +1124,7 @@ public interface WindowManagerPolicy {
* @see WindowManagerPolicy#USER_ROTATION_LOCKED
* @see WindowManagerPolicy#USER_ROTATION_FREE
*/
+ @UserRotationMode
public int getUserRotationMode();
/**
@@ -1123,12 +1135,12 @@ public interface WindowManagerPolicy {
* @param rotation One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
* {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
*/
- public void setUserRotationMode(int mode, int rotation);
+ public void setUserRotationMode(@UserRotationMode int mode, @Surface.Rotation int rotation);
/**
* Called when a new system UI visibility is being reported, allowing
* the policy to adjust what is actually reported.
- * @param visibility The raw visiblity reported by the status bar.
+ * @param visibility The raw visibility reported by the status bar.
* @return The new desired visibility.
*/
public int adjustSystemUiVisibilityLw(int visibility);
@@ -1151,6 +1163,11 @@ public interface WindowManagerPolicy {
public void setLastInputMethodWindowLw(WindowState ime, WindowState target);
/**
+ * @return The current height of the input method window.
+ */
+ public int getInputMethodWindowVisibleHeightLw();
+
+ /**
* Called when the current user changes. Guaranteed to be called before the broadcast
* of the new user id is made to all listeners.
*
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index f635eee..8b91155 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -722,7 +722,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
int mAction;
int mContentChangeTypes;
- private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>();
+ private ArrayList<AccessibilityRecord> mRecords;
/*
* Hide constructor from clients.
@@ -755,11 +755,13 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
@Override
public void setSealed(boolean sealed) {
super.setSealed(sealed);
- List<AccessibilityRecord> records = mRecords;
- final int recordCount = records.size();
- for (int i = 0; i < recordCount; i++) {
- AccessibilityRecord record = records.get(i);
- record.setSealed(sealed);
+ final List<AccessibilityRecord> records = mRecords;
+ if (records != null) {
+ final int recordCount = records.size();
+ for (int i = 0; i < recordCount; i++) {
+ AccessibilityRecord record = records.get(i);
+ record.setSealed(sealed);
+ }
}
}
@@ -769,7 +771,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* @return The number of records.
*/
public int getRecordCount() {
- return mRecords.size();
+ return mRecords == null ? 0 : mRecords.size();
}
/**
@@ -781,6 +783,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
*/
public void appendRecord(AccessibilityRecord record) {
enforceNotSealed();
+ if (mRecords == null) {
+ mRecords = new ArrayList<AccessibilityRecord>();
+ }
mRecords.add(record);
}
@@ -791,6 +796,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* @return The record at the specified index.
*/
public AccessibilityRecord getRecord(int index) {
+ if (mRecords == null) {
+ throw new IndexOutOfBoundsException("Invalid index " + index + ", size is 0");
+ }
return mRecords.get(index);
}
@@ -964,11 +972,14 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
AccessibilityEvent eventClone = AccessibilityEvent.obtain();
eventClone.init(event);
- final int recordCount = event.mRecords.size();
- for (int i = 0; i < recordCount; i++) {
- AccessibilityRecord record = event.mRecords.get(i);
- AccessibilityRecord recordClone = AccessibilityRecord.obtain(record);
- eventClone.mRecords.add(recordClone);
+ if (event.mRecords != null) {
+ final int recordCount = event.mRecords.size();
+ eventClone.mRecords = new ArrayList<AccessibilityRecord>(recordCount);
+ for (int i = 0; i < recordCount; i++) {
+ final AccessibilityRecord record = event.mRecords.get(i);
+ final AccessibilityRecord recordClone = AccessibilityRecord.obtain(record);
+ eventClone.mRecords.add(recordClone);
+ }
}
return eventClone;
@@ -1013,9 +1024,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
mContentChangeTypes = 0;
mPackageName = null;
mEventTime = 0;
- while (!mRecords.isEmpty()) {
- AccessibilityRecord record = mRecords.remove(0);
- record.recycle();
+ if (mRecords != null) {
+ while (!mRecords.isEmpty()) {
+ AccessibilityRecord record = mRecords.remove(0);
+ record.recycle();
+ }
}
}
@@ -1037,11 +1050,14 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
// Read the records.
final int recordCount = parcel.readInt();
- for (int i = 0; i < recordCount; i++) {
- AccessibilityRecord record = AccessibilityRecord.obtain();
- readAccessibilityRecordFromParcel(record, parcel);
- record.mConnectionId = mConnectionId;
- mRecords.add(record);
+ if (recordCount > 0) {
+ mRecords = new ArrayList<AccessibilityRecord>(recordCount);
+ for (int i = 0; i < recordCount; i++) {
+ AccessibilityRecord record = AccessibilityRecord.obtain();
+ readAccessibilityRecordFromParcel(record, parcel);
+ record.mConnectionId = mConnectionId;
+ mRecords.add(record);
+ }
}
}
@@ -1147,8 +1163,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
builder.append("; ContentChangeTypes: ").append(mContentChangeTypes);
builder.append("; sourceWindowId: ").append(mSourceWindowId);
builder.append("; mSourceNodeId: ").append(mSourceNodeId);
- for (int i = 0; i < mRecords.size(); i++) {
- AccessibilityRecord record = mRecords.get(i);
+ for (int i = 0; i < getRecordCount(); i++) {
+ final AccessibilityRecord record = getRecord(i);
builder.append(" Record ");
builder.append(i);
builder.append(":");
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 139df3e..5a55e34 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -27,7 +27,6 @@ import android.os.SystemClock;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;
-import android.util.SparseLongArray;
import java.util.ArrayList;
import java.util.Collections;
@@ -718,10 +717,9 @@ public final class AccessibilityInteractionClient
Log.e(LOG_TAG, "Duplicate node.");
return;
}
- SparseLongArray childIds = current.getChildNodeIds();
- final int childCount = childIds.size();
+ final int childCount = current.getChildCount();
for (int i = 0; i < childCount; i++) {
- final long childId = childIds.valueAt(i);
+ final long childId = current.getChildId(i);
for (int j = 0; j < infoCount; j++) {
AccessibilityNodeInfo child = infos.get(j);
if (child.getSourceNodeId() == childId) {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 00f4adb..324ba77 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -75,6 +75,42 @@ public final class AccessibilityManager {
/** @hide */
public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
+ /** @hide */
+ public static final int INVERSION_DISABLED = -1;
+
+ /** @hide */
+ public static final int INVERSION_STANDARD = 0;
+
+ /** @hide */
+ public static final int INVERSION_HUE_ONLY = 1;
+
+ /** @hide */
+ public static final int INVERSION_VALUE_ONLY = 2;
+
+ /** @hide */
+ public static final int DALTONIZER_DISABLED = -1;
+
+ /** @hide */
+ public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
+
+ /** @hide */
+ public static final int DALTONIZER_SIMULATE_PROTANOMALY = 1;
+
+ /** @hide */
+ public static final int DALTONIZER_SIMULATE_DEUTERANOMALY = 2;
+
+ /** @hide */
+ public static final int DALTONIZER_SIMULATE_TRITANOMALY = 3;
+
+ /** @hide */
+ public static final int DALTONIZER_CORRECT_PROTANOMALY = 11;
+
+ /** @hide */
+ public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
+
+ /** @hide */
+ public static final int DALTONIZER_CORRECT_TRITANOMALY = 13;
+
static final Object sInstanceSync = new Object();
private static AccessibilityManager sInstance;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 4f53c1e..61aabea 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -22,8 +22,8 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.InputType;
+import android.util.LongArray;
import android.util.Pools.SynchronizedPool;
-import android.util.SparseLongArray;
import android.view.View;
import java.util.Collections;
@@ -503,7 +503,7 @@ public class AccessibilityNodeInfo implements Parcelable {
private CharSequence mContentDescription;
private String mViewIdResourceName;
- private final SparseLongArray mChildNodeIds = new SparseLongArray();
+ private LongArray mChildNodeIds;
private int mActions;
private int mMovementGranularities;
@@ -666,21 +666,35 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
- * @return The ids of the children.
+ * Returns the array containing the IDs of this node's children.
*
* @hide
*/
- public SparseLongArray getChildNodeIds() {
+ public LongArray getChildNodeIds() {
return mChildNodeIds;
}
/**
+ * Returns the id of the child at the specified index.
+ *
+ * @throws IndexOutOfBoundsException when index &lt; 0 || index &gt;=
+ * getChildCount()
+ * @hide
+ */
+ public long getChildId(int index) {
+ if (mChildNodeIds == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mChildNodeIds.get(index);
+ }
+
+ /**
* Gets the number of children.
*
* @return The child count.
*/
public int getChildCount() {
- return mChildNodeIds.size();
+ return mChildNodeIds == null ? 0 : mChildNodeIds.size();
}
/**
@@ -699,6 +713,9 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public AccessibilityNodeInfo getChild(int index) {
enforceSealed();
+ if (mChildNodeIds == null) {
+ return null;
+ }
if (!canPerformRequestOverConnection(mSourceNodeId)) {
return null;
}
@@ -721,7 +738,35 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void addChild(View child) {
- addChild(child, UNDEFINED);
+ addChildInternal(child, UNDEFINED, true);
+ }
+
+ /**
+ * Unchecked version of {@link #addChild(View)} that does not verify
+ * uniqueness. For framework use only.
+ *
+ * @hide
+ */
+ public void addChildUnchecked(View child) {
+ addChildInternal(child, UNDEFINED, false);
+ }
+
+ /**
+ * Removes a child. If the child was not previously added to the node,
+ * calling this method has no effect.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param child The child.
+ * @return true if the child was present
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public boolean removeChild(View child) {
+ return removeChild(child, UNDEFINED);
}
/**
@@ -739,12 +784,49 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param virtualDescendantId The id of the virtual child.
*/
public void addChild(View root, int virtualDescendantId) {
+ addChildInternal(root, virtualDescendantId, true);
+ }
+
+ private void addChildInternal(View root, int virtualDescendantId, boolean checked) {
enforceNotSealed();
- final int index = mChildNodeIds.size();
+ if (mChildNodeIds == null) {
+ mChildNodeIds = new LongArray();
+ }
final int rootAccessibilityViewId =
(root != null) ? root.getAccessibilityViewId() : UNDEFINED;
final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
- mChildNodeIds.put(index, childNodeId);
+ // If we're checking uniqueness and the ID already exists, abort.
+ if (checked && mChildNodeIds.indexOf(childNodeId) >= 0) {
+ return;
+ }
+ mChildNodeIds.add(childNodeId);
+ }
+
+ /**
+ * Removes a virtual child which is a descendant of the given
+ * <code>root</code>. If the child was not previously added to the node,
+ * calling this method has no effect.
+ *
+ * @param root The root of the virtual subtree.
+ * @param virtualDescendantId The id of the virtual child.
+ * @return true if the child was present
+ * @see #addChild(View, int)
+ */
+ public boolean removeChild(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final LongArray childIds = mChildNodeIds;
+ if (childIds == null) {
+ return false;
+ }
+ final int rootAccessibilityViewId =
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
+ final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ final int index = childIds.indexOf(childNodeId);
+ if (index < 0) {
+ return false;
+ }
+ childIds.remove(index);
+ return true;
}
/**
@@ -789,6 +871,24 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Removes an action that can be performed on the node. If the action was
+ * not already added to the node, calling this method has no effect.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param action The action.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void removeAction(int action) {
+ enforceNotSealed();
+ mActions &= ~action;
+ }
+
+ /**
* Sets the movement granularities for traversing the text of this node.
* <p>
* <strong>Note:</strong> Cannot be called from an
@@ -1408,8 +1508,6 @@ public class AccessibilityNodeInfo implements Parcelable {
* {@link android.accessibilityservice.AccessibilityService}.
* This class is made immutable before being delivered to an AccessibilityService.
* </p>
- *
- * @return collectionItem True if the node is an item.
*/
public void setCollectionItemInfo(CollectionItemInfo collectionItemInfo) {
enforceNotSealed();
@@ -1951,6 +2049,7 @@ public class AccessibilityNodeInfo implements Parcelable {
/**
* {@inheritDoc}
*/
+ @Override
public int describeContents() {
return 0;
}
@@ -2114,6 +2213,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* is recycled. You must not touch the object after calling this function.
* </p>
*/
+ @Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(isSealed() ? 1 : 0);
parcel.writeLong(mSourceNodeId);
@@ -2123,11 +2223,15 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.writeLong(mLabeledById);
parcel.writeInt(mConnectionId);
- SparseLongArray childIds = mChildNodeIds;
- final int childIdsSize = childIds.size();
- parcel.writeInt(childIdsSize);
- for (int i = 0; i < childIdsSize; i++) {
- parcel.writeLong(childIds.valueAt(i));
+ final LongArray childIds = mChildNodeIds;
+ if (childIds == null) {
+ parcel.writeInt(0);
+ } else {
+ final int childIdsSize = childIds.size();
+ parcel.writeInt(childIdsSize);
+ for (int i = 0; i < childIdsSize; i++) {
+ parcel.writeLong(childIds.get(i));
+ }
}
parcel.writeInt(mBoundsInParent.top);
@@ -2222,10 +2326,16 @@ public class AccessibilityNodeInfo implements Parcelable {
mActions= other.mActions;
mBooleanProperties = other.mBooleanProperties;
mMovementGranularities = other.mMovementGranularities;
- final int otherChildIdCount = other.mChildNodeIds.size();
- for (int i = 0; i < otherChildIdCount; i++) {
- mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i));
+
+ final LongArray otherChildNodeIds = other.mChildNodeIds;
+ if (otherChildNodeIds != null && otherChildNodeIds.size() > 0) {
+ if (mChildNodeIds == null) {
+ mChildNodeIds = otherChildNodeIds.clone();
+ } else {
+ mChildNodeIds.addAll(otherChildNodeIds);
+ }
}
+
mTextSelectionStart = other.mTextSelectionStart;
mTextSelectionEnd = other.mTextSelectionEnd;
mInputType = other.mInputType;
@@ -2255,11 +2365,15 @@ public class AccessibilityNodeInfo implements Parcelable {
mLabeledById = parcel.readLong();
mConnectionId = parcel.readInt();
- SparseLongArray childIds = mChildNodeIds;
final int childrenSize = parcel.readInt();
- for (int i = 0; i < childrenSize; i++) {
- final long childId = parcel.readLong();
- childIds.put(i, childId);
+ if (childrenSize <= 0) {
+ mChildNodeIds = null;
+ } else {
+ mChildNodeIds = new LongArray(childrenSize);
+ for (int i = 0; i < childrenSize; i++) {
+ final long childId = parcel.readLong();
+ mChildNodeIds.add(childId);
+ }
}
mBoundsInParent.top = parcel.readInt();
@@ -2331,7 +2445,9 @@ public class AccessibilityNodeInfo implements Parcelable {
mWindowId = UNDEFINED;
mConnectionId = UNDEFINED;
mMovementGranularities = 0;
- mChildNodeIds.clear();
+ if (mChildNodeIds != null) {
+ mChildNodeIds.clear();
+ }
mBoundsInParent.set(0, 0, 0, 0);
mBoundsInScreen.set(0, 0, 0, 0);
mBooleanProperties = 0;
@@ -2493,12 +2609,14 @@ public class AccessibilityNodeInfo implements Parcelable {
}
builder.append("]");
- SparseLongArray childIds = mChildNodeIds;
builder.append("; childAccessibilityIds: [");
- for (int i = 0, count = childIds.size(); i < count; i++) {
- builder.append(childIds.valueAt(i));
- if (i < count - 1) {
- builder.append(", ");
+ final LongArray childIds = mChildNodeIds;
+ if (childIds != null) {
+ for (int i = 0, count = childIds.size(); i < count; i++) {
+ builder.append(childIds.get(i));
+ if (i < count - 1) {
+ builder.append(", ");
+ }
}
}
builder.append("]");
@@ -2893,16 +3011,18 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
- * @see Parcelable.Creator
+ * @see android.os.Parcelable.Creator
*/
public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR =
new Parcelable.Creator<AccessibilityNodeInfo>() {
+ @Override
public AccessibilityNodeInfo createFromParcel(Parcel parcel) {
AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
info.initFromParcel(parcel);
return info;
}
+ @Override
public AccessibilityNodeInfo[] newArray(int size) {
return new AccessibilityNodeInfo[size];
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
index a9473a8..3f79b47 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
@@ -18,8 +18,8 @@ package android.view.accessibility;
import android.os.Build;
import android.util.Log;
+import android.util.LongArray;
import android.util.LongSparseArray;
-import android.util.SparseLongArray;
import java.util.HashSet;
import java.util.LinkedList;
@@ -172,12 +172,12 @@ public class AccessibilityNodeInfoCache {
// the new one represents a source state where some of the
// children have been removed to avoid having disconnected
// subtrees in the cache.
- SparseLongArray oldChildrenIds = oldInfo.getChildNodeIds();
- SparseLongArray newChildrenIds = info.getChildNodeIds();
- final int oldChildCount = oldChildrenIds.size();
+ // TODO: Runs in O(n^2), could optimize to O(n + n log n)
+ final LongArray newChildrenIds = info.getChildNodeIds();
+ final int oldChildCount = oldInfo.getChildCount();
for (int i = 0; i < oldChildCount; i++) {
- final long oldChildId = oldChildrenIds.valueAt(i);
- if (newChildrenIds.indexOfValue(oldChildId) < 0) {
+ final long oldChildId = oldInfo.getChildId(i);
+ if (newChildrenIds.indexOf(oldChildId) < 0) {
clearSubTreeLocked(oldChildId);
}
}
@@ -237,10 +237,9 @@ public class AccessibilityNodeInfoCache {
return;
}
mCacheImpl.remove(rootNodeId);
- SparseLongArray childNodeIds = current.getChildNodeIds();
- final int childCount = childNodeIds.size();
+ final int childCount = current.getChildCount();
for (int i = 0; i < childCount; i++) {
- final long childNodeId = childNodeIds.valueAt(i);
+ final long childNodeId = current.getChildId(i);
clearSubTreeRecursiveLocked(childNodeId);
}
}
@@ -301,11 +300,10 @@ public class AccessibilityNodeInfoCache {
}
}
- SparseLongArray childIds = current.getChildNodeIds();
- final int childCount = childIds.size();
+ final int childCount = current.getChildCount();
for (int i = 0; i < childCount; i++) {
- final long childId = childIds.valueAt(i);
- AccessibilityNodeInfo child = mCacheImpl.get(childId);
+ final long childId = current.getChildId(i);
+ final AccessibilityNodeInfo child = mCacheImpl.get(childId);
if (child != null) {
fringe.add(child);
}
diff --git a/core/java/android/view/animation/BounceInterpolator.java b/core/java/android/view/animation/BounceInterpolator.java
index f79e730..ecf99a7 100644
--- a/core/java/android/view/animation/BounceInterpolator.java
+++ b/core/java/android/view/animation/BounceInterpolator.java
@@ -17,7 +17,6 @@
package android.view.animation;
import android.content.Context;
-import android.content.res.TypedArray;
import android.util.AttributeSet;
/**
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index f730cf7..5552952 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -601,7 +601,11 @@ public class BaseInputConnection implements InputConnection {
}
beginBatchEdit();
-
+ if (!composing && !TextUtils.isEmpty(text)) {
+ // Notify the text is committed by the user to InputMethodManagerService
+ mIMM.notifyTextCommitted();
+ }
+
// delete composing text set previously.
int a = getComposingSpanStart(content);
int b = getComposingSpanEnd(content);
diff --git a/core/java/android/view/inputmethod/ExtractedTextRequest.java b/core/java/android/view/inputmethod/ExtractedTextRequest.java
index f658b87..bf0bef3 100644
--- a/core/java/android/view/inputmethod/ExtractedTextRequest.java
+++ b/core/java/android/view/inputmethod/ExtractedTextRequest.java
@@ -18,7 +18,6 @@ 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
diff --git a/core/java/android/view/inputmethod/InputBinding.java b/core/java/android/view/inputmethod/InputBinding.java
index f4209ef..bcd459e 100644
--- a/core/java/android/view/inputmethod/InputBinding.java
+++ b/core/java/android/view/inputmethod/InputBinding.java
@@ -19,7 +19,6 @@ 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
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 53f7c79..70c53d2 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1805,6 +1805,20 @@ public final class InputMethodManager {
}
/**
+ * Notify the current IME commits text
+ * @hide
+ */
+ public void notifyTextCommitted() {
+ synchronized (mH) {
+ try {
+ mService.notifyTextCommitted();
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+
+ /**
* Returns a map of all shortcut input method info and their subtypes.
*/
public Map<InputMethodInfo, List<InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() {
@@ -1840,6 +1854,21 @@ public final class InputMethodManager {
}
/**
+ * @return The current height of the input method window.
+ * @hide
+ */
+ public int getInputMethodWindowVisibleHeight() {
+ synchronized (mH) {
+ try {
+ return mService.getInputMethodWindowVisibleHeight();
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ return 0;
+ }
+ }
+ }
+
+ /**
* Force switch to the last used input method and subtype. If the last input method didn't have
* any subtypes, the framework will simply switch to the last input method with no subtype
* specified.
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 2ab3024..e7ada27 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -472,12 +472,12 @@ public final class InputMethodSubtype implements Parcelable {
return (subtype.hashCode() == hashCode());
}
return (subtype.hashCode() == hashCode())
- && (subtype.getNameResId() == getNameResId())
- && (subtype.getMode().equals(getMode()))
- && (subtype.getIconResId() == getIconResId())
&& (subtype.getLocale().equals(getLocale()))
+ && (subtype.getMode().equals(getMode()))
&& (subtype.getExtraValue().equals(getExtraValue()))
&& (subtype.isAuxiliary() == isAuxiliary())
+ && (subtype.overridesImplicitlyEnabledSubtype()
+ == overridesImplicitlyEnabledSubtype())
&& (subtype.isAsciiCapable() == isAsciiCapable());
}
return false;
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index bbd3f2b..45e6eb3 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -16,13 +16,7 @@
package android.webkit;
-import android.content.Context;
-import android.net.http.Headers;
-import android.util.Log;
-
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
diff --git a/core/java/android/webkit/DateSorter.java b/core/java/android/webkit/DateSorter.java
index 82c13ae..fede244 100644
--- a/core/java/android/webkit/DateSorter.java
+++ b/core/java/android/webkit/DateSorter.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.res.Resources;
import java.util.Calendar;
-import java.util.Date;
import java.util.Locale;
import libcore.icu.LocaleData;
diff --git a/core/java/android/webkit/Plugin.java b/core/java/android/webkit/Plugin.java
index 529820b..072e02a 100644
--- a/core/java/android/webkit/Plugin.java
+++ b/core/java/android/webkit/Plugin.java
@@ -21,7 +21,6 @@ import com.android.internal.R;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
-import android.webkit.WebView;
/**
* Represents a plugin (Java equivalent of the PluginPackageAndroid
diff --git a/core/java/android/webkit/WebResourceResponse.java b/core/java/android/webkit/WebResourceResponse.java
index b7171ee..f21e2b4 100644
--- a/core/java/android/webkit/WebResourceResponse.java
+++ b/core/java/android/webkit/WebResourceResponse.java
@@ -16,8 +16,6 @@
package android.webkit;
-import android.net.http.Headers;
-
import java.io.InputStream;
/**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 5bc39f1..7ee33c1 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -28,7 +28,6 @@ import android.graphics.drawable.Drawable;
import android.net.http.SslCertificate;
import android.os.Build;
import android.os.Bundle;
-import android.os.CancellationSignal;
import android.os.Looper;
import android.os.Message;
import android.os.StrictMode;
@@ -255,7 +254,7 @@ public class WebView extends AbsoluteLayout
// Throwing an exception for incorrect thread usage if the
// build target is JB MR2 or newer. Defaults to false, and is
// set in the WebView constructor.
- private static Boolean sEnforceThreadChecking = false;
+ private static volatile boolean sEnforceThreadChecking = false;
/**
* Transportation object for returning WebView across thread boundaries.
@@ -449,10 +448,12 @@ public class WebView extends AbsoluteLayout
*
* @param context a Context object used to access application assets
* @param attrs an AttributeSet passed to our parent
- * @param defStyle the default style resource ID
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
*/
- public WebView(Context context, AttributeSet attrs, int defStyle) {
- this(context, attrs, defStyle, false);
+ public WebView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
/**
@@ -460,7 +461,26 @@ public class WebView extends AbsoluteLayout
*
* @param context a Context object used to access application assets
* @param attrs an AttributeSet passed to our parent
- * @param defStyle the default style resource ID
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes a resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ */
+ public WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ this(context, attrs, defStyleAttr, defStyleRes, null, false);
+ }
+
+ /**
+ * Constructs a new WebView with layout parameters and a default style.
+ *
+ * @param context a Context object used to access application assets
+ * @param attrs an AttributeSet passed to our parent
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
* @param privateBrowsing whether this WebView will be initialized in
* private mode
*
@@ -470,9 +490,9 @@ public class WebView extends AbsoluteLayout
* and {@link WebStorage} for fine-grained control of privacy data.
*/
@Deprecated
- public WebView(Context context, AttributeSet attrs, int defStyle,
+ public WebView(Context context, AttributeSet attrs, int defStyleAttr,
boolean privateBrowsing) {
- this(context, attrs, defStyle, null, privateBrowsing);
+ this(context, attrs, defStyleAttr, 0, null, privateBrowsing);
}
/**
@@ -483,7 +503,9 @@ public class WebView extends AbsoluteLayout
*
* @param context a Context object used to access application assets
* @param attrs an AttributeSet passed to our parent
- * @param defStyle the default style resource ID
+ * @param defStyleAttr an attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
* @param javaScriptInterfaces a Map of interface names, as keys, and
* object implementing those interfaces, as
* values
@@ -492,10 +514,18 @@ public class WebView extends AbsoluteLayout
* @hide This is used internally by dumprendertree, as it requires the javaScript interfaces to
* be added synchronously, before a subsequent loadUrl call takes effect.
*/
+ protected WebView(Context context, AttributeSet attrs, int defStyleAttr,
+ Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
+ this(context, attrs, defStyleAttr, 0, javaScriptInterfaces, privateBrowsing);
+ }
+
+ /**
+ * @hide
+ */
@SuppressWarnings("deprecation") // for super() call into deprecated base class constructor.
- protected WebView(Context context, AttributeSet attrs, int defStyle,
+ protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
- super(context, attrs, defStyle);
+ super(context, attrs, defStyleAttr, defStyleRes);
if (context == null) {
throw new IllegalArgumentException("Invalid context argument");
}
@@ -686,6 +716,15 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Used only by internal tests to free up memory.
+ *
+ * @hide
+ */
+ public static void freeMemoryForTests() {
+ getFactory().getStatics().freeMemoryForTests();
+ }
+
+ /**
* Informs 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
@@ -781,7 +820,15 @@ public class WebView extends AbsoluteLayout
*/
public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
checkThread();
- if (DebugFlags.TRACE_API) Log.d(LOGTAG, "loadUrl(extra headers)=" + url);
+ if (DebugFlags.TRACE_API) {
+ StringBuilder headers = new StringBuilder();
+ if (additionalHttpHeaders != null) {
+ for (Map.Entry<String, String> entry : additionalHttpHeaders.entrySet()) {
+ headers.append(entry.getKey() + ":" + entry.getValue() + "\n");
+ }
+ }
+ Log.d(LOGTAG, "loadUrl(extra headers)=" + url + "\n" + headers);
+ }
mProvider.loadUrl(url, additionalHttpHeaders);
}
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index b9131bf..25bcd44 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -16,9 +16,7 @@
package android.webkit;
-import android.os.Build;
import android.os.StrictMode;
-import android.os.SystemProperties;
import android.util.AndroidRuntimeException;
import android.util.Log;
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 9d9d882..e391aaf 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -50,6 +50,11 @@ public interface WebViewFactoryProvider {
String getDefaultUserAgent(Context context);
/**
+ * Used for tests only.
+ */
+ void freeMemoryForTests();
+
+ /**
* Implements the API method:
* {@link android.webkit.WebView#setWebContentsDebuggingEnabled(boolean) }
*/
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 092f474..413d6cf 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -55,6 +55,7 @@ import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -581,7 +582,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
/**
* Helper object that renders and controls the fast scroll thumb.
*/
- private FastScroller mFastScroller;
+ private FastScroller mFastScroll;
+
+ /**
+ * Temporary holder for fast scroller style until a FastScroller object
+ * is created.
+ */
+ private int mFastScrollStyle;
private boolean mGlobalLayoutListenerAddedFilter;
@@ -773,14 +780,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
this(context, attrs, com.android.internal.R.attr.absListViewStyle);
}
- public AbsListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public AbsListView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
initAbsListView();
mOwnerThread = Thread.currentThread();
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.AbsListView, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.AbsListView, defStyleAttr, defStyleRes);
Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector);
if (d != null) {
@@ -809,6 +820,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false);
setFastScrollEnabled(enableFastScroll);
+ int fastScrollStyle = a.getResourceId(R.styleable.AbsListView_fastScrollStyle, 0);
+ setFastScrollStyle(fastScrollStyle);
+
boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
setSmoothScrollbarEnabled(smoothScrollbar);
@@ -1238,17 +1252,31 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
private void setFastScrollerEnabledUiThread(boolean enabled) {
- if (mFastScroller != null) {
- mFastScroller.setEnabled(enabled);
+ if (mFastScroll != null) {
+ mFastScroll.setEnabled(enabled);
} else if (enabled) {
- mFastScroller = new FastScroller(this);
- mFastScroller.setEnabled(true);
+ mFastScroll = new FastScroller(this, mFastScrollStyle);
+ mFastScroll.setEnabled(true);
}
resolvePadding();
- if (mFastScroller != null) {
- mFastScroller.updateLayout();
+ if (mFastScroll != null) {
+ mFastScroll.updateLayout();
+ }
+ }
+
+ /**
+ * Specifies the style of the fast scroller decorations.
+ *
+ * @param styleResId style resource containing fast scroller properties
+ * @see android.R.styleable#FastScroll
+ */
+ public void setFastScrollStyle(int styleResId) {
+ if (mFastScroll == null) {
+ mFastScrollStyle = styleResId;
+ } else {
+ mFastScroll.setStyle(styleResId);
}
}
@@ -1288,8 +1316,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
private void setFastScrollerAlwaysVisibleUiThread(boolean alwaysShow) {
- if (mFastScroller != null) {
- mFastScroller.setAlwaysShow(alwaysShow);
+ if (mFastScroll != null) {
+ mFastScroll.setAlwaysShow(alwaysShow);
}
}
@@ -1307,17 +1335,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* @see #setFastScrollAlwaysVisible(boolean)
*/
public boolean isFastScrollAlwaysVisible() {
- if (mFastScroller == null) {
+ if (mFastScroll == null) {
return mFastScrollEnabled && mFastScrollAlwaysVisible;
} else {
- return mFastScroller.isEnabled() && mFastScroller.isAlwaysShowEnabled();
+ return mFastScroll.isEnabled() && mFastScroll.isAlwaysShowEnabled();
}
}
@Override
public int getVerticalScrollbarWidth() {
- if (mFastScroller != null && mFastScroller.isEnabled()) {
- return Math.max(super.getVerticalScrollbarWidth(), mFastScroller.getWidth());
+ if (mFastScroll != null && mFastScroll.isEnabled()) {
+ return Math.max(super.getVerticalScrollbarWidth(), mFastScroll.getWidth());
}
return super.getVerticalScrollbarWidth();
}
@@ -1330,26 +1358,26 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
@ViewDebug.ExportedProperty
public boolean isFastScrollEnabled() {
- if (mFastScroller == null) {
+ if (mFastScroll == null) {
return mFastScrollEnabled;
} else {
- return mFastScroller.isEnabled();
+ return mFastScroll.isEnabled();
}
}
@Override
public void setVerticalScrollbarPosition(int position) {
super.setVerticalScrollbarPosition(position);
- if (mFastScroller != null) {
- mFastScroller.setScrollbarPosition(position);
+ if (mFastScroll != null) {
+ mFastScroll.setScrollbarPosition(position);
}
}
@Override
public void setScrollBarStyle(int style) {
super.setScrollBarStyle(style);
- if (mFastScroller != null) {
- mFastScroller.setScrollBarStyle(style);
+ if (mFastScroll != null) {
+ mFastScroll.setScrollBarStyle(style);
}
}
@@ -1410,8 +1438,8 @@ 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(mFirstPosition, getChildCount(), mItemCount);
+ if (mFastScroll != null) {
+ mFastScroll.onScroll(mFirstPosition, getChildCount(), mItemCount);
}
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
@@ -2084,8 +2112,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mRecycler.markChildrenDirty();
}
- if (mFastScroller != null && (mItemCount != mOldItemCount || mDataChanged)) {
- mFastScroller.onItemCountChanged(mItemCount);
+ if (mFastScroll != null && (mItemCount != mOldItemCount || mDataChanged)) {
+ mFastScroll.onItemCountChanged(mItemCount);
}
layoutChildren();
@@ -2120,6 +2148,34 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
protected void layoutChildren() {
}
+ /**
+ * @return the direct child that contains accessibility focus, or null if no
+ * child contains accessibility focus
+ */
+ View getAccessibilityFocusedChild() {
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl == null) {
+ return null;
+ }
+
+ View focusedView = viewRootImpl.getAccessibilityFocusedHost();
+ if (focusedView == null) {
+ return null;
+ }
+
+ ViewParent viewParent = focusedView.getParent();
+ while ((viewParent instanceof View) && (viewParent != this)) {
+ focusedView = (View) viewParent;
+ viewParent = viewParent.getParent();
+ }
+
+ if (!(viewParent instanceof View)) {
+ return null;
+ }
+
+ return focusedView;
+ }
+
void updateScrollIndicators() {
if (mScrollUp != null) {
boolean canScrollUp;
@@ -2499,8 +2555,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
rememberSyncState();
}
- if (mFastScroller != null) {
- mFastScroller.onSizeChanged(w, h, oldw, oldh);
+ if (mFastScroll != null) {
+ mFastScroll.onSizeChanged(w, h, oldw, oldh);
}
}
@@ -2829,8 +2885,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
super.onRtlPropertiesChanged(layoutDirection);
- if (mFastScroller != null) {
- mFastScroller.setScrollbarPosition(getVerticalScrollbarPosition());
+ if (mFastScroll != null) {
+ mFastScroll.setScrollbarPosition(getVerticalScrollbarPosition());
}
}
@@ -3403,8 +3459,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return false;
}
- if (mFastScroller != null) {
- boolean intercepted = mFastScroller.onTouchEvent(ev);
+ if (mFastScroll != null) {
+ boolean intercepted = mFastScroll.onTouchEvent(ev);
if (intercepted) {
return true;
}
@@ -3893,7 +3949,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
@Override
public boolean onInterceptHoverEvent(MotionEvent event) {
- if (mFastScroller != null && mFastScroller.onInterceptHoverEvent(event)) {
+ if (mFastScroll != null && mFastScroll.onInterceptHoverEvent(event)) {
return true;
}
@@ -3917,7 +3973,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return false;
}
- if (mFastScroller != null && mFastScroller.onInterceptTouchEvent(ev)) {
+ if (mFastScroll != null && mFastScroll.onInterceptTouchEvent(ev)) {
return true;
}
@@ -6278,16 +6334,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
@Override
public void onChanged() {
super.onChanged();
- if (mFastScroller != null) {
- mFastScroller.onSectionsChanged();
+ if (mFastScroll != null) {
+ mFastScroll.onSectionsChanged();
}
}
@Override
public void onInvalidated() {
super.onInvalidated();
- if (mFastScroller != null) {
- mFastScroller.onSectionsChanged();
+ if (mFastScroll != null) {
+ mFastScroll.onSectionsChanged();
}
}
}
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 7674837..941cdd0 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -65,11 +65,15 @@ public abstract class AbsSeekBar extends ProgressBar {
super(context, attrs);
}
- public AbsSeekBar(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public AbsSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AbsSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.SeekBar, defStyle, 0);
+ TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.SeekBar, defStyleAttr, defStyleRes);
Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb);
setThumb(thumb); // will guess mThumbOffset if thumb != null...
// ...but allow layout to override this
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index f26527f..6a4ad75 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -64,12 +64,16 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
this(context, attrs, 0);
}
- public AbsSpinner(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public AbsSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AbsSpinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
initAbsSpinner();
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.AbsSpinner, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.AbsSpinner, defStyleAttr, defStyleRes);
CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries);
if (entries != null) {
diff --git a/core/java/android/widget/AbsoluteLayout.java b/core/java/android/widget/AbsoluteLayout.java
index 7df6aab..4ce0d5d 100644
--- a/core/java/android/widget/AbsoluteLayout.java
+++ b/core/java/android/widget/AbsoluteLayout.java
@@ -40,16 +40,19 @@ import android.widget.RemoteViews.RemoteView;
@RemoteView
public class AbsoluteLayout extends ViewGroup {
public AbsoluteLayout(Context context) {
- super(context);
+ this(context, null);
}
public AbsoluteLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
}
- public AbsoluteLayout(Context context, AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);
+ public AbsoluteLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AbsoluteLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java
index 8612964..f9af2f9 100644
--- a/core/java/android/widget/ActivityChooserView.java
+++ b/core/java/android/widget/ActivityChooserView.java
@@ -18,7 +18,6 @@ package android.widget;
import com.android.internal.R;
-import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -31,7 +30,6 @@ import android.util.AttributeSet;
import android.util.Log;
import android.view.ActionProvider;
import android.view.LayoutInflater;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -204,13 +202,32 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
*
* @param context The application environment.
* @param attrs A collection of attributes.
- * @param defStyle The default style to apply to this view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
*/
- public ActivityChooserView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ /**
+ * Create a new instance.
+ *
+ * @param context The application environment.
+ * @param attrs A collection of attributes.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ */
+ public ActivityChooserView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
TypedArray attributesArray = context.obtainStyledAttributes(attrs,
- R.styleable.ActivityChooserView, defStyle, 0);
+ R.styleable.ActivityChooserView, defStyleAttr, defStyleRes);
mInitialActivityCount = attributesArray.getInt(
R.styleable.ActivityChooserView_initialActivityCount,
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index a5fad60..962ffba 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -223,15 +223,19 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
boolean mBlockLayoutRequests = false;
public AdapterView(Context context) {
- super(context);
+ this(context, null);
}
public AdapterView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
}
- public AdapterView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public AdapterView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AdapterView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
// If not explicitly specified this view is important for accessibility.
if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 90e949a..1bc2f4b 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -173,10 +173,15 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
}
public AdapterViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AdapterViewAnimator(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.AdapterViewAnimator, defStyleAttr, 0);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.AdapterViewAnimator, defStyleAttr, defStyleRes);
int resource = a.getResourceId(
com.android.internal.R.styleable.AdapterViewAnimator_inAnimation, 0);
if (resource > 0) {
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
index aea029b..3b026bd 100644
--- a/core/java/android/widget/AdapterViewFlipper.java
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -59,10 +59,19 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
}
public AdapterViewFlipper(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
+ }
+
+ public AdapterViewFlipper(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AdapterViewFlipper(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.AdapterViewFlipper);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.AdapterViewFlipper, defStyleAttr, defStyleRes);
mFlipInterval = a.getInt(
com.android.internal.R.styleable.AdapterViewFlipper_flipInterval, DEFAULT_INTERVAL);
mAutoStart = a.getBoolean(
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index c7da818..3c88e94 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -67,13 +67,16 @@ public class AnalogClock extends View {
this(context, attrs, 0);
}
- public AnalogClock(Context context, AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);
- Resources r = mContext.getResources();
- TypedArray a =
- context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.AnalogClock, defStyle, 0);
+ public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final Resources r = context.getResources();
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.AnalogClock, defStyleAttr, defStyleRes);
mDial = a.getDrawable(com.android.internal.R.styleable.AnalogClock_dial);
if (mDial == null) {
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index f0eb94f..259c66b 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -133,17 +133,21 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
this(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle);
}
- public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AutoCompleteTextView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
mPopup = new ListPopupWindow(context, attrs,
com.android.internal.R.attr.autoCompleteTextViewStyle);
mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW);
- TypedArray a =
- context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.AutoCompleteTextView, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.AutoCompleteTextView, defStyleAttr, defStyleRes);
mThreshold = a.getInt(
R.styleable.AutoCompleteTextView_completionThreshold, 2);
diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java
index 2ac56ac..1663620 100644
--- a/core/java/android/widget/Button.java
+++ b/core/java/android/widget/Button.java
@@ -103,8 +103,12 @@ public class Button extends TextView {
this(context, attrs, com.android.internal.R.attr.buttonStyle);
}
- public Button(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public Button(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public Button(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java
index 0957ab4..a87c7d2 100644
--- a/core/java/android/widget/CalendarView.java
+++ b/core/java/android/widget/CalendarView.java
@@ -80,234 +80,7 @@ public class CalendarView extends FrameLayout {
*/
private static final String LOG_TAG = CalendarView.class.getSimpleName();
- /**
- * Default value whether to show week number.
- */
- private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true;
-
- /**
- * The number of milliseconds in a day.e
- */
- private static final long MILLIS_IN_DAY = 86400000L;
-
- /**
- * The number of day in a week.
- */
- private static final int DAYS_PER_WEEK = 7;
-
- /**
- * The number of milliseconds in a week.
- */
- private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY;
-
- /**
- * Affects when the month selection will change while scrolling upe
- */
- private static final int SCROLL_HYST_WEEKS = 2;
-
- /**
- * How long the GoTo fling animation should last.
- */
- private static final int GOTO_SCROLL_DURATION = 1000;
-
- /**
- * The duration of the adjustment upon a user scroll in milliseconds.
- */
- private static final int ADJUSTMENT_SCROLL_DURATION = 500;
-
- /**
- * How long to wait after receiving an onScrollStateChanged notification
- * before acting on it.
- */
- private static final int SCROLL_CHANGE_DELAY = 40;
-
- /**
- * String for parsing dates.
- */
- private static final String DATE_FORMAT = "MM/dd/yyyy";
-
- /**
- * The default minimal date.
- */
- private static final String DEFAULT_MIN_DATE = "01/01/1900";
-
- /**
- * The default maximal date.
- */
- private static final String DEFAULT_MAX_DATE = "01/01/2100";
-
- private static final int DEFAULT_SHOWN_WEEK_COUNT = 6;
-
- private static final int DEFAULT_DATE_TEXT_SIZE = 14;
-
- private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6;
-
- private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12;
-
- private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2;
-
- private static final int UNSCALED_BOTTOM_BUFFER = 20;
-
- private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1;
-
- private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1;
-
- private final int mWeekSeperatorLineWidth;
-
- private int mDateTextSize;
-
- private Drawable mSelectedDateVerticalBar;
-
- private final int mSelectedDateVerticalBarWidth;
-
- private int mSelectedWeekBackgroundColor;
-
- private int mFocusedMonthDateColor;
-
- private int mUnfocusedMonthDateColor;
-
- private int mWeekSeparatorLineColor;
-
- private int mWeekNumberColor;
-
- private int mWeekDayTextAppearanceResId;
-
- private int mDateTextAppearanceResId;
-
- /**
- * The top offset of the weeks list.
- */
- private int mListScrollTopOffset = 2;
-
- /**
- * The visible height of a week view.
- */
- private int mWeekMinVisibleHeight = 12;
-
- /**
- * The visible height of a week view.
- */
- private int mBottomBuffer = 20;
-
- /**
- * The number of shown weeks.
- */
- private int mShownWeekCount;
-
- /**
- * Flag whether to show the week number.
- */
- private boolean mShowWeekNumber;
-
- /**
- * The number of day per week to be shown.
- */
- private int mDaysPerWeek = 7;
-
- /**
- * The friction of the week list while flinging.
- */
- private float mFriction = .05f;
-
- /**
- * Scale for adjusting velocity of the week list while flinging.
- */
- private float mVelocityScale = 0.333f;
-
- /**
- * The adapter for the weeks list.
- */
- private WeeksAdapter mAdapter;
-
- /**
- * The weeks list.
- */
- private ListView mListView;
-
- /**
- * The name of the month to display.
- */
- private TextView mMonthName;
-
- /**
- * The header with week day names.
- */
- private ViewGroup mDayNamesHeader;
-
- /**
- * Cached labels for the week names header.
- */
- private String[] mDayLabels;
-
- /**
- * The first day of the week.
- */
- private int mFirstDayOfWeek;
-
- /**
- * Which month should be displayed/highlighted [0-11].
- */
- private int mCurrentMonthDisplayed = -1;
-
- /**
- * Used for tracking during a scroll.
- */
- private long mPreviousScrollPosition;
-
- /**
- * Used for tracking which direction the view is scrolling.
- */
- private boolean mIsScrollingUp = false;
-
- /**
- * The previous scroll state of the weeks ListView.
- */
- private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
-
- /**
- * The current scroll state of the weeks ListView.
- */
- private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
-
- /**
- * Listener for changes in the selected day.
- */
- private OnDateChangeListener mOnDateChangeListener;
-
- /**
- * Command for adjusting the position after a scroll/fling.
- */
- private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
-
- /**
- * Temporary instance to avoid multiple instantiations.
- */
- private Calendar mTempDate;
-
- /**
- * The first day of the focused month.
- */
- private Calendar mFirstDayOfMonth;
-
- /**
- * The start date of the range supported by this picker.
- */
- private Calendar mMinDate;
-
- /**
- * The end date of the range supported by this picker.
- */
- private Calendar mMaxDate;
-
- /**
- * Date format for parsing dates.
- */
- private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
-
- /**
- * The current locale.
- */
- private Locale mCurrentLocale;
+ private CalendarViewDelegate mDelegate;
/**
* The callback used to indicate the user changes the date.
@@ -330,91 +103,17 @@ public class CalendarView extends FrameLayout {
}
public CalendarView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
+ this(context, attrs, R.attr.calendarViewStyle);
}
- public CalendarView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, 0);
-
- // initialization based on locale
- setCurrentLocale(Locale.getDefault());
-
- TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.CalendarView,
- R.attr.calendarViewStyle, 0);
- mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber,
- DEFAULT_SHOW_WEEK_NUMBER);
- mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek,
- LocaleData.get(Locale.getDefault()).firstDayOfWeek);
- String minDate = attributesArray.getString(R.styleable.CalendarView_minDate);
- if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) {
- parseDate(DEFAULT_MIN_DATE, mMinDate);
- }
- String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate);
- if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) {
- parseDate(DEFAULT_MAX_DATE, mMaxDate);
- }
- if (mMaxDate.before(mMinDate)) {
- throw new IllegalArgumentException("Max date cannot be before min date.");
- }
- mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount,
- DEFAULT_SHOWN_WEEK_COUNT);
- mSelectedWeekBackgroundColor = attributesArray.getColor(
- R.styleable.CalendarView_selectedWeekBackgroundColor, 0);
- mFocusedMonthDateColor = attributesArray.getColor(
- R.styleable.CalendarView_focusedMonthDateColor, 0);
- mUnfocusedMonthDateColor = attributesArray.getColor(
- R.styleable.CalendarView_unfocusedMonthDateColor, 0);
- mWeekSeparatorLineColor = attributesArray.getColor(
- R.styleable.CalendarView_weekSeparatorLineColor, 0);
- mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0);
- mSelectedDateVerticalBar = attributesArray.getDrawable(
- R.styleable.CalendarView_selectedDateVerticalBar);
-
- mDateTextAppearanceResId = attributesArray.getResourceId(
- R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small);
- updateDateTextSize();
-
- mWeekDayTextAppearanceResId = attributesArray.getResourceId(
- R.styleable.CalendarView_weekDayTextAppearance,
- DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID);
- attributesArray.recycle();
-
- DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
- mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics);
- mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics);
- mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_BOTTOM_BUFFER, displayMetrics);
- mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics);
- mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics);
-
- LayoutInflater layoutInflater = (LayoutInflater) context
- .getSystemService(Service.LAYOUT_INFLATER_SERVICE);
- View content = layoutInflater.inflate(R.layout.calendar_view, null, false);
- addView(content);
-
- mListView = (ListView) findViewById(R.id.list);
- mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names);
- mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name);
-
- setUpHeader();
- setUpListView();
- setUpAdapter();
-
- // go to today or whichever is close to today min or max date
- mTempDate.setTimeInMillis(System.currentTimeMillis());
- if (mTempDate.before(mMinDate)) {
- goTo(mMinDate, false, true, true);
- } else if (mMaxDate.before(mTempDate)) {
- goTo(mMaxDate, false, true, true);
- } else {
- goTo(mTempDate, false, true, true);
- }
+ public CalendarView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public CalendarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- invalidate();
+ mDelegate = new LegacyCalendarViewDelegate(this, context, attrs, defStyleAttr, defStyleRes);
}
/**
@@ -425,10 +124,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_shownWeekCount
*/
public void setShownWeekCount(int count) {
- if (mShownWeekCount != count) {
- mShownWeekCount = count;
- invalidate();
- }
+ mDelegate.setShownWeekCount(count);
}
/**
@@ -439,7 +135,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_shownWeekCount
*/
public int getShownWeekCount() {
- return mShownWeekCount;
+ return mDelegate.getShownWeekCount();
}
/**
@@ -450,16 +146,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
*/
public void setSelectedWeekBackgroundColor(int color) {
- if (mSelectedWeekBackgroundColor != color) {
- mSelectedWeekBackgroundColor = color;
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- WeekView weekView = (WeekView) mListView.getChildAt(i);
- if (weekView.mHasSelectedDay) {
- weekView.invalidate();
- }
- }
- }
+ mDelegate.setSelectedWeekBackgroundColor(color);
}
/**
@@ -470,7 +157,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_selectedWeekBackgroundColor
*/
public int getSelectedWeekBackgroundColor() {
- return mSelectedWeekBackgroundColor;
+ return mDelegate.getSelectedWeekBackgroundColor();
}
/**
@@ -481,16 +168,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
*/
public void setFocusedMonthDateColor(int color) {
- if (mFocusedMonthDateColor != color) {
- mFocusedMonthDateColor = color;
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- WeekView weekView = (WeekView) mListView.getChildAt(i);
- if (weekView.mHasFocusedDay) {
- weekView.invalidate();
- }
- }
- }
+ mDelegate.setFocusedMonthDateColor(color);
}
/**
@@ -501,7 +179,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_focusedMonthDateColor
*/
public int getFocusedMonthDateColor() {
- return mFocusedMonthDateColor;
+ return mDelegate.getFocusedMonthDateColor();
}
/**
@@ -512,16 +190,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
*/
public void setUnfocusedMonthDateColor(int color) {
- if (mUnfocusedMonthDateColor != color) {
- mUnfocusedMonthDateColor = color;
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- WeekView weekView = (WeekView) mListView.getChildAt(i);
- if (weekView.mHasUnfocusedDay) {
- weekView.invalidate();
- }
- }
- }
+ mDelegate.setUnfocusedMonthDateColor(color);
}
/**
@@ -532,7 +201,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_unfocusedMonthDateColor
*/
public int getUnfocusedMonthDateColor() {
- return mFocusedMonthDateColor;
+ return mDelegate.getUnfocusedMonthDateColor();
}
/**
@@ -543,12 +212,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_weekNumberColor
*/
public void setWeekNumberColor(int color) {
- if (mWeekNumberColor != color) {
- mWeekNumberColor = color;
- if (mShowWeekNumber) {
- invalidateAllWeekViews();
- }
- }
+ mDelegate.setWeekNumberColor(color);
}
/**
@@ -559,7 +223,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_weekNumberColor
*/
public int getWeekNumberColor() {
- return mWeekNumberColor;
+ return mDelegate.getWeekNumberColor();
}
/**
@@ -570,10 +234,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
*/
public void setWeekSeparatorLineColor(int color) {
- if (mWeekSeparatorLineColor != color) {
- mWeekSeparatorLineColor = color;
- invalidateAllWeekViews();
- }
+ mDelegate.setWeekSeparatorLineColor(color);
}
/**
@@ -584,7 +245,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_weekSeparatorLineColor
*/
public int getWeekSeparatorLineColor() {
- return mWeekSeparatorLineColor;
+ return mDelegate.getWeekSeparatorLineColor();
}
/**
@@ -596,8 +257,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
*/
public void setSelectedDateVerticalBar(int resourceId) {
- Drawable drawable = getResources().getDrawable(resourceId);
- setSelectedDateVerticalBar(drawable);
+ mDelegate.setSelectedDateVerticalBar(resourceId);
}
/**
@@ -609,16 +269,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_selectedDateVerticalBar
*/
public void setSelectedDateVerticalBar(Drawable drawable) {
- if (mSelectedDateVerticalBar != drawable) {
- mSelectedDateVerticalBar = drawable;
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- WeekView weekView = (WeekView) mListView.getChildAt(i);
- if (weekView.mHasSelectedDay) {
- weekView.invalidate();
- }
- }
- }
+ mDelegate.setSelectedDateVerticalBar(drawable);
}
/**
@@ -628,7 +279,7 @@ public class CalendarView extends FrameLayout {
* @return The vertical bar drawable.
*/
public Drawable getSelectedDateVerticalBar() {
- return mSelectedDateVerticalBar;
+ return mDelegate.getSelectedDateVerticalBar();
}
/**
@@ -639,10 +290,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
*/
public void setWeekDayTextAppearance(int resourceId) {
- if (mWeekDayTextAppearanceResId != resourceId) {
- mWeekDayTextAppearanceResId = resourceId;
- setUpHeader();
- }
+ mDelegate.setWeekDayTextAppearance(resourceId);
}
/**
@@ -653,7 +301,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_weekDayTextAppearance
*/
public int getWeekDayTextAppearance() {
- return mWeekDayTextAppearanceResId;
+ return mDelegate.getWeekDayTextAppearance();
}
/**
@@ -664,11 +312,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_dateTextAppearance
*/
public void setDateTextAppearance(int resourceId) {
- if (mDateTextAppearanceResId != resourceId) {
- mDateTextAppearanceResId = resourceId;
- updateDateTextSize();
- invalidateAllWeekViews();
- }
+ mDelegate.setDateTextAppearance(resourceId);
}
/**
@@ -679,35 +323,17 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_dateTextAppearance
*/
public int getDateTextAppearance() {
- return mDateTextAppearanceResId;
+ return mDelegate.getDateTextAppearance();
}
@Override
public void setEnabled(boolean enabled) {
- mListView.setEnabled(enabled);
+ mDelegate.setEnabled(enabled);
}
@Override
public boolean isEnabled() {
- return mListView.isEnabled();
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- setCurrentLocale(newConfig.locale);
- }
-
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(CalendarView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(CalendarView.class.getName());
+ return mDelegate.isEnabled();
}
/**
@@ -723,7 +349,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_minDate
*/
public long getMinDate() {
- return mMinDate.getTimeInMillis();
+ return mDelegate.getMinDate();
}
/**
@@ -736,30 +362,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_minDate
*/
public void setMinDate(long minDate) {
- mTempDate.setTimeInMillis(minDate);
- if (isSameDate(mTempDate, mMinDate)) {
- return;
- }
- mMinDate.setTimeInMillis(minDate);
- // make sure the current date is not earlier than
- // the new min date since the latter is used for
- // calculating the indices in the adapter thus
- // avoiding out of bounds error
- Calendar date = mAdapter.mSelectedDate;
- if (date.before(mMinDate)) {
- mAdapter.setSelectedDay(mMinDate);
- }
- // reinitialize the adapter since its range depends on min date
- mAdapter.init();
- if (date.before(mMinDate)) {
- setDate(mTempDate.getTimeInMillis());
- } else {
- // we go to the current date to force the ListView to query its
- // adapter for the shown views since we have changed the adapter
- // range and the base from which the later calculates item indices
- // note that calling setDate will not work since the date is the same
- goTo(date, false, true, false);
- }
+ mDelegate.setMinDate(minDate);
}
/**
@@ -775,7 +378,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_maxDate
*/
public long getMaxDate() {
- return mMaxDate.getTimeInMillis();
+ return mDelegate.getMaxDate();
}
/**
@@ -788,23 +391,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_maxDate
*/
public void setMaxDate(long maxDate) {
- mTempDate.setTimeInMillis(maxDate);
- if (isSameDate(mTempDate, mMaxDate)) {
- return;
- }
- mMaxDate.setTimeInMillis(maxDate);
- // reinitialize the adapter since its range depends on max date
- mAdapter.init();
- Calendar date = mAdapter.mSelectedDate;
- if (date.after(mMaxDate)) {
- setDate(mMaxDate.getTimeInMillis());
- } else {
- // we go to the current date to force the ListView to query its
- // adapter for the shown views since we have changed the adapter
- // range and the base from which the later calculates item indices
- // note that calling setDate will not work since the date is the same
- goTo(date, false, true, false);
- }
+ mDelegate.setMaxDate(maxDate);
}
/**
@@ -815,12 +402,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_showWeekNumber
*/
public void setShowWeekNumber(boolean showWeekNumber) {
- if (mShowWeekNumber == showWeekNumber) {
- return;
- }
- mShowWeekNumber = showWeekNumber;
- mAdapter.notifyDataSetChanged();
- setUpHeader();
+ mDelegate.setShowWeekNumber(showWeekNumber);
}
/**
@@ -831,7 +413,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_showWeekNumber
*/
public boolean getShowWeekNumber() {
- return mShowWeekNumber;
+ return mDelegate.getShowWeekNumber();
}
/**
@@ -850,7 +432,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_firstDayOfWeek
*/
public int getFirstDayOfWeek() {
- return mFirstDayOfWeek;
+ return mDelegate.getFirstDayOfWeek();
}
/**
@@ -869,12 +451,7 @@ public class CalendarView extends FrameLayout {
* @attr ref android.R.styleable#CalendarView_firstDayOfWeek
*/
public void setFirstDayOfWeek(int firstDayOfWeek) {
- if (mFirstDayOfWeek == firstDayOfWeek) {
- return;
- }
- mFirstDayOfWeek = firstDayOfWeek;
- mAdapter.init();
- setUpHeader();
+ mDelegate.setFirstDayOfWeek(firstDayOfWeek);
}
/**
@@ -883,7 +460,7 @@ public class CalendarView extends FrameLayout {
* @param listener The listener to be notified.
*/
public void setOnDateChangeListener(OnDateChangeListener listener) {
- mOnDateChangeListener = listener;
+ mDelegate.setOnDateChangeListener(listener);
}
/**
@@ -893,7 +470,7 @@ public class CalendarView extends FrameLayout {
* @return The selected date.
*/
public long getDate() {
- return mAdapter.mSelectedDate.getTimeInMillis();
+ return mDelegate.getDate();
}
/**
@@ -910,7 +487,7 @@ public class CalendarView extends FrameLayout {
* @see #setMaxDate(long)
*/
public void setDate(long date) {
- setDate(date, false, false);
+ mDelegate.setDate(date);
}
/**
@@ -928,934 +505,1645 @@ public class CalendarView extends FrameLayout {
* @see #setMaxDate(long)
*/
public void setDate(long date, boolean animate, boolean center) {
- mTempDate.setTimeInMillis(date);
- if (isSameDate(mTempDate, mAdapter.mSelectedDate)) {
- return;
- }
- goTo(mTempDate, animate, true, center);
+ mDelegate.setDate(date, animate, center);
}
- private void updateDateTextSize() {
- TypedArray dateTextAppearance = mContext.obtainStyledAttributes(
- mDateTextAppearanceResId, R.styleable.TextAppearance);
- mDateTextSize = dateTextAppearance.getDimensionPixelSize(
- R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE);
- dateTextAppearance.recycle();
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDelegate.onConfigurationChanged(newConfig);
}
- /**
- * Invalidates all week views.
- */
- private void invalidateAllWeekViews() {
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View view = mListView.getChildAt(i);
- view.invalidate();
- }
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ mDelegate.onInitializeAccessibilityEvent(event);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ mDelegate.onInitializeAccessibilityNodeInfo(info);
}
/**
- * Sets the current locale.
- *
- * @param locale The current locale.
+ * A delegate interface that defined the public API of the CalendarView. Allows different
+ * CalendarView implementations. This would need to be implemented by the CalendarView delegates
+ * for the real behavior.
*/
- private void setCurrentLocale(Locale locale) {
- if (locale.equals(mCurrentLocale)) {
- return;
- }
+ private interface CalendarViewDelegate {
+ void setShownWeekCount(int count);
+ int getShownWeekCount();
- mCurrentLocale = locale;
+ void setSelectedWeekBackgroundColor(int color);
+ int getSelectedWeekBackgroundColor();
- mTempDate = getCalendarForLocale(mTempDate, locale);
- mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale);
- mMinDate = getCalendarForLocale(mMinDate, locale);
- mMaxDate = getCalendarForLocale(mMaxDate, locale);
- }
+ void setFocusedMonthDateColor(int color);
+ int getFocusedMonthDateColor();
- /**
- * Gets a calendar for locale bootstrapped with the value of a given calendar.
- *
- * @param oldCalendar The old calendar.
- * @param locale The locale.
- */
- private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
- if (oldCalendar == null) {
- return Calendar.getInstance(locale);
- } else {
- final long currentTimeMillis = oldCalendar.getTimeInMillis();
- Calendar newCalendar = Calendar.getInstance(locale);
- newCalendar.setTimeInMillis(currentTimeMillis);
- return newCalendar;
- }
- }
+ void setUnfocusedMonthDateColor(int color);
+ int getUnfocusedMonthDateColor();
- /**
- * @return True if the <code>firstDate</code> is the same as the <code>
- * secondDate</code>.
- */
- private boolean isSameDate(Calendar firstDate, Calendar secondDate) {
- return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR)
- && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR));
- }
+ void setWeekNumberColor(int color);
+ int getWeekNumberColor();
- /**
- * Creates a new adapter if necessary and sets up its parameters.
- */
- private void setUpAdapter() {
- if (mAdapter == null) {
- mAdapter = new WeeksAdapter();
- mAdapter.registerDataSetObserver(new DataSetObserver() {
- @Override
- public void onChanged() {
- if (mOnDateChangeListener != null) {
- Calendar selectedDay = mAdapter.getSelectedDay();
- mOnDateChangeListener.onSelectedDayChange(CalendarView.this,
- selectedDay.get(Calendar.YEAR),
- selectedDay.get(Calendar.MONTH),
- selectedDay.get(Calendar.DAY_OF_MONTH));
- }
- }
- });
- mListView.setAdapter(mAdapter);
- }
+ void setWeekSeparatorLineColor(int color);
+ int getWeekSeparatorLineColor();
+
+ void setSelectedDateVerticalBar(int resourceId);
+ void setSelectedDateVerticalBar(Drawable drawable);
+ Drawable getSelectedDateVerticalBar();
+
+ void setWeekDayTextAppearance(int resourceId);
+ int getWeekDayTextAppearance();
+
+ void setDateTextAppearance(int resourceId);
+ int getDateTextAppearance();
+
+ void setEnabled(boolean enabled);
+ boolean isEnabled();
+
+ void setMinDate(long minDate);
+ long getMinDate();
+
+ void setMaxDate(long maxDate);
+ long getMaxDate();
+
+ void setShowWeekNumber(boolean showWeekNumber);
+ boolean getShowWeekNumber();
+
+ void setFirstDayOfWeek(int firstDayOfWeek);
+ int getFirstDayOfWeek();
+
+ void setDate(long date);
+ void setDate(long date, boolean animate, boolean center);
+ long getDate();
+
+ void setOnDateChangeListener(OnDateChangeListener listener);
- // refresh the view with the new parameters
- mAdapter.notifyDataSetChanged();
+ void onConfigurationChanged(Configuration newConfig);
+ void onInitializeAccessibilityEvent(AccessibilityEvent event);
+ void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
}
/**
- * Sets up the strings to be used by the header.
+ * An abstract class which can be used as a start for CalendarView implementations
*/
- private void setUpHeader() {
- final String[] tinyWeekdayNames = LocaleData.get(Locale.getDefault()).tinyWeekdayNames;
- mDayLabels = new String[mDaysPerWeek];
- for (int i = 0; i < mDaysPerWeek; i++) {
- final int j = i + mFirstDayOfWeek;
- final int calendarDay = (j > Calendar.SATURDAY) ? j - Calendar.SATURDAY : j;
- mDayLabels[i] = tinyWeekdayNames[calendarDay];
- }
- // Deal with week number
- TextView label = (TextView) mDayNamesHeader.getChildAt(0);
- if (mShowWeekNumber) {
- label.setVisibility(View.VISIBLE);
- } else {
- label.setVisibility(View.GONE);
+ abstract static class AbstractCalendarViewDelegate implements CalendarViewDelegate {
+ // The delegator
+ protected CalendarView mDelegator;
+
+ // The context
+ protected Context mContext;
+
+ // The current locale
+ protected Locale mCurrentLocale;
+
+ AbstractCalendarViewDelegate(CalendarView delegator, Context context) {
+ mDelegator = delegator;
+ mContext = context;
+
+ // Initialization based on locale
+ setCurrentLocale(Locale.getDefault());
}
- // Deal with day labels
- final int count = mDayNamesHeader.getChildCount();
- for (int i = 0; i < count - 1; i++) {
- label = (TextView) mDayNamesHeader.getChildAt(i + 1);
- if (mWeekDayTextAppearanceResId > -1) {
- label.setTextAppearance(mContext, mWeekDayTextAppearanceResId);
- }
- if (i < mDaysPerWeek) {
- label.setText(mDayLabels[i]);
- label.setVisibility(View.VISIBLE);
- } else {
- label.setVisibility(View.GONE);
+
+ protected void setCurrentLocale(Locale locale) {
+ if (locale.equals(mCurrentLocale)) {
+ return;
}
+ mCurrentLocale = locale;
}
- mDayNamesHeader.invalidate();
}
/**
- * Sets all the required fields for the list view.
+ * A delegate implementing the legacy CalendarView
*/
- private void setUpListView() {
- // Configure the listview
- mListView.setDivider(null);
- mListView.setItemsCanFocus(true);
- mListView.setVerticalScrollBarEnabled(false);
- mListView.setOnScrollListener(new OnScrollListener() {
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- CalendarView.this.onScrollStateChanged(view, scrollState);
- }
-
- public void onScroll(
- AbsListView view, int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
- CalendarView.this.onScroll(view, firstVisibleItem, visibleItemCount,
- totalItemCount);
- }
- });
- // Make the scrolling behavior nicer
- mListView.setFriction(mFriction);
- mListView.setVelocityScale(mVelocityScale);
- }
+ private static class LegacyCalendarViewDelegate extends AbstractCalendarViewDelegate {
- /**
- * This moves to the specified time in the view. If the time is not already
- * in range it will move the list so that the first of the month containing
- * the time is at the top of the view. If the new time is already in view
- * the list will not be scrolled unless forceScroll is true. This time may
- * optionally be highlighted as selected as well.
- *
- * @param date The time to move to.
- * @param animate Whether to scroll to the given time or just redraw at the
- * new location.
- * @param setSelected Whether to set the given time as selected.
- * @param forceScroll Whether to recenter even if the time is already
- * visible.
- *
- * @throws IllegalArgumentException of the provided date is before the
- * range start of after the range end.
- */
- private void goTo(Calendar date, boolean animate, boolean setSelected, boolean forceScroll) {
- if (date.before(mMinDate) || date.after(mMaxDate)) {
- throw new IllegalArgumentException("Time not between " + mMinDate.getTime()
- + " and " + mMaxDate.getTime());
- }
- // Find the first and last entirely visible weeks
- int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
- View firstChild = mListView.getChildAt(0);
- if (firstChild != null && firstChild.getTop() < 0) {
- firstFullyVisiblePosition++;
- }
- int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1;
- if (firstChild != null && firstChild.getTop() > mBottomBuffer) {
- lastFullyVisiblePosition--;
- }
- if (setSelected) {
- mAdapter.setSelectedDay(date);
- }
- // Get the week we're going to
- int position = getWeeksSinceMinDate(date);
+ /**
+ * Default value whether to show week number.
+ */
+ private static final boolean DEFAULT_SHOW_WEEK_NUMBER = true;
- // Check if the selected day is now outside of our visible range
- // and if so scroll to the month that contains it
- if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition
- || forceScroll) {
- mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis());
- mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1);
+ /**
+ * The number of milliseconds in a day.e
+ */
+ private static final long MILLIS_IN_DAY = 86400000L;
- setMonthDisplayed(mFirstDayOfMonth);
+ /**
+ * The number of day in a week.
+ */
+ private static final int DAYS_PER_WEEK = 7;
- // the earliest time we can scroll to is the min date
- if (mFirstDayOfMonth.before(mMinDate)) {
- position = 0;
- } else {
- position = getWeeksSinceMinDate(mFirstDayOfMonth);
+ /**
+ * The number of milliseconds in a week.
+ */
+ private static final long MILLIS_IN_WEEK = DAYS_PER_WEEK * MILLIS_IN_DAY;
+
+ /**
+ * Affects when the month selection will change while scrolling upe
+ */
+ private static final int SCROLL_HYST_WEEKS = 2;
+
+ /**
+ * How long the GoTo fling animation should last.
+ */
+ private static final int GOTO_SCROLL_DURATION = 1000;
+
+ /**
+ * The duration of the adjustment upon a user scroll in milliseconds.
+ */
+ private static final int ADJUSTMENT_SCROLL_DURATION = 500;
+
+ /**
+ * How long to wait after receiving an onScrollStateChanged notification
+ * before acting on it.
+ */
+ private static final int SCROLL_CHANGE_DELAY = 40;
+
+ /**
+ * String for parsing dates.
+ */
+ private static final String DATE_FORMAT = "MM/dd/yyyy";
+
+ /**
+ * The default minimal date.
+ */
+ private static final String DEFAULT_MIN_DATE = "01/01/1900";
+
+ /**
+ * The default maximal date.
+ */
+ private static final String DEFAULT_MAX_DATE = "01/01/2100";
+
+ private static final int DEFAULT_SHOWN_WEEK_COUNT = 6;
+
+ private static final int DEFAULT_DATE_TEXT_SIZE = 14;
+
+ private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6;
+
+ private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12;
+
+ private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2;
+
+ private static final int UNSCALED_BOTTOM_BUFFER = 20;
+
+ private static final int UNSCALED_WEEK_SEPARATOR_LINE_WIDTH = 1;
+
+ private static final int DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID = -1;
+
+ private final int mWeekSeperatorLineWidth;
+
+ private int mDateTextSize;
+
+ private Drawable mSelectedDateVerticalBar;
+
+ private final int mSelectedDateVerticalBarWidth;
+
+ private int mSelectedWeekBackgroundColor;
+
+ private int mFocusedMonthDateColor;
+
+ private int mUnfocusedMonthDateColor;
+
+ private int mWeekSeparatorLineColor;
+
+ private int mWeekNumberColor;
+
+ private int mWeekDayTextAppearanceResId;
+
+ private int mDateTextAppearanceResId;
+
+ /**
+ * The top offset of the weeks list.
+ */
+ private int mListScrollTopOffset = 2;
+
+ /**
+ * The visible height of a week view.
+ */
+ private int mWeekMinVisibleHeight = 12;
+
+ /**
+ * The visible height of a week view.
+ */
+ private int mBottomBuffer = 20;
+
+ /**
+ * The number of shown weeks.
+ */
+ private int mShownWeekCount;
+
+ /**
+ * Flag whether to show the week number.
+ */
+ private boolean mShowWeekNumber;
+
+ /**
+ * The number of day per week to be shown.
+ */
+ private int mDaysPerWeek = 7;
+
+ /**
+ * The friction of the week list while flinging.
+ */
+ private float mFriction = .05f;
+
+ /**
+ * Scale for adjusting velocity of the week list while flinging.
+ */
+ private float mVelocityScale = 0.333f;
+
+ /**
+ * The adapter for the weeks list.
+ */
+ private WeeksAdapter mAdapter;
+
+ /**
+ * The weeks list.
+ */
+ private ListView mListView;
+
+ /**
+ * The name of the month to display.
+ */
+ private TextView mMonthName;
+
+ /**
+ * The header with week day names.
+ */
+ private ViewGroup mDayNamesHeader;
+
+ /**
+ * Cached labels for the week names header.
+ */
+ private String[] mDayLabels;
+
+ /**
+ * The first day of the week.
+ */
+ private int mFirstDayOfWeek;
+
+ /**
+ * Which month should be displayed/highlighted [0-11].
+ */
+ private int mCurrentMonthDisplayed = -1;
+
+ /**
+ * Used for tracking during a scroll.
+ */
+ private long mPreviousScrollPosition;
+
+ /**
+ * Used for tracking which direction the view is scrolling.
+ */
+ private boolean mIsScrollingUp = false;
+
+ /**
+ * The previous scroll state of the weeks ListView.
+ */
+ private int mPreviousScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+
+ /**
+ * The current scroll state of the weeks ListView.
+ */
+ private int mCurrentScrollState = OnScrollListener.SCROLL_STATE_IDLE;
+
+ /**
+ * Listener for changes in the selected day.
+ */
+ private OnDateChangeListener mOnDateChangeListener;
+
+ /**
+ * Command for adjusting the position after a scroll/fling.
+ */
+ private ScrollStateRunnable mScrollStateChangedRunnable = new ScrollStateRunnable();
+
+ /**
+ * Temporary instance to avoid multiple instantiations.
+ */
+ private Calendar mTempDate;
+
+ /**
+ * The first day of the focused month.
+ */
+ private Calendar mFirstDayOfMonth;
+
+ /**
+ * The start date of the range supported by this picker.
+ */
+ private Calendar mMinDate;
+
+ /**
+ * The end date of the range supported by this picker.
+ */
+ private Calendar mMaxDate;
+
+ /**
+ * Date format for parsing dates.
+ */
+ private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
+
+ LegacyCalendarViewDelegate(CalendarView delegator, Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(delegator, context);
+
+ // initialization based on locale
+ setCurrentLocale(Locale.getDefault());
+
+ TypedArray attributesArray = context.obtainStyledAttributes(attrs,
+ R.styleable.CalendarView, defStyleAttr, defStyleRes);
+ mShowWeekNumber = attributesArray.getBoolean(R.styleable.CalendarView_showWeekNumber,
+ DEFAULT_SHOW_WEEK_NUMBER);
+ mFirstDayOfWeek = attributesArray.getInt(R.styleable.CalendarView_firstDayOfWeek,
+ LocaleData.get(Locale.getDefault()).firstDayOfWeek);
+ String minDate = attributesArray.getString(R.styleable.CalendarView_minDate);
+ if (TextUtils.isEmpty(minDate) || !parseDate(minDate, mMinDate)) {
+ parseDate(DEFAULT_MIN_DATE, mMinDate);
+ }
+ String maxDate = attributesArray.getString(R.styleable.CalendarView_maxDate);
+ if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, mMaxDate)) {
+ parseDate(DEFAULT_MAX_DATE, mMaxDate);
+ }
+ if (mMaxDate.before(mMinDate)) {
+ throw new IllegalArgumentException("Max date cannot be before min date.");
}
+ mShownWeekCount = attributesArray.getInt(R.styleable.CalendarView_shownWeekCount,
+ DEFAULT_SHOWN_WEEK_COUNT);
+ mSelectedWeekBackgroundColor = attributesArray.getColor(
+ R.styleable.CalendarView_selectedWeekBackgroundColor, 0);
+ mFocusedMonthDateColor = attributesArray.getColor(
+ R.styleable.CalendarView_focusedMonthDateColor, 0);
+ mUnfocusedMonthDateColor = attributesArray.getColor(
+ R.styleable.CalendarView_unfocusedMonthDateColor, 0);
+ mWeekSeparatorLineColor = attributesArray.getColor(
+ R.styleable.CalendarView_weekSeparatorLineColor, 0);
+ mWeekNumberColor = attributesArray.getColor(R.styleable.CalendarView_weekNumberColor, 0);
+ mSelectedDateVerticalBar = attributesArray.getDrawable(
+ R.styleable.CalendarView_selectedDateVerticalBar);
+
+ mDateTextAppearanceResId = attributesArray.getResourceId(
+ R.styleable.CalendarView_dateTextAppearance, R.style.TextAppearance_Small);
+ updateDateTextSize();
+
+ mWeekDayTextAppearanceResId = attributesArray.getResourceId(
+ R.styleable.CalendarView_weekDayTextAppearance,
+ DEFAULT_WEEK_DAY_TEXT_APPEARANCE_RES_ID);
+ attributesArray.recycle();
+
+ DisplayMetrics displayMetrics = mDelegator.getResources().getDisplayMetrics();
+ mWeekMinVisibleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_WEEK_MIN_VISIBLE_HEIGHT, displayMetrics);
+ mListScrollTopOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_LIST_SCROLL_TOP_OFFSET, displayMetrics);
+ mBottomBuffer = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_BOTTOM_BUFFER, displayMetrics);
+ mSelectedDateVerticalBarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH, displayMetrics);
+ mWeekSeperatorLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ UNSCALED_WEEK_SEPARATOR_LINE_WIDTH, displayMetrics);
+
+ LayoutInflater layoutInflater = (LayoutInflater) mContext
+ .getSystemService(Service.LAYOUT_INFLATER_SERVICE);
+ View content = layoutInflater.inflate(R.layout.calendar_view, null, false);
+ mDelegator.addView(content);
+
+ mListView = (ListView) mDelegator.findViewById(R.id.list);
+ mDayNamesHeader = (ViewGroup) content.findViewById(com.android.internal.R.id.day_names);
+ mMonthName = (TextView) content.findViewById(com.android.internal.R.id.month_name);
- mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
- if (animate) {
- mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset,
- GOTO_SCROLL_DURATION);
+ setUpHeader();
+ setUpListView();
+ setUpAdapter();
+
+ // go to today or whichever is close to today min or max date
+ mTempDate.setTimeInMillis(System.currentTimeMillis());
+ if (mTempDate.before(mMinDate)) {
+ goTo(mMinDate, false, true, true);
+ } else if (mMaxDate.before(mTempDate)) {
+ goTo(mMaxDate, false, true, true);
} else {
- mListView.setSelectionFromTop(position, mListScrollTopOffset);
- // Perform any after scroll operations that are needed
- onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE);
+ goTo(mTempDate, false, true, true);
}
- } else if (setSelected) {
- // Otherwise just set the selection
- setMonthDisplayed(date);
+
+ mDelegator.invalidate();
}
- }
- /**
- * Parses the given <code>date</code> and in case of success sets
- * the result to the <code>outDate</code>.
- *
- * @return True if the date was parsed.
- */
- private boolean parseDate(String date, Calendar outDate) {
- try {
- outDate.setTime(mDateFormat.parse(date));
- return true;
- } catch (ParseException e) {
- Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
- return false;
+ @Override
+ public void setShownWeekCount(int count) {
+ if (mShownWeekCount != count) {
+ mShownWeekCount = count;
+ mDelegator.invalidate();
+ }
}
- }
- /**
- * Called when a <code>view</code> transitions to a new <code>scrollState
- * </code>.
- */
- private void onScrollStateChanged(AbsListView view, int scrollState) {
- mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
- }
+ @Override
+ public int getShownWeekCount() {
+ return mShownWeekCount;
+ }
- /**
- * Updates the title and selected month if the <code>view</code> has moved to a new
- * month.
- */
- private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
- WeekView child = (WeekView) view.getChildAt(0);
- if (child == null) {
- return;
+ @Override
+ public void setSelectedWeekBackgroundColor(int color) {
+ if (mSelectedWeekBackgroundColor != color) {
+ mSelectedWeekBackgroundColor = color;
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ WeekView weekView = (WeekView) mListView.getChildAt(i);
+ if (weekView.mHasSelectedDay) {
+ weekView.invalidate();
+ }
+ }
+ }
}
- // Figure out where we are
- long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
+ @Override
+ public int getSelectedWeekBackgroundColor() {
+ return mSelectedWeekBackgroundColor;
+ }
- // If we have moved since our last call update the direction
- if (currScroll < mPreviousScrollPosition) {
- mIsScrollingUp = true;
- } else if (currScroll > mPreviousScrollPosition) {
- mIsScrollingUp = false;
- } else {
- return;
+ @Override
+ public void setFocusedMonthDateColor(int color) {
+ if (mFocusedMonthDateColor != color) {
+ mFocusedMonthDateColor = color;
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ WeekView weekView = (WeekView) mListView.getChildAt(i);
+ if (weekView.mHasFocusedDay) {
+ weekView.invalidate();
+ }
+ }
+ }
}
- // Use some hysteresis for checking which month to highlight. This
- // causes the month to transition when two full weeks of a month are
- // visible when scrolling up, and when the first day in a month reaches
- // the top of the screen when scrolling down.
- int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0;
- if (mIsScrollingUp) {
- child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
- } else if (offset != 0) {
- child = (WeekView) view.getChildAt(offset);
+ @Override
+ public int getFocusedMonthDateColor() {
+ return mFocusedMonthDateColor;
}
- // Find out which month we're moving into
- int month;
- if (mIsScrollingUp) {
- month = child.getMonthOfFirstWeekDay();
- } else {
- month = child.getMonthOfLastWeekDay();
+ @Override
+ public void setUnfocusedMonthDateColor(int color) {
+ if (mUnfocusedMonthDateColor != color) {
+ mUnfocusedMonthDateColor = color;
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ WeekView weekView = (WeekView) mListView.getChildAt(i);
+ if (weekView.mHasUnfocusedDay) {
+ weekView.invalidate();
+ }
+ }
+ }
}
- // And how it relates to our current highlighted month
- int monthDiff;
- if (mCurrentMonthDisplayed == 11 && month == 0) {
- monthDiff = 1;
- } else if (mCurrentMonthDisplayed == 0 && month == 11) {
- monthDiff = -1;
- } else {
- monthDiff = month - mCurrentMonthDisplayed;
+ @Override
+ public int getUnfocusedMonthDateColor() {
+ return mFocusedMonthDateColor;
}
- // Only switch months if we're scrolling away from the currently
- // selected month
- if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
- Calendar firstDay = child.getFirstDay();
- if (mIsScrollingUp) {
- firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
- } else {
- firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
+ @Override
+ public void setWeekNumberColor(int color) {
+ if (mWeekNumberColor != color) {
+ mWeekNumberColor = color;
+ if (mShowWeekNumber) {
+ invalidateAllWeekViews();
+ }
}
- setMonthDisplayed(firstDay);
}
- mPreviousScrollPosition = currScroll;
- mPreviousScrollState = mCurrentScrollState;
- }
- /**
- * Sets the month displayed at the top of this view based on time. Override
- * to add custom events when the title is changed.
- *
- * @param calendar A day in the new focus month.
- */
- private void setMonthDisplayed(Calendar calendar) {
- mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
- mAdapter.setFocusMonth(mCurrentMonthDisplayed);
- final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
- | DateUtils.FORMAT_SHOW_YEAR;
- final long millis = calendar.getTimeInMillis();
- String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
- mMonthName.setText(newMonthName);
- mMonthName.invalidate();
- }
-
- /**
- * @return Returns the number of weeks between the current <code>date</code>
- * and the <code>mMinDate</code>.
- */
- private int getWeeksSinceMinDate(Calendar date) {
- if (date.before(mMinDate)) {
- throw new IllegalArgumentException("fromDate: " + mMinDate.getTime()
- + " does not precede toDate: " + date.getTime());
+ @Override
+ public int getWeekNumberColor() {
+ return mWeekNumberColor;
}
- long endTimeMillis = date.getTimeInMillis()
- + date.getTimeZone().getOffset(date.getTimeInMillis());
- long startTimeMillis = mMinDate.getTimeInMillis()
- + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis());
- long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek)
- * MILLIS_IN_DAY;
- return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK);
- }
- /**
- * Command responsible for acting upon scroll state changes.
- */
- private class ScrollStateRunnable implements Runnable {
- private AbsListView mView;
+ @Override
+ public void setWeekSeparatorLineColor(int color) {
+ if (mWeekSeparatorLineColor != color) {
+ mWeekSeparatorLineColor = color;
+ invalidateAllWeekViews();
+ }
+ }
- private int mNewState;
+ @Override
+ public int getWeekSeparatorLineColor() {
+ return mWeekSeparatorLineColor;
+ }
- /**
- * Sets up the runnable with a short delay in case the scroll state
- * immediately changes again.
- *
- * @param view The list view that changed state
- * @param scrollState The new state it changed to
- */
- public void doScrollStateChange(AbsListView view, int scrollState) {
- mView = view;
- mNewState = scrollState;
- removeCallbacks(this);
- postDelayed(this, SCROLL_CHANGE_DELAY);
+ @Override
+ public void setSelectedDateVerticalBar(int resourceId) {
+ Drawable drawable = mDelegator.getResources().getDrawable(resourceId);
+ setSelectedDateVerticalBar(drawable);
}
- public void run() {
- mCurrentScrollState = mNewState;
- // Fix the position after a scroll or a fling ends
- if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
- && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
- View child = mView.getChildAt(0);
- if (child == null) {
- // The view is no longer visible, just return
- return;
- }
- int dist = child.getBottom() - mListScrollTopOffset;
- if (dist > mListScrollTopOffset) {
- if (mIsScrollingUp) {
- mView.smoothScrollBy(dist - child.getHeight(), ADJUSTMENT_SCROLL_DURATION);
- } else {
- mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION);
+ @Override
+ public void setSelectedDateVerticalBar(Drawable drawable) {
+ if (mSelectedDateVerticalBar != drawable) {
+ mSelectedDateVerticalBar = drawable;
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ WeekView weekView = (WeekView) mListView.getChildAt(i);
+ if (weekView.mHasSelectedDay) {
+ weekView.invalidate();
}
}
}
- mPreviousScrollState = mNewState;
}
- }
- /**
- * <p>
- * This is a specialized adapter for creating a list of weeks with
- * selectable days. It can be configured to display the week number, start
- * the week on a given day, show a reduced number of days, or display an
- * arbitrary number of weeks at a time.
- * </p>
- */
- private class WeeksAdapter extends BaseAdapter implements OnTouchListener {
- private final Calendar mSelectedDate = Calendar.getInstance();
- private final GestureDetector mGestureDetector;
+ @Override
+ public Drawable getSelectedDateVerticalBar() {
+ return mSelectedDateVerticalBar;
+ }
- private int mSelectedWeek;
+ @Override
+ public void setWeekDayTextAppearance(int resourceId) {
+ if (mWeekDayTextAppearanceResId != resourceId) {
+ mWeekDayTextAppearanceResId = resourceId;
+ setUpHeader();
+ }
+ }
- private int mFocusedMonth;
+ @Override
+ public int getWeekDayTextAppearance() {
+ return mWeekDayTextAppearanceResId;
+ }
- private int mTotalWeekCount;
+ @Override
+ public void setDateTextAppearance(int resourceId) {
+ if (mDateTextAppearanceResId != resourceId) {
+ mDateTextAppearanceResId = resourceId;
+ updateDateTextSize();
+ invalidateAllWeekViews();
+ }
+ }
- public WeeksAdapter() {
- mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
- init();
+ @Override
+ public int getDateTextAppearance() {
+ return mDateTextAppearanceResId;
}
- /**
- * Set up the gesture detector and selected time
- */
- private void init() {
- mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
- mTotalWeekCount = getWeeksSinceMinDate(mMaxDate);
- if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek
- || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) {
- mTotalWeekCount++;
- }
- notifyDataSetChanged();
+ @Override
+ public void setEnabled(boolean enabled) {
+ mListView.setEnabled(enabled);
}
- /**
- * Updates the selected day and related parameters.
- *
- * @param selectedDay The time to highlight
- */
- public void setSelectedDay(Calendar selectedDay) {
- if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR)
- && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) {
+ @Override
+ public boolean isEnabled() {
+ return mListView.isEnabled();
+ }
+
+ @Override
+ public void setMinDate(long minDate) {
+ mTempDate.setTimeInMillis(minDate);
+ if (isSameDate(mTempDate, mMinDate)) {
return;
}
- mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis());
- mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
- mFocusedMonth = mSelectedDate.get(Calendar.MONTH);
- notifyDataSetChanged();
+ mMinDate.setTimeInMillis(minDate);
+ // make sure the current date is not earlier than
+ // the new min date since the latter is used for
+ // calculating the indices in the adapter thus
+ // avoiding out of bounds error
+ Calendar date = mAdapter.mSelectedDate;
+ if (date.before(mMinDate)) {
+ mAdapter.setSelectedDay(mMinDate);
+ }
+ // reinitialize the adapter since its range depends on min date
+ mAdapter.init();
+ if (date.before(mMinDate)) {
+ setDate(mTempDate.getTimeInMillis());
+ } else {
+ // we go to the current date to force the ListView to query its
+ // adapter for the shown views since we have changed the adapter
+ // range and the base from which the later calculates item indices
+ // note that calling setDate will not work since the date is the same
+ goTo(date, false, true, false);
+ }
}
- /**
- * @return The selected day of month.
- */
- public Calendar getSelectedDay() {
- return mSelectedDate;
+ @Override
+ public long getMinDate() {
+ return mMinDate.getTimeInMillis();
}
@Override
- public int getCount() {
- return mTotalWeekCount;
+ public void setMaxDate(long maxDate) {
+ mTempDate.setTimeInMillis(maxDate);
+ if (isSameDate(mTempDate, mMaxDate)) {
+ return;
+ }
+ mMaxDate.setTimeInMillis(maxDate);
+ // reinitialize the adapter since its range depends on max date
+ mAdapter.init();
+ Calendar date = mAdapter.mSelectedDate;
+ if (date.after(mMaxDate)) {
+ setDate(mMaxDate.getTimeInMillis());
+ } else {
+ // we go to the current date to force the ListView to query its
+ // adapter for the shown views since we have changed the adapter
+ // range and the base from which the later calculates item indices
+ // note that calling setDate will not work since the date is the same
+ goTo(date, false, true, false);
+ }
}
@Override
- public Object getItem(int position) {
- return null;
+ public long getMaxDate() {
+ return mMaxDate.getTimeInMillis();
}
@Override
- public long getItemId(int position) {
- return position;
+ public void setShowWeekNumber(boolean showWeekNumber) {
+ if (mShowWeekNumber == showWeekNumber) {
+ return;
+ }
+ mShowWeekNumber = showWeekNumber;
+ mAdapter.notifyDataSetChanged();
+ setUpHeader();
}
@Override
- public View getView(int position, View convertView, ViewGroup parent) {
- WeekView weekView = null;
- if (convertView != null) {
- weekView = (WeekView) convertView;
- } else {
- weekView = new WeekView(mContext);
- android.widget.AbsListView.LayoutParams params =
- new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
- weekView.setLayoutParams(params);
- weekView.setClickable(true);
- weekView.setOnTouchListener(this);
+ public boolean getShowWeekNumber() {
+ return mShowWeekNumber;
+ }
+
+ @Override
+ public void setFirstDayOfWeek(int firstDayOfWeek) {
+ if (mFirstDayOfWeek == firstDayOfWeek) {
+ return;
}
+ mFirstDayOfWeek = firstDayOfWeek;
+ mAdapter.init();
+ mAdapter.notifyDataSetChanged();
+ setUpHeader();
+ }
- int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
- Calendar.DAY_OF_WEEK) : -1;
- weekView.init(position, selectedWeekDay, mFocusedMonth);
+ @Override
+ public int getFirstDayOfWeek() {
+ return mFirstDayOfWeek;
+ }
- return weekView;
+ @Override
+ public void setDate(long date) {
+ setDate(date, false, false);
}
- /**
- * Changes which month is in focus and updates the view.
- *
- * @param month The month to show as in focus [0-11]
- */
- public void setFocusMonth(int month) {
- if (mFocusedMonth == month) {
+ @Override
+ public void setDate(long date, boolean animate, boolean center) {
+ mTempDate.setTimeInMillis(date);
+ if (isSameDate(mTempDate, mAdapter.mSelectedDate)) {
return;
}
- mFocusedMonth = month;
- notifyDataSetChanged();
+ goTo(mTempDate, animate, true, center);
}
@Override
- public boolean onTouch(View v, MotionEvent event) {
- if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) {
- WeekView weekView = (WeekView) v;
- // if we cannot find a day for the given location we are done
- if (!weekView.getDayFromLocation(event.getX(), mTempDate)) {
- return true;
- }
- // it is possible that the touched day is outside the valid range
- // we draw whole weeks but range end can fall not on the week end
- if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
- return true;
- }
- onDateTapped(mTempDate);
- return true;
- }
- return false;
+ public long getDate() {
+ return mAdapter.mSelectedDate.getTimeInMillis();
+ }
+
+ @Override
+ public void setOnDateChangeListener(OnDateChangeListener listener) {
+ mOnDateChangeListener = listener;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ setCurrentLocale(newConfig.locale);
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ event.setClassName(CalendarView.class.getName());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ info.setClassName(CalendarView.class.getName());
}
/**
- * Maintains the same hour/min/sec but moves the day to the tapped day.
+ * Sets the current locale.
*
- * @param day The day that was tapped
+ * @param locale The current locale.
*/
- private void onDateTapped(Calendar day) {
- setSelectedDay(day);
- setMonthDisplayed(day);
+ @Override
+ protected void setCurrentLocale(Locale locale) {
+ super.setCurrentLocale(locale);
+
+ mTempDate = getCalendarForLocale(mTempDate, locale);
+ mFirstDayOfMonth = getCalendarForLocale(mFirstDayOfMonth, locale);
+ mMinDate = getCalendarForLocale(mMinDate, locale);
+ mMaxDate = getCalendarForLocale(mMaxDate, locale);
+ }
+ private void updateDateTextSize() {
+ TypedArray dateTextAppearance = mDelegator.getContext().obtainStyledAttributes(
+ mDateTextAppearanceResId, R.styleable.TextAppearance);
+ mDateTextSize = dateTextAppearance.getDimensionPixelSize(
+ R.styleable.TextAppearance_textSize, DEFAULT_DATE_TEXT_SIZE);
+ dateTextAppearance.recycle();
}
/**
- * This is here so we can identify single tap events and set the
- * selected day correctly
+ * Invalidates all week views.
*/
- class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- return true;
+ private void invalidateAllWeekViews() {
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View view = mListView.getChildAt(i);
+ view.invalidate();
}
}
- }
- /**
- * <p>
- * This is a dynamic view for drawing a single week. It can be configured to
- * display the week number, start the week on a given day, or show a reduced
- * number of days. It is intended for use as a single view within a
- * ListView. See {@link WeeksAdapter} for usage.
- * </p>
- */
- private class WeekView extends View {
+ /**
+ * Gets a calendar for locale bootstrapped with the value of a given calendar.
+ *
+ * @param oldCalendar The old calendar.
+ * @param locale The locale.
+ */
+ private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
+ if (oldCalendar == null) {
+ return Calendar.getInstance(locale);
+ } else {
+ final long currentTimeMillis = oldCalendar.getTimeInMillis();
+ Calendar newCalendar = Calendar.getInstance(locale);
+ newCalendar.setTimeInMillis(currentTimeMillis);
+ return newCalendar;
+ }
+ }
- private final Rect mTempRect = new Rect();
+ /**
+ * @return True if the <code>firstDate</code> is the same as the <code>
+ * secondDate</code>.
+ */
+ private static boolean isSameDate(Calendar firstDate, Calendar secondDate) {
+ return (firstDate.get(Calendar.DAY_OF_YEAR) == secondDate.get(Calendar.DAY_OF_YEAR)
+ && firstDate.get(Calendar.YEAR) == secondDate.get(Calendar.YEAR));
+ }
- private final Paint mDrawPaint = new Paint();
+ /**
+ * Creates a new adapter if necessary and sets up its parameters.
+ */
+ private void setUpAdapter() {
+ if (mAdapter == null) {
+ mAdapter = new WeeksAdapter(mContext);
+ mAdapter.registerDataSetObserver(new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ if (mOnDateChangeListener != null) {
+ Calendar selectedDay = mAdapter.getSelectedDay();
+ mOnDateChangeListener.onSelectedDayChange(mDelegator,
+ selectedDay.get(Calendar.YEAR),
+ selectedDay.get(Calendar.MONTH),
+ selectedDay.get(Calendar.DAY_OF_MONTH));
+ }
+ }
+ });
+ mListView.setAdapter(mAdapter);
+ }
- private final Paint mMonthNumDrawPaint = new Paint();
+ // refresh the view with the new parameters
+ mAdapter.notifyDataSetChanged();
+ }
- // Cache the number strings so we don't have to recompute them each time
- private String[] mDayNumbers;
+ /**
+ * Sets up the strings to be used by the header.
+ */
+ private void setUpHeader() {
+ mDayLabels = new String[mDaysPerWeek];
+ for (int i = mFirstDayOfWeek, count = mFirstDayOfWeek + mDaysPerWeek; i < count; i++) {
+ int calendarDay = (i > Calendar.SATURDAY) ? i - Calendar.SATURDAY : i;
+ mDayLabels[i - mFirstDayOfWeek] = DateUtils.getDayOfWeekString(calendarDay,
+ DateUtils.LENGTH_SHORTEST);
+ }
- // Quick lookup for checking which days are in the focus month
- private boolean[] mFocusDay;
+ TextView label = (TextView) mDayNamesHeader.getChildAt(0);
+ if (mShowWeekNumber) {
+ label.setVisibility(View.VISIBLE);
+ } else {
+ label.setVisibility(View.GONE);
+ }
+ for (int i = 1, count = mDayNamesHeader.getChildCount(); i < count; i++) {
+ label = (TextView) mDayNamesHeader.getChildAt(i);
+ if (mWeekDayTextAppearanceResId > -1) {
+ label.setTextAppearance(mContext, mWeekDayTextAppearanceResId);
+ }
+ if (i < mDaysPerWeek + 1) {
+ label.setText(mDayLabels[i - 1]);
+ label.setVisibility(View.VISIBLE);
+ } else {
+ label.setVisibility(View.GONE);
+ }
+ }
+ mDayNamesHeader.invalidate();
+ }
- // Whether this view has a focused day.
- private boolean mHasFocusedDay;
+ /**
+ * Sets all the required fields for the list view.
+ */
+ private void setUpListView() {
+ // Configure the listview
+ mListView.setDivider(null);
+ mListView.setItemsCanFocus(true);
+ mListView.setVerticalScrollBarEnabled(false);
+ mListView.setOnScrollListener(new OnScrollListener() {
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ LegacyCalendarViewDelegate.this.onScrollStateChanged(view, scrollState);
+ }
- // Whether this view has only focused days.
- private boolean mHasUnfocusedDay;
+ public void onScroll(
+ AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ LegacyCalendarViewDelegate.this.onScroll(view, firstVisibleItem,
+ visibleItemCount, totalItemCount);
+ }
+ });
+ // Make the scrolling behavior nicer
+ mListView.setFriction(mFriction);
+ mListView.setVelocityScale(mVelocityScale);
+ }
- // The first day displayed by this item
- private Calendar mFirstDay;
+ /**
+ * This moves to the specified time in the view. If the time is not already
+ * in range it will move the list so that the first of the month containing
+ * the time is at the top of the view. If the new time is already in view
+ * the list will not be scrolled unless forceScroll is true. This time may
+ * optionally be highlighted as selected as well.
+ *
+ * @param date The time to move to.
+ * @param animate Whether to scroll to the given time or just redraw at the
+ * new location.
+ * @param setSelected Whether to set the given time as selected.
+ * @param forceScroll Whether to recenter even if the time is already
+ * visible.
+ *
+ * @throws IllegalArgumentException of the provided date is before the
+ * range start of after the range end.
+ */
+ private void goTo(Calendar date, boolean animate, boolean setSelected,
+ boolean forceScroll) {
+ if (date.before(mMinDate) || date.after(mMaxDate)) {
+ throw new IllegalArgumentException("Time not between " + mMinDate.getTime()
+ + " and " + mMaxDate.getTime());
+ }
+ // Find the first and last entirely visible weeks
+ int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
+ View firstChild = mListView.getChildAt(0);
+ if (firstChild != null && firstChild.getTop() < 0) {
+ firstFullyVisiblePosition++;
+ }
+ int lastFullyVisiblePosition = firstFullyVisiblePosition + mShownWeekCount - 1;
+ if (firstChild != null && firstChild.getTop() > mBottomBuffer) {
+ lastFullyVisiblePosition--;
+ }
+ if (setSelected) {
+ mAdapter.setSelectedDay(date);
+ }
+ // Get the week we're going to
+ int position = getWeeksSinceMinDate(date);
- // The month of the first day in this week
- private int mMonthOfFirstWeekDay = -1;
+ // Check if the selected day is now outside of our visible range
+ // and if so scroll to the month that contains it
+ if (position < firstFullyVisiblePosition || position > lastFullyVisiblePosition
+ || forceScroll) {
+ mFirstDayOfMonth.setTimeInMillis(date.getTimeInMillis());
+ mFirstDayOfMonth.set(Calendar.DAY_OF_MONTH, 1);
- // The month of the last day in this week
- private int mLastWeekDayMonth = -1;
+ setMonthDisplayed(mFirstDayOfMonth);
- // The position of this week, equivalent to weeks since the week of Jan
- // 1st, 1900
- private int mWeek = -1;
+ // the earliest time we can scroll to is the min date
+ if (mFirstDayOfMonth.before(mMinDate)) {
+ position = 0;
+ } else {
+ position = getWeeksSinceMinDate(mFirstDayOfMonth);
+ }
- // Quick reference to the width of this view, matches parent
- private int mWidth;
+ mPreviousScrollState = OnScrollListener.SCROLL_STATE_FLING;
+ if (animate) {
+ mListView.smoothScrollToPositionFromTop(position, mListScrollTopOffset,
+ GOTO_SCROLL_DURATION);
+ } else {
+ mListView.setSelectionFromTop(position, mListScrollTopOffset);
+ // Perform any after scroll operations that are needed
+ onScrollStateChanged(mListView, OnScrollListener.SCROLL_STATE_IDLE);
+ }
+ } else if (setSelected) {
+ // Otherwise just set the selection
+ setMonthDisplayed(date);
+ }
+ }
- // The height this view should draw at in pixels, set by height param
- private int mHeight;
+ /**
+ * Parses the given <code>date</code> and in case of success sets
+ * the result to the <code>outDate</code>.
+ *
+ * @return True if the date was parsed.
+ */
+ private boolean parseDate(String date, Calendar outDate) {
+ try {
+ outDate.setTime(mDateFormat.parse(date));
+ return true;
+ } catch (ParseException e) {
+ Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
+ return false;
+ }
+ }
- // If this view contains the selected day
- private boolean mHasSelectedDay = false;
+ /**
+ * Called when a <code>view</code> transitions to a new <code>scrollState
+ * </code>.
+ */
+ private void onScrollStateChanged(AbsListView view, int scrollState) {
+ mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
+ }
- // Which day is selected [0-6] or -1 if no day is selected
- private int mSelectedDay = -1;
+ /**
+ * Updates the title and selected month if the <code>view</code> has moved to a new
+ * month.
+ */
+ private void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ WeekView child = (WeekView) view.getChildAt(0);
+ if (child == null) {
+ return;
+ }
- // The number of days + a spot for week number if it is displayed
- private int mNumCells;
+ // Figure out where we are
+ long currScroll =
+ view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
- // The left edge of the selected day
- private int mSelectedLeft = -1;
+ // If we have moved since our last call update the direction
+ if (currScroll < mPreviousScrollPosition) {
+ mIsScrollingUp = true;
+ } else if (currScroll > mPreviousScrollPosition) {
+ mIsScrollingUp = false;
+ } else {
+ return;
+ }
- // The right edge of the selected day
- private int mSelectedRight = -1;
+ // Use some hysteresis for checking which month to highlight. This
+ // causes the month to transition when two full weeks of a month are
+ // visible when scrolling up, and when the first day in a month reaches
+ // the top of the screen when scrolling down.
+ int offset = child.getBottom() < mWeekMinVisibleHeight ? 1 : 0;
+ if (mIsScrollingUp) {
+ child = (WeekView) view.getChildAt(SCROLL_HYST_WEEKS + offset);
+ } else if (offset != 0) {
+ child = (WeekView) view.getChildAt(offset);
+ }
- public WeekView(Context context) {
- super(context);
+ // Find out which month we're moving into
+ int month;
+ if (mIsScrollingUp) {
+ month = child.getMonthOfFirstWeekDay();
+ } else {
+ month = child.getMonthOfLastWeekDay();
+ }
- // Sets up any standard paints that will be used
- initilaizePaints();
- }
+ // And how it relates to our current highlighted month
+ int monthDiff;
+ if (mCurrentMonthDisplayed == 11 && month == 0) {
+ monthDiff = 1;
+ } else if (mCurrentMonthDisplayed == 0 && month == 11) {
+ monthDiff = -1;
+ } else {
+ monthDiff = month - mCurrentMonthDisplayed;
+ }
- /**
- * Initializes this week view.
- *
- * @param weekNumber The number of the week this view represents. The
- * week number is a zero based index of the weeks since
- * {@link CalendarView#getMinDate()}.
- * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no
- * selected day.
- * @param focusedMonth The month that is currently in focus i.e.
- * highlighted.
- */
- public void init(int weekNumber, int selectedWeekDay, int focusedMonth) {
- mSelectedDay = selectedWeekDay;
- mHasSelectedDay = mSelectedDay != -1;
- mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek;
- mWeek = weekNumber;
- mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
-
- mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
- mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
-
- // Allocate space for caching the day numbers and focus values
- mDayNumbers = new String[mNumCells];
- mFocusDay = new boolean[mNumCells];
-
- // If we're showing the week number calculate it based on Monday
- int i = 0;
- if (mShowWeekNumber) {
- mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
- mTempDate.get(Calendar.WEEK_OF_YEAR));
- i++;
- }
-
- // Now adjust our starting day based on the start day of the week
- int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK);
- mTempDate.add(Calendar.DAY_OF_MONTH, diff);
-
- mFirstDay = (Calendar) mTempDate.clone();
- mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH);
-
- mHasUnfocusedDay = true;
- for (; i < mNumCells; i++) {
- final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth);
- mFocusDay[i] = isFocusedDay;
- mHasFocusedDay |= isFocusedDay;
- mHasUnfocusedDay &= !isFocusedDay;
- // do not draw dates outside the valid range to avoid user confusion
- if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
- mDayNumbers[i] = "";
+ // Only switch months if we're scrolling away from the currently
+ // selected month
+ if ((!mIsScrollingUp && monthDiff > 0) || (mIsScrollingUp && monthDiff < 0)) {
+ Calendar firstDay = child.getFirstDay();
+ if (mIsScrollingUp) {
+ firstDay.add(Calendar.DAY_OF_MONTH, -DAYS_PER_WEEK);
} else {
- mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
- mTempDate.get(Calendar.DAY_OF_MONTH));
+ firstDay.add(Calendar.DAY_OF_MONTH, DAYS_PER_WEEK);
}
- mTempDate.add(Calendar.DAY_OF_MONTH, 1);
- }
- // We do one extra add at the end of the loop, if that pushed us to
- // new month undo it
- if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) {
- mTempDate.add(Calendar.DAY_OF_MONTH, -1);
+ setMonthDisplayed(firstDay);
}
- mLastWeekDayMonth = mTempDate.get(Calendar.MONTH);
-
- updateSelectionPositions();
+ mPreviousScrollPosition = currScroll;
+ mPreviousScrollState = mCurrentScrollState;
}
/**
- * Initialize the paint instances.
- */
- private void initilaizePaints() {
- mDrawPaint.setFakeBoldText(false);
- mDrawPaint.setAntiAlias(true);
- mDrawPaint.setStyle(Style.FILL);
-
- mMonthNumDrawPaint.setFakeBoldText(true);
- mMonthNumDrawPaint.setAntiAlias(true);
- mMonthNumDrawPaint.setStyle(Style.FILL);
- mMonthNumDrawPaint.setTextAlign(Align.CENTER);
- mMonthNumDrawPaint.setTextSize(mDateTextSize);
- }
-
- /**
- * Returns the month of the first day in this week.
+ * Sets the month displayed at the top of this view based on time. Override
+ * to add custom events when the title is changed.
*
- * @return The month the first day of this view is in.
+ * @param calendar A day in the new focus month.
*/
- public int getMonthOfFirstWeekDay() {
- return mMonthOfFirstWeekDay;
+ private void setMonthDisplayed(Calendar calendar) {
+ mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
+ mAdapter.setFocusMonth(mCurrentMonthDisplayed);
+ final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
+ | DateUtils.FORMAT_SHOW_YEAR;
+ final long millis = calendar.getTimeInMillis();
+ String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
+ mMonthName.setText(newMonthName);
+ mMonthName.invalidate();
}
/**
- * Returns the month of the last day in this week
- *
- * @return The month the last day of this view is in
+ * @return Returns the number of weeks between the current <code>date</code>
+ * and the <code>mMinDate</code>.
*/
- public int getMonthOfLastWeekDay() {
- return mLastWeekDayMonth;
+ private int getWeeksSinceMinDate(Calendar date) {
+ if (date.before(mMinDate)) {
+ throw new IllegalArgumentException("fromDate: " + mMinDate.getTime()
+ + " does not precede toDate: " + date.getTime());
+ }
+ long endTimeMillis = date.getTimeInMillis()
+ + date.getTimeZone().getOffset(date.getTimeInMillis());
+ long startTimeMillis = mMinDate.getTimeInMillis()
+ + mMinDate.getTimeZone().getOffset(mMinDate.getTimeInMillis());
+ long dayOffsetMillis = (mMinDate.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek)
+ * MILLIS_IN_DAY;
+ return (int) ((endTimeMillis - startTimeMillis + dayOffsetMillis) / MILLIS_IN_WEEK);
}
/**
- * Returns the first day in this view.
- *
- * @return The first day in the view.
+ * Command responsible for acting upon scroll state changes.
*/
- public Calendar getFirstDay() {
- return mFirstDay;
+ private class ScrollStateRunnable implements Runnable {
+ private AbsListView mView;
+
+ private int mNewState;
+
+ /**
+ * Sets up the runnable with a short delay in case the scroll state
+ * immediately changes again.
+ *
+ * @param view The list view that changed state
+ * @param scrollState The new state it changed to
+ */
+ public void doScrollStateChange(AbsListView view, int scrollState) {
+ mView = view;
+ mNewState = scrollState;
+ mDelegator.removeCallbacks(this);
+ mDelegator.postDelayed(this, SCROLL_CHANGE_DELAY);
+ }
+
+ public void run() {
+ mCurrentScrollState = mNewState;
+ // Fix the position after a scroll or a fling ends
+ if (mNewState == OnScrollListener.SCROLL_STATE_IDLE
+ && mPreviousScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
+ View child = mView.getChildAt(0);
+ if (child == null) {
+ // The view is no longer visible, just return
+ return;
+ }
+ int dist = child.getBottom() - mListScrollTopOffset;
+ if (dist > mListScrollTopOffset) {
+ if (mIsScrollingUp) {
+ mView.smoothScrollBy(dist - child.getHeight(),
+ ADJUSTMENT_SCROLL_DURATION);
+ } else {
+ mView.smoothScrollBy(dist, ADJUSTMENT_SCROLL_DURATION);
+ }
+ }
+ }
+ mPreviousScrollState = mNewState;
+ }
}
/**
- * Calculates the day that the given x position is in, accounting for
- * week number.
- *
- * @param x The x position of the touch event.
- * @return True if a day was found for the given location.
+ * <p>
+ * This is a specialized adapter for creating a list of weeks with
+ * selectable days. It can be configured to display the week number, start
+ * the week on a given day, show a reduced number of days, or display an
+ * arbitrary number of weeks at a time.
+ * </p>
*/
- public boolean getDayFromLocation(float x, Calendar outCalendar) {
- final boolean isLayoutRtl = isLayoutRtl();
+ private class WeeksAdapter extends BaseAdapter implements OnTouchListener {
- int start;
- int end;
+ private int mSelectedWeek;
- if (isLayoutRtl) {
- start = 0;
- end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
- } else {
- start = mShowWeekNumber ? mWidth / mNumCells : 0;
- end = mWidth;
+ private GestureDetector mGestureDetector;
+
+ private int mFocusedMonth;
+
+ private final Calendar mSelectedDate = Calendar.getInstance();
+
+ private int mTotalWeekCount;
+
+ public WeeksAdapter(Context context) {
+ mContext = context;
+ mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
+ init();
}
- if (x < start || x > end) {
- outCalendar.clear();
- return false;
+ /**
+ * Set up the gesture detector and selected time
+ */
+ private void init() {
+ mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
+ mTotalWeekCount = getWeeksSinceMinDate(mMaxDate);
+ if (mMinDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek
+ || mMaxDate.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek) {
+ mTotalWeekCount++;
+ }
+ }
+
+ /**
+ * Updates the selected day and related parameters.
+ *
+ * @param selectedDay The time to highlight
+ */
+ public void setSelectedDay(Calendar selectedDay) {
+ if (selectedDay.get(Calendar.DAY_OF_YEAR) == mSelectedDate.get(Calendar.DAY_OF_YEAR)
+ && selectedDay.get(Calendar.YEAR) == mSelectedDate.get(Calendar.YEAR)) {
+ return;
+ }
+ mSelectedDate.setTimeInMillis(selectedDay.getTimeInMillis());
+ mSelectedWeek = getWeeksSinceMinDate(mSelectedDate);
+ mFocusedMonth = mSelectedDate.get(Calendar.MONTH);
+ notifyDataSetChanged();
+ }
+
+ /**
+ * @return The selected day of month.
+ */
+ public Calendar getSelectedDay() {
+ return mSelectedDate;
}
- // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
- int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
+ @Override
+ public int getCount() {
+ return mTotalWeekCount;
+ }
- if (isLayoutRtl) {
- dayPosition = mDaysPerWeek - 1 - dayPosition;
+ @Override
+ public Object getItem(int position) {
+ return null;
}
- outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
- outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
- return true;
- }
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ WeekView weekView = null;
+ if (convertView != null) {
+ weekView = (WeekView) convertView;
+ } else {
+ weekView = new WeekView(mContext);
+ android.widget.AbsListView.LayoutParams params =
+ new android.widget.AbsListView.LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ weekView.setLayoutParams(params);
+ weekView.setClickable(true);
+ weekView.setOnTouchListener(this);
+ }
- @Override
- protected void onDraw(Canvas canvas) {
- drawBackground(canvas);
- drawWeekNumbersAndDates(canvas);
- drawWeekSeparators(canvas);
- drawSelectedDateVerticalBars(canvas);
- }
+ int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
+ Calendar.DAY_OF_WEEK) : -1;
+ weekView.init(position, selectedWeekDay, mFocusedMonth);
- /**
- * This draws the selection highlight if a day is selected in this week.
- *
- * @param canvas The canvas to draw on
- */
- private void drawBackground(Canvas canvas) {
- if (!mHasSelectedDay) {
- return;
+ return weekView;
}
- mDrawPaint.setColor(mSelectedWeekBackgroundColor);
- mTempRect.top = mWeekSeperatorLineWidth;
- mTempRect.bottom = mHeight;
+ /**
+ * Changes which month is in focus and updates the view.
+ *
+ * @param month The month to show as in focus [0-11]
+ */
+ public void setFocusMonth(int month) {
+ if (mFocusedMonth == month) {
+ return;
+ }
+ mFocusedMonth = month;
+ notifyDataSetChanged();
+ }
- final boolean isLayoutRtl = isLayoutRtl();
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mListView.isEnabled() && mGestureDetector.onTouchEvent(event)) {
+ WeekView weekView = (WeekView) v;
+ // if we cannot find a day for the given location we are done
+ if (!weekView.getDayFromLocation(event.getX(), mTempDate)) {
+ return true;
+ }
+ // it is possible that the touched day is outside the valid range
+ // we draw whole weeks but range end can fall not on the week end
+ if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
+ return true;
+ }
+ onDateTapped(mTempDate);
+ return true;
+ }
+ return false;
+ }
- if (isLayoutRtl) {
- mTempRect.left = 0;
- mTempRect.right = mSelectedLeft - 2;
- } else {
- mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
- mTempRect.right = mSelectedLeft - 2;
+ /**
+ * Maintains the same hour/min/sec but moves the day to the tapped day.
+ *
+ * @param day The day that was tapped
+ */
+ private void onDateTapped(Calendar day) {
+ setSelectedDay(day);
+ setMonthDisplayed(day);
}
- canvas.drawRect(mTempRect, mDrawPaint);
- if (isLayoutRtl) {
- mTempRect.left = mSelectedRight + 3;
- mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
- } else {
- mTempRect.left = mSelectedRight + 3;
- mTempRect.right = mWidth;
+ /**
+ * This is here so we can identify single tap events and set the
+ * selected day correctly
+ */
+ class CalendarGestureListener extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return true;
+ }
}
- canvas.drawRect(mTempRect, mDrawPaint);
}
/**
- * Draws the week and month day numbers for this week.
- *
- * @param canvas The canvas to draw on
+ * <p>
+ * This is a dynamic view for drawing a single week. It can be configured to
+ * display the week number, start the week on a given day, or show a reduced
+ * number of days. It is intended for use as a single view within a
+ * ListView. See {@link WeeksAdapter} for usage.
+ * </p>
*/
- private void drawWeekNumbersAndDates(Canvas canvas) {
- final float textHeight = mDrawPaint.getTextSize();
- final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
- final int nDays = mNumCells;
- final int divisor = 2 * nDays;
-
- mDrawPaint.setTextAlign(Align.CENTER);
- mDrawPaint.setTextSize(mDateTextSize);
-
- int i = 0;
-
- if (isLayoutRtl()) {
- for (; i < nDays - 1; i++) {
- mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
- : mUnfocusedMonthDateColor);
- int x = (2 * i + 1) * mWidth / divisor;
- canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
- }
- if (mShowWeekNumber) {
- mDrawPaint.setColor(mWeekNumberColor);
- int x = mWidth - mWidth / divisor;
- canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
- }
- } else {
+ private class WeekView extends View {
+
+ private final Rect mTempRect = new Rect();
+
+ private final Paint mDrawPaint = new Paint();
+
+ private final Paint mMonthNumDrawPaint = new Paint();
+
+ // Cache the number strings so we don't have to recompute them each time
+ private String[] mDayNumbers;
+
+ // Quick lookup for checking which days are in the focus month
+ private boolean[] mFocusDay;
+
+ // Whether this view has a focused day.
+ private boolean mHasFocusedDay;
+
+ // Whether this view has only focused days.
+ private boolean mHasUnfocusedDay;
+
+ // The first day displayed by this item
+ private Calendar mFirstDay;
+
+ // The month of the first day in this week
+ private int mMonthOfFirstWeekDay = -1;
+
+ // The month of the last day in this week
+ private int mLastWeekDayMonth = -1;
+
+ // The position of this week, equivalent to weeks since the week of Jan
+ // 1st, 1900
+ private int mWeek = -1;
+
+ // Quick reference to the width of this view, matches parent
+ private int mWidth;
+
+ // The height this view should draw at in pixels, set by height param
+ private int mHeight;
+
+ // If this view contains the selected day
+ private boolean mHasSelectedDay = false;
+
+ // Which day is selected [0-6] or -1 if no day is selected
+ private int mSelectedDay = -1;
+
+ // The number of days + a spot for week number if it is displayed
+ private int mNumCells;
+
+ // The left edge of the selected day
+ private int mSelectedLeft = -1;
+
+ // The right edge of the selected day
+ private int mSelectedRight = -1;
+
+ public WeekView(Context context) {
+ super(context);
+
+ // Sets up any standard paints that will be used
+ initilaizePaints();
+ }
+
+ /**
+ * Initializes this week view.
+ *
+ * @param weekNumber The number of the week this view represents. The
+ * week number is a zero based index of the weeks since
+ * {@link CalendarView#getMinDate()}.
+ * @param selectedWeekDay The selected day of the week from 0 to 6, -1 if no
+ * selected day.
+ * @param focusedMonth The month that is currently in focus i.e.
+ * highlighted.
+ */
+ public void init(int weekNumber, int selectedWeekDay, int focusedMonth) {
+ mSelectedDay = selectedWeekDay;
+ mHasSelectedDay = mSelectedDay != -1;
+ mNumCells = mShowWeekNumber ? mDaysPerWeek + 1 : mDaysPerWeek;
+ mWeek = weekNumber;
+ mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
+
+ mTempDate.add(Calendar.WEEK_OF_YEAR, mWeek);
+ mTempDate.setFirstDayOfWeek(mFirstDayOfWeek);
+
+ // Allocate space for caching the day numbers and focus values
+ mDayNumbers = new String[mNumCells];
+ mFocusDay = new boolean[mNumCells];
+
+ // If we're showing the week number calculate it based on Monday
+ int i = 0;
if (mShowWeekNumber) {
- mDrawPaint.setColor(mWeekNumberColor);
- int x = mWidth / divisor;
- canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
+ mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
+ mTempDate.get(Calendar.WEEK_OF_YEAR));
i++;
}
- for (; i < nDays; i++) {
- mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
- : mUnfocusedMonthDateColor);
- int x = (2 * i + 1) * mWidth / divisor;
- canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
+
+ // Now adjust our starting day based on the start day of the week
+ int diff = mFirstDayOfWeek - mTempDate.get(Calendar.DAY_OF_WEEK);
+ mTempDate.add(Calendar.DAY_OF_MONTH, diff);
+
+ mFirstDay = (Calendar) mTempDate.clone();
+ mMonthOfFirstWeekDay = mTempDate.get(Calendar.MONTH);
+
+ mHasUnfocusedDay = true;
+ for (; i < mNumCells; i++) {
+ final boolean isFocusedDay = (mTempDate.get(Calendar.MONTH) == focusedMonth);
+ mFocusDay[i] = isFocusedDay;
+ mHasFocusedDay |= isFocusedDay;
+ mHasUnfocusedDay &= !isFocusedDay;
+ // do not draw dates outside the valid range to avoid user confusion
+ if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
+ mDayNumbers[i] = "";
+ } else {
+ mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
+ mTempDate.get(Calendar.DAY_OF_MONTH));
+ }
+ mTempDate.add(Calendar.DAY_OF_MONTH, 1);
}
- }
- }
+ // We do one extra add at the end of the loop, if that pushed us to
+ // new month undo it
+ if (mTempDate.get(Calendar.DAY_OF_MONTH) == 1) {
+ mTempDate.add(Calendar.DAY_OF_MONTH, -1);
+ }
+ mLastWeekDayMonth = mTempDate.get(Calendar.MONTH);
- /**
- * Draws a horizontal line for separating the weeks.
- *
- * @param canvas The canvas to draw on.
- */
- private void drawWeekSeparators(Canvas canvas) {
- // If it is the topmost fully visible child do not draw separator line
- int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
- if (mListView.getChildAt(0).getTop() < 0) {
- firstFullyVisiblePosition++;
+ updateSelectionPositions();
}
- if (firstFullyVisiblePosition == mWeek) {
- return;
+
+ /**
+ * Initialize the paint instances.
+ */
+ private void initilaizePaints() {
+ mDrawPaint.setFakeBoldText(false);
+ mDrawPaint.setAntiAlias(true);
+ mDrawPaint.setStyle(Style.FILL);
+
+ mMonthNumDrawPaint.setFakeBoldText(true);
+ mMonthNumDrawPaint.setAntiAlias(true);
+ mMonthNumDrawPaint.setStyle(Style.FILL);
+ mMonthNumDrawPaint.setTextAlign(Align.CENTER);
+ mMonthNumDrawPaint.setTextSize(mDateTextSize);
}
- mDrawPaint.setColor(mWeekSeparatorLineColor);
- mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth);
- float startX;
- float stopX;
- if (isLayoutRtl()) {
- startX = 0;
- stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
- } else {
- startX = mShowWeekNumber ? mWidth / mNumCells : 0;
- stopX = mWidth;
+
+ /**
+ * Returns the month of the first day in this week.
+ *
+ * @return The month the first day of this view is in.
+ */
+ public int getMonthOfFirstWeekDay() {
+ return mMonthOfFirstWeekDay;
}
- canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
- }
- /**
- * Draws the selected date bars if this week has a selected day.
- *
- * @param canvas The canvas to draw on
- */
- private void drawSelectedDateVerticalBars(Canvas canvas) {
- if (!mHasSelectedDay) {
- return;
+ /**
+ * Returns the month of the last day in this week
+ *
+ * @return The month the last day of this view is in
+ */
+ public int getMonthOfLastWeekDay() {
+ return mLastWeekDayMonth;
}
- mSelectedDateVerticalBar.setBounds(mSelectedLeft - mSelectedDateVerticalBarWidth / 2,
- mWeekSeperatorLineWidth,
- mSelectedLeft + mSelectedDateVerticalBarWidth / 2, mHeight);
- mSelectedDateVerticalBar.draw(canvas);
- mSelectedDateVerticalBar.setBounds(mSelectedRight - mSelectedDateVerticalBarWidth / 2,
- mWeekSeperatorLineWidth,
- mSelectedRight + mSelectedDateVerticalBarWidth / 2, mHeight);
- mSelectedDateVerticalBar.draw(canvas);
- }
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- mWidth = w;
- updateSelectionPositions();
- }
+ /**
+ * Returns the first day in this view.
+ *
+ * @return The first day in the view.
+ */
+ public Calendar getFirstDay() {
+ return mFirstDay;
+ }
- /**
- * This calculates the positions for the selected day lines.
- */
- private void updateSelectionPositions() {
- if (mHasSelectedDay) {
+ /**
+ * Calculates the day that the given x position is in, accounting for
+ * week number.
+ *
+ * @param x The x position of the touch event.
+ * @return True if a day was found for the given location.
+ */
+ public boolean getDayFromLocation(float x, Calendar outCalendar) {
final boolean isLayoutRtl = isLayoutRtl();
- int selectedPosition = mSelectedDay - mFirstDayOfWeek;
- if (selectedPosition < 0) {
- selectedPosition += 7;
+
+ int start;
+ int end;
+
+ if (isLayoutRtl) {
+ start = 0;
+ end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
+ } else {
+ start = mShowWeekNumber ? mWidth / mNumCells : 0;
+ end = mWidth;
}
- if (mShowWeekNumber && !isLayoutRtl) {
- selectedPosition++;
+
+ if (x < start || x > end) {
+ outCalendar.clear();
+ return false;
}
+
+ // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
+ int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
+
if (isLayoutRtl) {
- mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
+ dayPosition = mDaysPerWeek - 1 - dayPosition;
+ }
+ outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
+ outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
+
+ return true;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ drawBackground(canvas);
+ drawWeekNumbersAndDates(canvas);
+ drawWeekSeparators(canvas);
+ drawSelectedDateVerticalBars(canvas);
+ }
+
+ /**
+ * This draws the selection highlight if a day is selected in this week.
+ *
+ * @param canvas The canvas to draw on
+ */
+ private void drawBackground(Canvas canvas) {
+ if (!mHasSelectedDay) {
+ return;
+ }
+ mDrawPaint.setColor(mSelectedWeekBackgroundColor);
+
+ mTempRect.top = mWeekSeperatorLineWidth;
+ mTempRect.bottom = mHeight;
+
+ final boolean isLayoutRtl = isLayoutRtl();
+
+ if (isLayoutRtl) {
+ mTempRect.left = 0;
+ mTempRect.right = mSelectedLeft - 2;
+ } else {
+ mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
+ mTempRect.right = mSelectedLeft - 2;
+ }
+ canvas.drawRect(mTempRect, mDrawPaint);
+
+ if (isLayoutRtl) {
+ mTempRect.left = mSelectedRight + 3;
+ mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
} else {
- mSelectedLeft = selectedPosition * mWidth / mNumCells;
+ mTempRect.left = mSelectedRight + 3;
+ mTempRect.right = mWidth;
}
- mSelectedRight = mSelectedLeft + mWidth / mNumCells;
+ canvas.drawRect(mTempRect, mDrawPaint);
}
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
- .getPaddingBottom()) / mShownWeekCount;
- setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
+ /**
+ * Draws the week and month day numbers for this week.
+ *
+ * @param canvas The canvas to draw on
+ */
+ private void drawWeekNumbersAndDates(Canvas canvas) {
+ final float textHeight = mDrawPaint.getTextSize();
+ final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
+ final int nDays = mNumCells;
+ final int divisor = 2 * nDays;
+
+ mDrawPaint.setTextAlign(Align.CENTER);
+ mDrawPaint.setTextSize(mDateTextSize);
+
+ int i = 0;
+
+ if (isLayoutRtl()) {
+ for (; i < nDays - 1; i++) {
+ mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
+ : mUnfocusedMonthDateColor);
+ int x = (2 * i + 1) * mWidth / divisor;
+ canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
+ }
+ if (mShowWeekNumber) {
+ mDrawPaint.setColor(mWeekNumberColor);
+ int x = mWidth - mWidth / divisor;
+ canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
+ }
+ } else {
+ if (mShowWeekNumber) {
+ mDrawPaint.setColor(mWeekNumberColor);
+ int x = mWidth / divisor;
+ canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
+ i++;
+ }
+ for (; i < nDays; i++) {
+ mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
+ : mUnfocusedMonthDateColor);
+ int x = (2 * i + 1) * mWidth / divisor;
+ canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
+ }
+ }
+ }
+
+ /**
+ * Draws a horizontal line for separating the weeks.
+ *
+ * @param canvas The canvas to draw on.
+ */
+ private void drawWeekSeparators(Canvas canvas) {
+ // If it is the topmost fully visible child do not draw separator line
+ int firstFullyVisiblePosition = mListView.getFirstVisiblePosition();
+ if (mListView.getChildAt(0).getTop() < 0) {
+ firstFullyVisiblePosition++;
+ }
+ if (firstFullyVisiblePosition == mWeek) {
+ return;
+ }
+ mDrawPaint.setColor(mWeekSeparatorLineColor);
+ mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth);
+ float startX;
+ float stopX;
+ if (isLayoutRtl()) {
+ startX = 0;
+ stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
+ } else {
+ startX = mShowWeekNumber ? mWidth / mNumCells : 0;
+ stopX = mWidth;
+ }
+ canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
+ }
+
+ /**
+ * Draws the selected date bars if this week has a selected day.
+ *
+ * @param canvas The canvas to draw on
+ */
+ private void drawSelectedDateVerticalBars(Canvas canvas) {
+ if (!mHasSelectedDay) {
+ return;
+ }
+ mSelectedDateVerticalBar.setBounds(
+ mSelectedLeft - mSelectedDateVerticalBarWidth / 2,
+ mWeekSeperatorLineWidth,
+ mSelectedLeft + mSelectedDateVerticalBarWidth / 2,
+ mHeight);
+ mSelectedDateVerticalBar.draw(canvas);
+ mSelectedDateVerticalBar.setBounds(
+ mSelectedRight - mSelectedDateVerticalBarWidth / 2,
+ mWeekSeperatorLineWidth,
+ mSelectedRight + mSelectedDateVerticalBarWidth / 2,
+ mHeight);
+ mSelectedDateVerticalBar.draw(canvas);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWidth = w;
+ updateSelectionPositions();
+ }
+
+ /**
+ * This calculates the positions for the selected day lines.
+ */
+ private void updateSelectionPositions() {
+ if (mHasSelectedDay) {
+ final boolean isLayoutRtl = isLayoutRtl();
+ int selectedPosition = mSelectedDay - mFirstDayOfWeek;
+ if (selectedPosition < 0) {
+ selectedPosition += 7;
+ }
+ if (mShowWeekNumber && !isLayoutRtl) {
+ selectedPosition++;
+ }
+ if (isLayoutRtl) {
+ mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
+
+ } else {
+ mSelectedLeft = selectedPosition * mWidth / mNumCells;
+ }
+ mSelectedRight = mSelectedLeft + mWidth / mNumCells;
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mHeight = (mListView.getHeight() - mListView.getPaddingTop() - mListView
+ .getPaddingBottom()) / mShownWeekCount;
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mHeight);
+ }
}
+
}
+
}
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
index f1804f8..71438c9 100644
--- a/core/java/android/widget/CheckBox.java
+++ b/core/java/android/widget/CheckBox.java
@@ -64,8 +64,12 @@ public class CheckBox extends CompoundButton {
this(context, attrs, com.android.internal.R.attr.checkboxStyle);
}
- public CheckBox(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public CheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public CheckBox(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 5c10a77..78b1b75 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -58,11 +58,15 @@ public class CheckedTextView extends TextView implements Checkable {
this(context, attrs, R.attr.checkedTextViewStyle);
}
- public CheckedTextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public CheckedTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public CheckedTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.CheckedTextView, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.CheckedTextView, defStyleAttr, defStyleRes);
Drawable d = a.getDrawable(R.styleable.CheckedTextView_checkMark);
if (d != null) {
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index b7a126e..f94789d 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -18,14 +18,12 @@ package android.widget;
import android.content.Context;
import android.content.res.TypedArray;
-import android.graphics.Canvas;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.Slog;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
@@ -96,12 +94,15 @@ public class Chronometer extends TextView {
* Initialize with standard view layout information and style.
* Sets the base to the current time.
*/
- public Chronometer(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public Chronometer(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public Chronometer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(
- attrs,
- com.android.internal.R.styleable.Chronometer, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.Chronometer, defStyleAttr, defStyleRes);
setFormat(a.getString(com.android.internal.R.styleable.Chronometer_format));
a.recycle();
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 082ff3d..bdb5046 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -64,12 +64,15 @@ public abstract class CompoundButton extends Button implements Checkable {
this(context, attrs, 0);
}
- public CompoundButton(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a =
- context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.CompoundButton, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.CompoundButton, defStyleAttr, defStyleRes);
Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);
if (d != null) {
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index d03161e..265dbcd 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -24,7 +24,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.text.InputType;
-import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -76,53 +75,7 @@ public class DatePicker extends FrameLayout {
private static final String LOG_TAG = DatePicker.class.getSimpleName();
- private static final String DATE_FORMAT = "MM/dd/yyyy";
-
- private static final int DEFAULT_START_YEAR = 1900;
-
- private static final int DEFAULT_END_YEAR = 2100;
-
- private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true;
-
- private static final boolean DEFAULT_SPINNERS_SHOWN = true;
-
- private static final boolean DEFAULT_ENABLED_STATE = true;
-
- private final LinearLayout mSpinners;
-
- private final NumberPicker mDaySpinner;
-
- private final NumberPicker mMonthSpinner;
-
- private final NumberPicker mYearSpinner;
-
- private final EditText mDaySpinnerInput;
-
- private final EditText mMonthSpinnerInput;
-
- private final EditText mYearSpinnerInput;
-
- private final CalendarView mCalendarView;
-
- private Locale mCurrentLocale;
-
- private OnDateChangedListener mOnDateChangedListener;
-
- private String[] mShortMonths;
-
- private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
-
- private int mNumberOfMonths;
-
- private Calendar mTempDate;
-
- private Calendar mMinDate;
-
- private Calendar mMaxDate;
-
- private Calendar mCurrentDate;
-
- private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
+ private DatePickerDelegate mDelegate;
/**
* The callback used to indicate the user changes\d the date.
@@ -149,147 +102,61 @@ public class DatePicker extends FrameLayout {
this(context, attrs, R.attr.datePickerStyle);
}
- public DatePicker(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- // initialization based on locale
- setCurrentLocale(Locale.getDefault());
-
- TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.DatePicker,
- defStyle, 0);
- boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown,
- DEFAULT_SPINNERS_SHOWN);
- boolean calendarViewShown = attributesArray.getBoolean(
- R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN);
- int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear,
- DEFAULT_START_YEAR);
- int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
- String minDate = attributesArray.getString(R.styleable.DatePicker_minDate);
- String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate);
- int layoutResourceId = attributesArray.getResourceId(R.styleable.DatePicker_internalLayout,
- R.layout.date_picker);
- attributesArray.recycle();
-
- LayoutInflater inflater = (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(layoutResourceId, this, true);
-
- OnValueChangeListener onChangeListener = new OnValueChangeListener() {
- public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
- updateInputState();
- mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
- // take care of wrapping of days and months to update greater fields
- if (picker == mDaySpinner) {
- int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
- if (oldVal == maxDayOfMonth && newVal == 1) {
- mTempDate.add(Calendar.DAY_OF_MONTH, 1);
- } else if (oldVal == 1 && newVal == maxDayOfMonth) {
- mTempDate.add(Calendar.DAY_OF_MONTH, -1);
- } else {
- mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
- }
- } else if (picker == mMonthSpinner) {
- if (oldVal == 11 && newVal == 0) {
- mTempDate.add(Calendar.MONTH, 1);
- } else if (oldVal == 0 && newVal == 11) {
- mTempDate.add(Calendar.MONTH, -1);
- } else {
- mTempDate.add(Calendar.MONTH, newVal - oldVal);
- }
- } else if (picker == mYearSpinner) {
- mTempDate.set(Calendar.YEAR, newVal);
- } else {
- throw new IllegalArgumentException();
- }
- // now set the date to the adjusted one
- setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
- mTempDate.get(Calendar.DAY_OF_MONTH));
- updateSpinners();
- updateCalendarView();
- notifyDateChanged();
- }
- };
+ public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
- mSpinners = (LinearLayout) findViewById(R.id.pickers);
+ public DatePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- // calendar view day-picker
- mCalendarView = (CalendarView) findViewById(R.id.calendar_view);
- mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
- public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) {
- setDate(year, month, monthDay);
- updateSpinners();
- notifyDateChanged();
- }
- });
-
- // day
- mDaySpinner = (NumberPicker) findViewById(R.id.day);
- mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
- mDaySpinner.setOnLongPressUpdateInterval(100);
- mDaySpinner.setOnValueChangedListener(onChangeListener);
- mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input);
-
- // month
- mMonthSpinner = (NumberPicker) findViewById(R.id.month);
- mMonthSpinner.setMinValue(0);
- mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
- mMonthSpinner.setDisplayedValues(mShortMonths);
- mMonthSpinner.setOnLongPressUpdateInterval(200);
- mMonthSpinner.setOnValueChangedListener(onChangeListener);
- mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(R.id.numberpicker_input);
-
- // year
- mYearSpinner = (NumberPicker) findViewById(R.id.year);
- mYearSpinner.setOnLongPressUpdateInterval(100);
- mYearSpinner.setOnValueChangedListener(onChangeListener);
- mYearSpinnerInput = (EditText) mYearSpinner.findViewById(R.id.numberpicker_input);
-
- // show only what the user required but make sure we
- // show something and the spinners have higher priority
- if (!spinnersShown && !calendarViewShown) {
- setSpinnersShown(true);
- } else {
- setSpinnersShown(spinnersShown);
- setCalendarViewShown(calendarViewShown);
- }
-
- // set the min date giving priority of the minDate over startYear
- mTempDate.clear();
- if (!TextUtils.isEmpty(minDate)) {
- if (!parseDate(minDate, mTempDate)) {
- mTempDate.set(startYear, 0, 1);
- }
- } else {
- mTempDate.set(startYear, 0, 1);
- }
- setMinDate(mTempDate.getTimeInMillis());
+ mDelegate = new LegacyDatePickerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
+ }
- // set the max date giving priority of the maxDate over endYear
- mTempDate.clear();
- if (!TextUtils.isEmpty(maxDate)) {
- if (!parseDate(maxDate, mTempDate)) {
- mTempDate.set(endYear, 11, 31);
- }
- } else {
- mTempDate.set(endYear, 11, 31);
- }
- setMaxDate(mTempDate.getTimeInMillis());
+ /**
+ * Initialize the state. If the provided values designate an inconsistent
+ * date the values are normalized before updating the spinners.
+ *
+ * @param year The initial year.
+ * @param monthOfYear The initial month <strong>starting from zero</strong>.
+ * @param dayOfMonth The initial day of the month.
+ * @param onDateChangedListener How user is notified date is changed by
+ * user, can be null.
+ */
+ public void init(int year, int monthOfYear, int dayOfMonth,
+ OnDateChangedListener onDateChangedListener) {
+ mDelegate.init(year, monthOfYear, dayOfMonth, onDateChangedListener);
+ }
- // initialize to current date
- mCurrentDate.setTimeInMillis(System.currentTimeMillis());
- init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate
- .get(Calendar.DAY_OF_MONTH), null);
+ /**
+ * Updates the current date.
+ *
+ * @param year The year.
+ * @param month The month which is <strong>starting from zero</strong>.
+ * @param dayOfMonth The day of the month.
+ */
+ public void updateDate(int year, int month, int dayOfMonth) {
+ mDelegate.updateDate(year, month, dayOfMonth);
+ }
- // re-order the number spinners to match the current date format
- reorderSpinners();
+ /**
+ * @return The selected year.
+ */
+ public int getYear() {
+ return mDelegate.getYear();
+ }
- // accessibility
- setContentDescriptions();
+ /**
+ * @return The selected month.
+ */
+ public int getMonth() {
+ return mDelegate.getMonth();
+ }
- // If not explicitly specified this view is important for accessibility.
- if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
- setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
+ /**
+ * @return The selected day of month.
+ */
+ public int getDayOfMonth() {
+ return mDelegate.getDayOfMonth();
}
/**
@@ -303,7 +170,7 @@ public class DatePicker extends FrameLayout {
* @return The minimal supported date.
*/
public long getMinDate() {
- return mCalendarView.getMinDate();
+ return mDelegate.getMinDate();
}
/**
@@ -314,18 +181,7 @@ public class DatePicker extends FrameLayout {
* @param minDate The minimal supported date.
*/
public void setMinDate(long minDate) {
- mTempDate.setTimeInMillis(minDate);
- if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
- && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
- return;
- }
- mMinDate.setTimeInMillis(minDate);
- mCalendarView.setMinDate(minDate);
- if (mCurrentDate.before(mMinDate)) {
- mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
- updateCalendarView();
- }
- updateSpinners();
+ mDelegate.setMinDate(minDate);
}
/**
@@ -339,7 +195,7 @@ public class DatePicker extends FrameLayout {
* @return The maximal supported date.
*/
public long getMaxDate() {
- return mCalendarView.getMaxDate();
+ return mDelegate.getMaxDate();
}
/**
@@ -350,70 +206,50 @@ public class DatePicker extends FrameLayout {
* @param maxDate The maximal supported date.
*/
public void setMaxDate(long maxDate) {
- mTempDate.setTimeInMillis(maxDate);
- if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
- && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
- return;
- }
- mMaxDate.setTimeInMillis(maxDate);
- mCalendarView.setMaxDate(maxDate);
- if (mCurrentDate.after(mMaxDate)) {
- mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
- updateCalendarView();
- }
- updateSpinners();
+ mDelegate.setMaxDate(maxDate);
}
@Override
public void setEnabled(boolean enabled) {
- if (mIsEnabled == enabled) {
+ if (mDelegate.isEnabled() == enabled) {
return;
}
super.setEnabled(enabled);
- mDaySpinner.setEnabled(enabled);
- mMonthSpinner.setEnabled(enabled);
- mYearSpinner.setEnabled(enabled);
- mCalendarView.setEnabled(enabled);
- mIsEnabled = enabled;
+ mDelegate.setEnabled(enabled);
}
@Override
public boolean isEnabled() {
- return mIsEnabled;
+ return mDelegate.isEnabled();
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- onPopulateAccessibilityEvent(event);
- return true;
+ return mDelegate.dispatchPopulateAccessibilityEvent(event);
}
@Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
super.onPopulateAccessibilityEvent(event);
-
- final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
- String selectedDateUtterance = DateUtils.formatDateTime(mContext,
- mCurrentDate.getTimeInMillis(), flags);
- event.getText().add(selectedDateUtterance);
+ mDelegate.onPopulateAccessibilityEvent(event);
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
- event.setClassName(DatePicker.class.getName());
+ mDelegate.onInitializeAccessibilityEvent(event);
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(DatePicker.class.getName());
+ mDelegate.onInitializeAccessibilityNodeInfo(info);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- setCurrentLocale(newConfig.locale);
+ mDelegate.onConfigurationChanged(newConfig);
}
/**
@@ -423,7 +259,7 @@ public class DatePicker extends FrameLayout {
* @see #getCalendarView()
*/
public boolean getCalendarViewShown() {
- return (mCalendarView.getVisibility() == View.VISIBLE);
+ return mDelegate.getCalendarViewShown();
}
/**
@@ -433,7 +269,7 @@ public class DatePicker extends FrameLayout {
* @see #getCalendarViewShown()
*/
public CalendarView getCalendarView () {
- return mCalendarView;
+ return mDelegate.getCalendarView();
}
/**
@@ -442,7 +278,7 @@ public class DatePicker extends FrameLayout {
* @param shown True if the calendar view is to be shown.
*/
public void setCalendarViewShown(boolean shown) {
- mCalendarView.setVisibility(shown ? VISIBLE : GONE);
+ mDelegate.setCalendarViewShown(shown);
}
/**
@@ -451,7 +287,7 @@ public class DatePicker extends FrameLayout {
* @return True if the spinners are shown.
*/
public boolean getSpinnersShown() {
- return mSpinners.isShown();
+ return mDelegate.getSpinnersShown();
}
/**
@@ -460,330 +296,708 @@ public class DatePicker extends FrameLayout {
* @param shown True if the spinners are to be shown.
*/
public void setSpinnersShown(boolean shown) {
- mSpinners.setVisibility(shown ? VISIBLE : GONE);
+ mDelegate.setSpinnersShown(shown);
+ }
+
+ // Override so we are in complete control of save / restore for this widget.
+ @Override
+ protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+ mDelegate.dispatchRestoreInstanceState(container);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ return mDelegate.onSaveInstanceState(superState);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+ mDelegate.onRestoreInstanceState(ss);
}
/**
- * Sets the current locale.
- *
- * @param locale The current locale.
+ * A delegate interface that defined the public API of the DatePicker. Allows different
+ * DatePicker implementations. This would need to be implemented by the DatePicker delegates
+ * for the real behavior.
*/
- private void setCurrentLocale(Locale locale) {
- if (locale.equals(mCurrentLocale)) {
- return;
- }
+ interface DatePickerDelegate {
+ void init(int year, int monthOfYear, int dayOfMonth,
+ OnDateChangedListener onDateChangedListener);
- mCurrentLocale = locale;
+ void updateDate(int year, int month, int dayOfMonth);
- mTempDate = getCalendarForLocale(mTempDate, locale);
- mMinDate = getCalendarForLocale(mMinDate, locale);
- mMaxDate = getCalendarForLocale(mMaxDate, locale);
- mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
+ int getYear();
+ int getMonth();
+ int getDayOfMonth();
- mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
- mShortMonths = new DateFormatSymbols().getShortMonths();
+ void setMinDate(long minDate);
+ long getMinDate();
- if (usingNumericMonths()) {
- // We're in a locale where a date should either be all-numeric, or all-text.
- // All-text would require custom NumberPicker formatters for day and year.
- mShortMonths = new String[mNumberOfMonths];
- for (int i = 0; i < mNumberOfMonths; ++i) {
- mShortMonths[i] = String.format("%d", i + 1);
- }
- }
- }
+ void setMaxDate(long maxDate);
+ long getMaxDate();
- /**
- * Tests whether the current locale is one where there are no real month names,
- * such as Chinese, Japanese, or Korean locales.
- */
- private boolean usingNumericMonths() {
- return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0));
+ void setEnabled(boolean enabled);
+ boolean isEnabled();
+
+ CalendarView getCalendarView ();
+
+ void setCalendarViewShown(boolean shown);
+ boolean getCalendarViewShown();
+
+ void setSpinnersShown(boolean shown);
+ boolean getSpinnersShown();
+
+ void onConfigurationChanged(Configuration newConfig);
+
+ void dispatchRestoreInstanceState(SparseArray<Parcelable> container);
+ Parcelable onSaveInstanceState(Parcelable superState);
+ void onRestoreInstanceState(Parcelable state);
+
+ boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
+ void onPopulateAccessibilityEvent(AccessibilityEvent event);
+ void onInitializeAccessibilityEvent(AccessibilityEvent event);
+ void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
}
/**
- * Gets a calendar for locale bootstrapped with the value of a given calendar.
- *
- * @param oldCalendar The old calendar.
- * @param locale The locale.
+ * An abstract class which can be used as a start for DatePicker implementations
*/
- private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
- if (oldCalendar == null) {
- return Calendar.getInstance(locale);
- } else {
- final long currentTimeMillis = oldCalendar.getTimeInMillis();
- Calendar newCalendar = Calendar.getInstance(locale);
- newCalendar.setTimeInMillis(currentTimeMillis);
- return newCalendar;
+ abstract static class AbstractTimePickerDelegate implements DatePickerDelegate {
+ // The delegator
+ protected DatePicker mDelegator;
+
+ // The context
+ protected Context mContext;
+
+ // The current locale
+ protected Locale mCurrentLocale;
+
+ // Callbacks
+ protected OnDateChangedListener mOnDateChangedListener;
+
+ public AbstractTimePickerDelegate(DatePicker delegator, Context context) {
+ mDelegator = delegator;
+ mContext = context;
+
+ // initialization based on locale
+ setCurrentLocale(Locale.getDefault());
}
- }
- /**
- * Reorders the spinners according to the date format that is
- * explicitly set by the user and if no such is set fall back
- * to the current locale's default format.
- */
- private void reorderSpinners() {
- mSpinners.removeAllViews();
- // We use numeric spinners for year and day, but textual months. Ask icu4c what
- // order the user's locale uses for that combination. http://b/7207103.
- String pattern = ICU.getBestDateTimePattern("yyyyMMMdd", Locale.getDefault().toString());
- char[] order = ICU.getDateFormatOrder(pattern);
- final int spinnerCount = order.length;
- for (int i = 0; i < spinnerCount; i++) {
- switch (order[i]) {
- case 'd':
- mSpinners.addView(mDaySpinner);
- setImeOptions(mDaySpinner, spinnerCount, i);
- break;
- case 'M':
- mSpinners.addView(mMonthSpinner);
- setImeOptions(mMonthSpinner, spinnerCount, i);
- break;
- case 'y':
- mSpinners.addView(mYearSpinner);
- setImeOptions(mYearSpinner, spinnerCount, i);
- break;
- default:
- throw new IllegalArgumentException(Arrays.toString(order));
+ protected void setCurrentLocale(Locale locale) {
+ if (locale.equals(mCurrentLocale)) {
+ return;
}
+ mCurrentLocale = locale;
}
}
/**
- * Updates the current date.
- *
- * @param year The year.
- * @param month The month which is <strong>starting from zero</strong>.
- * @param dayOfMonth The day of the month.
+ * A delegate implementing the basic DatePicker
*/
- public void updateDate(int year, int month, int dayOfMonth) {
- if (!isNewDate(year, month, dayOfMonth)) {
- return;
+ private static class LegacyDatePickerDelegate extends AbstractTimePickerDelegate {
+
+ private static final String DATE_FORMAT = "MM/dd/yyyy";
+
+ private static final int DEFAULT_START_YEAR = 1900;
+
+ private static final int DEFAULT_END_YEAR = 2100;
+
+ private static final boolean DEFAULT_CALENDAR_VIEW_SHOWN = true;
+
+ private static final boolean DEFAULT_SPINNERS_SHOWN = true;
+
+ private static final boolean DEFAULT_ENABLED_STATE = true;
+
+ private final LinearLayout mSpinners;
+
+ private final NumberPicker mDaySpinner;
+
+ private final NumberPicker mMonthSpinner;
+
+ private final NumberPicker mYearSpinner;
+
+ private final EditText mDaySpinnerInput;
+
+ private final EditText mMonthSpinnerInput;
+
+ private final EditText mYearSpinnerInput;
+
+ private final CalendarView mCalendarView;
+
+ private String[] mShortMonths;
+
+ private final java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
+
+ private int mNumberOfMonths;
+
+ private Calendar mTempDate;
+
+ private Calendar mMinDate;
+
+ private Calendar mMaxDate;
+
+ private Calendar mCurrentDate;
+
+ private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
+
+ LegacyDatePickerDelegate(DatePicker delegator, Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(delegator, context);
+
+ mDelegator = delegator;
+ mContext = context;
+
+ // initialization based on locale
+ setCurrentLocale(Locale.getDefault());
+
+ final TypedArray attributesArray = context.obtainStyledAttributes(attrs,
+ R.styleable.DatePicker, defStyleAttr, defStyleRes);
+ boolean spinnersShown = attributesArray.getBoolean(R.styleable.DatePicker_spinnersShown,
+ DEFAULT_SPINNERS_SHOWN);
+ boolean calendarViewShown = attributesArray.getBoolean(
+ R.styleable.DatePicker_calendarViewShown, DEFAULT_CALENDAR_VIEW_SHOWN);
+ int startYear = attributesArray.getInt(R.styleable.DatePicker_startYear,
+ DEFAULT_START_YEAR);
+ int endYear = attributesArray.getInt(R.styleable.DatePicker_endYear, DEFAULT_END_YEAR);
+ String minDate = attributesArray.getString(R.styleable.DatePicker_minDate);
+ String maxDate = attributesArray.getString(R.styleable.DatePicker_maxDate);
+ int layoutResourceId = attributesArray.getResourceId(
+ R.styleable.DatePicker_internalLayout, R.layout.date_picker);
+ attributesArray.recycle();
+
+ LayoutInflater inflater = (LayoutInflater) context
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(layoutResourceId, mDelegator, true);
+
+ OnValueChangeListener onChangeListener = new OnValueChangeListener() {
+ public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
+ updateInputState();
+ mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
+ // take care of wrapping of days and months to update greater fields
+ if (picker == mDaySpinner) {
+ int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
+ if (oldVal == maxDayOfMonth && newVal == 1) {
+ mTempDate.add(Calendar.DAY_OF_MONTH, 1);
+ } else if (oldVal == 1 && newVal == maxDayOfMonth) {
+ mTempDate.add(Calendar.DAY_OF_MONTH, -1);
+ } else {
+ mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
+ }
+ } else if (picker == mMonthSpinner) {
+ if (oldVal == 11 && newVal == 0) {
+ mTempDate.add(Calendar.MONTH, 1);
+ } else if (oldVal == 0 && newVal == 11) {
+ mTempDate.add(Calendar.MONTH, -1);
+ } else {
+ mTempDate.add(Calendar.MONTH, newVal - oldVal);
+ }
+ } else if (picker == mYearSpinner) {
+ mTempDate.set(Calendar.YEAR, newVal);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ // now set the date to the adjusted one
+ setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
+ mTempDate.get(Calendar.DAY_OF_MONTH));
+ updateSpinners();
+ updateCalendarView();
+ notifyDateChanged();
+ }
+ };
+
+ mSpinners = (LinearLayout) mDelegator.findViewById(R.id.pickers);
+
+ // calendar view day-picker
+ mCalendarView = (CalendarView) mDelegator.findViewById(R.id.calendar_view);
+ mCalendarView.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
+ public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) {
+ setDate(year, month, monthDay);
+ updateSpinners();
+ notifyDateChanged();
+ }
+ });
+
+ // day
+ mDaySpinner = (NumberPicker) mDelegator.findViewById(R.id.day);
+ mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
+ mDaySpinner.setOnLongPressUpdateInterval(100);
+ mDaySpinner.setOnValueChangedListener(onChangeListener);
+ mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input);
+
+ // month
+ mMonthSpinner = (NumberPicker) mDelegator.findViewById(R.id.month);
+ mMonthSpinner.setMinValue(0);
+ mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
+ mMonthSpinner.setDisplayedValues(mShortMonths);
+ mMonthSpinner.setOnLongPressUpdateInterval(200);
+ mMonthSpinner.setOnValueChangedListener(onChangeListener);
+ mMonthSpinnerInput = (EditText) mMonthSpinner.findViewById(R.id.numberpicker_input);
+
+ // year
+ mYearSpinner = (NumberPicker) mDelegator.findViewById(R.id.year);
+ mYearSpinner.setOnLongPressUpdateInterval(100);
+ mYearSpinner.setOnValueChangedListener(onChangeListener);
+ mYearSpinnerInput = (EditText) mYearSpinner.findViewById(R.id.numberpicker_input);
+
+ // show only what the user required but make sure we
+ // show something and the spinners have higher priority
+ if (!spinnersShown && !calendarViewShown) {
+ setSpinnersShown(true);
+ } else {
+ setSpinnersShown(spinnersShown);
+ setCalendarViewShown(calendarViewShown);
+ }
+
+ // set the min date giving priority of the minDate over startYear
+ mTempDate.clear();
+ if (!TextUtils.isEmpty(minDate)) {
+ if (!parseDate(minDate, mTempDate)) {
+ mTempDate.set(startYear, 0, 1);
+ }
+ } else {
+ mTempDate.set(startYear, 0, 1);
+ }
+ setMinDate(mTempDate.getTimeInMillis());
+
+ // set the max date giving priority of the maxDate over endYear
+ mTempDate.clear();
+ if (!TextUtils.isEmpty(maxDate)) {
+ if (!parseDate(maxDate, mTempDate)) {
+ mTempDate.set(endYear, 11, 31);
+ }
+ } else {
+ mTempDate.set(endYear, 11, 31);
+ }
+ setMaxDate(mTempDate.getTimeInMillis());
+
+ // initialize to current date
+ mCurrentDate.setTimeInMillis(System.currentTimeMillis());
+ init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate
+ .get(Calendar.DAY_OF_MONTH), null);
+
+ // re-order the number spinners to match the current date format
+ reorderSpinners();
+
+ // accessibility
+ setContentDescriptions();
+
+ // If not explicitly specified this view is important for accessibility.
+ if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
- setDate(year, month, dayOfMonth);
- updateSpinners();
- updateCalendarView();
- notifyDateChanged();
- }
- // Override so we are in complete control of save / restore for this widget.
- @Override
- protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
- dispatchThawSelfOnly(container);
- }
+ @Override
+ public void init(int year, int monthOfYear, int dayOfMonth,
+ OnDateChangedListener onDateChangedListener) {
+ setDate(year, monthOfYear, dayOfMonth);
+ updateSpinners();
+ updateCalendarView();
+ mOnDateChangedListener = onDateChangedListener;
+ }
- @Override
- protected Parcelable onSaveInstanceState() {
- Parcelable superState = super.onSaveInstanceState();
- return new SavedState(superState, getYear(), getMonth(), getDayOfMonth());
- }
+ @Override
+ public void updateDate(int year, int month, int dayOfMonth) {
+ if (!isNewDate(year, month, dayOfMonth)) {
+ return;
+ }
+ setDate(year, month, dayOfMonth);
+ updateSpinners();
+ updateCalendarView();
+ notifyDateChanged();
+ }
- @Override
- protected void onRestoreInstanceState(Parcelable state) {
- SavedState ss = (SavedState) state;
- super.onRestoreInstanceState(ss.getSuperState());
- setDate(ss.mYear, ss.mMonth, ss.mDay);
- updateSpinners();
- updateCalendarView();
- }
+ @Override
+ public int getYear() {
+ return mCurrentDate.get(Calendar.YEAR);
+ }
- /**
- * Initialize the state. If the provided values designate an inconsistent
- * date the values are normalized before updating the spinners.
- *
- * @param year The initial year.
- * @param monthOfYear The initial month <strong>starting from zero</strong>.
- * @param dayOfMonth The initial day of the month.
- * @param onDateChangedListener How user is notified date is changed by
- * user, can be null.
- */
- public void init(int year, int monthOfYear, int dayOfMonth,
- OnDateChangedListener onDateChangedListener) {
- setDate(year, monthOfYear, dayOfMonth);
- updateSpinners();
- updateCalendarView();
- mOnDateChangedListener = onDateChangedListener;
- }
+ @Override
+ public int getMonth() {
+ return mCurrentDate.get(Calendar.MONTH);
+ }
- /**
- * Parses the given <code>date</code> and in case of success sets the result
- * to the <code>outDate</code>.
- *
- * @return True if the date was parsed.
- */
- private boolean parseDate(String date, Calendar outDate) {
- try {
- outDate.setTime(mDateFormat.parse(date));
+ @Override
+ public int getDayOfMonth() {
+ return mCurrentDate.get(Calendar.DAY_OF_MONTH);
+ }
+
+ @Override
+ public void setMinDate(long minDate) {
+ mTempDate.setTimeInMillis(minDate);
+ if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
+ && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
+ return;
+ }
+ mMinDate.setTimeInMillis(minDate);
+ mCalendarView.setMinDate(minDate);
+ if (mCurrentDate.before(mMinDate)) {
+ mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
+ updateCalendarView();
+ }
+ updateSpinners();
+ }
+
+ @Override
+ public long getMinDate() {
+ return mCalendarView.getMinDate();
+ }
+
+ @Override
+ public void setMaxDate(long maxDate) {
+ mTempDate.setTimeInMillis(maxDate);
+ if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
+ && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
+ return;
+ }
+ mMaxDate.setTimeInMillis(maxDate);
+ mCalendarView.setMaxDate(maxDate);
+ if (mCurrentDate.after(mMaxDate)) {
+ mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
+ updateCalendarView();
+ }
+ updateSpinners();
+ }
+
+ @Override
+ public long getMaxDate() {
+ return mCalendarView.getMaxDate();
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ mDaySpinner.setEnabled(enabled);
+ mMonthSpinner.setEnabled(enabled);
+ mYearSpinner.setEnabled(enabled);
+ mCalendarView.setEnabled(enabled);
+ mIsEnabled = enabled;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ @Override
+ public CalendarView getCalendarView() {
+ return mCalendarView;
+ }
+
+ @Override
+ public void setCalendarViewShown(boolean shown) {
+ mCalendarView.setVisibility(shown ? VISIBLE : GONE);
+ }
+
+ @Override
+ public boolean getCalendarViewShown() {
+ return (mCalendarView.getVisibility() == View.VISIBLE);
+ }
+
+ @Override
+ public void setSpinnersShown(boolean shown) {
+ mSpinners.setVisibility(shown ? VISIBLE : GONE);
+ }
+
+ @Override
+ public boolean getSpinnersShown() {
+ return mSpinners.isShown();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ setCurrentLocale(newConfig.locale);
+ }
+
+ @Override
+ public void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+ mDelegator.dispatchThawSelfOnly(container);
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState(Parcelable superState) {
+ return new SavedState(superState, getYear(), getMonth(), getDayOfMonth());
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+ setDate(ss.mYear, ss.mMonth, ss.mDay);
+ updateSpinners();
+ updateCalendarView();
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ onPopulateAccessibilityEvent(event);
return true;
- } catch (ParseException e) {
- Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
- return false;
- }
- }
-
- private boolean isNewDate(int year, int month, int dayOfMonth) {
- return (mCurrentDate.get(Calendar.YEAR) != year
- || mCurrentDate.get(Calendar.MONTH) != dayOfMonth
- || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
- }
-
- private void setDate(int year, int month, int dayOfMonth) {
- mCurrentDate.set(year, month, dayOfMonth);
- if (mCurrentDate.before(mMinDate)) {
- mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
- } else if (mCurrentDate.after(mMaxDate)) {
- mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
- }
- }
-
- private void updateSpinners() {
- // set the spinner ranges respecting the min and max dates
- if (mCurrentDate.equals(mMinDate)) {
- mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
- mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
- mDaySpinner.setWrapSelectorWheel(false);
- mMonthSpinner.setDisplayedValues(null);
- mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
- mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
- mMonthSpinner.setWrapSelectorWheel(false);
- } else if (mCurrentDate.equals(mMaxDate)) {
- mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
- mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
- mDaySpinner.setWrapSelectorWheel(false);
- mMonthSpinner.setDisplayedValues(null);
- mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
- mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
- mMonthSpinner.setWrapSelectorWheel(false);
- } else {
- mDaySpinner.setMinValue(1);
- mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
- mDaySpinner.setWrapSelectorWheel(true);
- mMonthSpinner.setDisplayedValues(null);
- mMonthSpinner.setMinValue(0);
- mMonthSpinner.setMaxValue(11);
- mMonthSpinner.setWrapSelectorWheel(true);
}
- // make sure the month names are a zero based array
- // with the months in the month spinner
- String[] displayedValues = Arrays.copyOfRange(mShortMonths,
- mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
- mMonthSpinner.setDisplayedValues(displayedValues);
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
+ String selectedDateUtterance = DateUtils.formatDateTime(mContext,
+ mCurrentDate.getTimeInMillis(), flags);
+ event.getText().add(selectedDateUtterance);
+ }
- // year spinner range does not change based on the current date
- mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
- mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
- mYearSpinner.setWrapSelectorWheel(false);
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ event.setClassName(DatePicker.class.getName());
+ }
- // set the spinner values
- mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
- mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
- mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ info.setClassName(DatePicker.class.getName());
+ }
- if (usingNumericMonths()) {
- mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER);
+ /**
+ * Sets the current locale.
+ *
+ * @param locale The current locale.
+ */
+ @Override
+ protected void setCurrentLocale(Locale locale) {
+ super.setCurrentLocale(locale);
+
+ mTempDate = getCalendarForLocale(mTempDate, locale);
+ mMinDate = getCalendarForLocale(mMinDate, locale);
+ mMaxDate = getCalendarForLocale(mMaxDate, locale);
+ mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
+
+ mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
+ mShortMonths = new DateFormatSymbols().getShortMonths();
+
+ if (usingNumericMonths()) {
+ // We're in a locale where a date should either be all-numeric, or all-text.
+ // All-text would require custom NumberPicker formatters for day and year.
+ mShortMonths = new String[mNumberOfMonths];
+ for (int i = 0; i < mNumberOfMonths; ++i) {
+ mShortMonths[i] = String.format("%d", i + 1);
+ }
+ }
}
- }
- /**
- * Updates the calendar view with the current date.
- */
- private void updateCalendarView() {
- mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
- }
+ /**
+ * Tests whether the current locale is one where there are no real month names,
+ * such as Chinese, Japanese, or Korean locales.
+ */
+ private boolean usingNumericMonths() {
+ return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0));
+ }
- /**
- * @return The selected year.
- */
- public int getYear() {
- return mCurrentDate.get(Calendar.YEAR);
- }
+ /**
+ * Gets a calendar for locale bootstrapped with the value of a given calendar.
+ *
+ * @param oldCalendar The old calendar.
+ * @param locale The locale.
+ */
+ private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
+ if (oldCalendar == null) {
+ return Calendar.getInstance(locale);
+ } else {
+ final long currentTimeMillis = oldCalendar.getTimeInMillis();
+ Calendar newCalendar = Calendar.getInstance(locale);
+ newCalendar.setTimeInMillis(currentTimeMillis);
+ return newCalendar;
+ }
+ }
- /**
- * @return The selected month.
- */
- public int getMonth() {
- return mCurrentDate.get(Calendar.MONTH);
- }
+ /**
+ * Reorders the spinners according to the date format that is
+ * explicitly set by the user and if no such is set fall back
+ * to the current locale's default format.
+ */
+ private void reorderSpinners() {
+ mSpinners.removeAllViews();
+ // We use numeric spinners for year and day, but textual months. Ask icu4c what
+ // order the user's locale uses for that combination. http://b/7207103.
+ String pattern = ICU.getBestDateTimePattern("yyyyMMMdd",
+ Locale.getDefault().toString());
+ char[] order = ICU.getDateFormatOrder(pattern);
+ final int spinnerCount = order.length;
+ for (int i = 0; i < spinnerCount; i++) {
+ switch (order[i]) {
+ case 'd':
+ mSpinners.addView(mDaySpinner);
+ setImeOptions(mDaySpinner, spinnerCount, i);
+ break;
+ case 'M':
+ mSpinners.addView(mMonthSpinner);
+ setImeOptions(mMonthSpinner, spinnerCount, i);
+ break;
+ case 'y':
+ mSpinners.addView(mYearSpinner);
+ setImeOptions(mYearSpinner, spinnerCount, i);
+ break;
+ default:
+ throw new IllegalArgumentException(Arrays.toString(order));
+ }
+ }
+ }
- /**
- * @return The selected day of month.
- */
- public int getDayOfMonth() {
- return mCurrentDate.get(Calendar.DAY_OF_MONTH);
- }
+ /**
+ * Parses the given <code>date</code> and in case of success sets the result
+ * to the <code>outDate</code>.
+ *
+ * @return True if the date was parsed.
+ */
+ private boolean parseDate(String date, Calendar outDate) {
+ try {
+ outDate.setTime(mDateFormat.parse(date));
+ return true;
+ } catch (ParseException e) {
+ Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
+ return false;
+ }
+ }
- /**
- * Notifies the listener, if such, for a change in the selected date.
- */
- private void notifyDateChanged() {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- if (mOnDateChangedListener != null) {
- mOnDateChangedListener.onDateChanged(this, getYear(), getMonth(), getDayOfMonth());
+ private boolean isNewDate(int year, int month, int dayOfMonth) {
+ return (mCurrentDate.get(Calendar.YEAR) != year
+ || mCurrentDate.get(Calendar.MONTH) != dayOfMonth
+ || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
}
- }
- /**
- * Sets the IME options for a spinner based on its ordering.
- *
- * @param spinner The spinner.
- * @param spinnerCount The total spinner count.
- * @param spinnerIndex The index of the given spinner.
- */
- private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) {
- final int imeOptions;
- if (spinnerIndex < spinnerCount - 1) {
- imeOptions = EditorInfo.IME_ACTION_NEXT;
- } else {
- imeOptions = EditorInfo.IME_ACTION_DONE;
- }
- TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input);
- input.setImeOptions(imeOptions);
- }
-
- private void setContentDescriptions() {
- // Day
- trySetContentDescription(mDaySpinner, R.id.increment,
- R.string.date_picker_increment_day_button);
- trySetContentDescription(mDaySpinner, R.id.decrement,
- R.string.date_picker_decrement_day_button);
- // Month
- trySetContentDescription(mMonthSpinner, R.id.increment,
- R.string.date_picker_increment_month_button);
- trySetContentDescription(mMonthSpinner, R.id.decrement,
- R.string.date_picker_decrement_month_button);
- // Year
- trySetContentDescription(mYearSpinner, R.id.increment,
- R.string.date_picker_increment_year_button);
- trySetContentDescription(mYearSpinner, R.id.decrement,
- R.string.date_picker_decrement_year_button);
- }
-
- private void trySetContentDescription(View root, int viewId, int contDescResId) {
- View target = root.findViewById(viewId);
- if (target != null) {
- target.setContentDescription(mContext.getString(contDescResId));
- }
- }
-
- private void updateInputState() {
- // Make sure that if the user changes the value and the IME is active
- // for one of the inputs if this widget, the IME is closed. If the user
- // changed the value via the IME and there is a next input the IME will
- // be shown, otherwise the user chose another means of changing the
- // value and having the IME up makes no sense.
- InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
- if (inputMethodManager != null) {
- if (inputMethodManager.isActive(mYearSpinnerInput)) {
- mYearSpinnerInput.clearFocus();
- inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
- } else if (inputMethodManager.isActive(mMonthSpinnerInput)) {
- mMonthSpinnerInput.clearFocus();
- inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
- } else if (inputMethodManager.isActive(mDaySpinnerInput)) {
- mDaySpinnerInput.clearFocus();
- inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+ private void setDate(int year, int month, int dayOfMonth) {
+ mCurrentDate.set(year, month, dayOfMonth);
+ if (mCurrentDate.before(mMinDate)) {
+ mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
+ } else if (mCurrentDate.after(mMaxDate)) {
+ mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
+ }
+ }
+
+ private void updateSpinners() {
+ // set the spinner ranges respecting the min and max dates
+ if (mCurrentDate.equals(mMinDate)) {
+ mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
+ mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
+ mDaySpinner.setWrapSelectorWheel(false);
+ mMonthSpinner.setDisplayedValues(null);
+ mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
+ mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
+ mMonthSpinner.setWrapSelectorWheel(false);
+ } else if (mCurrentDate.equals(mMaxDate)) {
+ mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
+ mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
+ mDaySpinner.setWrapSelectorWheel(false);
+ mMonthSpinner.setDisplayedValues(null);
+ mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
+ mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
+ mMonthSpinner.setWrapSelectorWheel(false);
+ } else {
+ mDaySpinner.setMinValue(1);
+ mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
+ mDaySpinner.setWrapSelectorWheel(true);
+ mMonthSpinner.setDisplayedValues(null);
+ mMonthSpinner.setMinValue(0);
+ mMonthSpinner.setMaxValue(11);
+ mMonthSpinner.setWrapSelectorWheel(true);
+ }
+
+ // make sure the month names are a zero based array
+ // with the months in the month spinner
+ String[] displayedValues = Arrays.copyOfRange(mShortMonths,
+ mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
+ mMonthSpinner.setDisplayedValues(displayedValues);
+
+ // year spinner range does not change based on the current date
+ mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
+ mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
+ mYearSpinner.setWrapSelectorWheel(false);
+
+ // set the spinner values
+ mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
+ mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
+ mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
+
+ if (usingNumericMonths()) {
+ mMonthSpinnerInput.setRawInputType(InputType.TYPE_CLASS_NUMBER);
+ }
+ }
+
+ /**
+ * Updates the calendar view with the current date.
+ */
+ private void updateCalendarView() {
+ mCalendarView.setDate(mCurrentDate.getTimeInMillis(), false, false);
+ }
+
+
+ /**
+ * Notifies the listener, if such, for a change in the selected date.
+ */
+ private void notifyDateChanged() {
+ mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ if (mOnDateChangedListener != null) {
+ mOnDateChangedListener.onDateChanged(mDelegator, getYear(), getMonth(),
+ getDayOfMonth());
+ }
+ }
+
+ /**
+ * Sets the IME options for a spinner based on its ordering.
+ *
+ * @param spinner The spinner.
+ * @param spinnerCount The total spinner count.
+ * @param spinnerIndex The index of the given spinner.
+ */
+ private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) {
+ final int imeOptions;
+ if (spinnerIndex < spinnerCount - 1) {
+ imeOptions = EditorInfo.IME_ACTION_NEXT;
+ } else {
+ imeOptions = EditorInfo.IME_ACTION_DONE;
+ }
+ TextView input = (TextView) spinner.findViewById(R.id.numberpicker_input);
+ input.setImeOptions(imeOptions);
+ }
+
+ private void setContentDescriptions() {
+ // Day
+ trySetContentDescription(mDaySpinner, R.id.increment,
+ R.string.date_picker_increment_day_button);
+ trySetContentDescription(mDaySpinner, R.id.decrement,
+ R.string.date_picker_decrement_day_button);
+ // Month
+ trySetContentDescription(mMonthSpinner, R.id.increment,
+ R.string.date_picker_increment_month_button);
+ trySetContentDescription(mMonthSpinner, R.id.decrement,
+ R.string.date_picker_decrement_month_button);
+ // Year
+ trySetContentDescription(mYearSpinner, R.id.increment,
+ R.string.date_picker_increment_year_button);
+ trySetContentDescription(mYearSpinner, R.id.decrement,
+ R.string.date_picker_decrement_year_button);
+ }
+
+ private void trySetContentDescription(View root, int viewId, int contDescResId) {
+ View target = root.findViewById(viewId);
+ if (target != null) {
+ target.setContentDescription(mContext.getString(contDescResId));
+ }
+ }
+
+ private void updateInputState() {
+ // Make sure that if the user changes the value and the IME is active
+ // for one of the inputs if this widget, the IME is closed. If the user
+ // changed the value via the IME and there is a next input the IME will
+ // be shown, otherwise the user chose another means of changing the
+ // value and having the IME up makes no sense.
+ InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
+ if (inputMethodManager != null) {
+ if (inputMethodManager.isActive(mYearSpinnerInput)) {
+ mYearSpinnerInput.clearFocus();
+ inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
+ } else if (inputMethodManager.isActive(mMonthSpinnerInput)) {
+ mMonthSpinnerInput.clearFocus();
+ inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
+ } else if (inputMethodManager.isActive(mDaySpinnerInput)) {
+ mDaySpinnerInput.clearFocus();
+ inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
+ }
}
}
}
diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java
index af6bbcb..45d1403 100644
--- a/core/java/android/widget/DateTimeView.java
+++ b/core/java/android/widget/DateTimeView.java
@@ -27,12 +27,9 @@ import android.text.format.Time;
import android.util.AttributeSet;
import android.util.Log;
import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
import android.widget.TextView;
import android.widget.RemoteViews.RemoteView;
-import com.android.internal.R;
-
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
diff --git a/core/java/android/widget/DialerFilter.java b/core/java/android/widget/DialerFilter.java
index 20bc114..78786e1 100644
--- a/core/java/android/widget/DialerFilter.java
+++ b/core/java/android/widget/DialerFilter.java
@@ -28,8 +28,6 @@ import android.text.method.DialerKeyListener;
import android.text.method.KeyListener;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyCharacterMap;
import android.view.View;
import android.graphics.Rect;
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 57e51c2..3a7cc87 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -56,8 +56,12 @@ public class EditText extends TextView {
this(context, attrs, com.android.internal.R.attr.editTextStyle);
}
- public EditText(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public EditText(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public EditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 9dab7b4..8ea35ff 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -45,8 +45,6 @@ import android.graphics.drawable.Drawable;
import android.inputmethodservice.ExtractEditText;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Message;
-import android.os.Messenger;
import android.os.SystemClock;
import android.provider.Settings;
import android.text.DynamicLayout;
@@ -1346,7 +1344,7 @@ public class Editor {
DisplayList blockDisplayList = mTextDisplayLists[blockIndex];
if (blockDisplayList == null) {
blockDisplayList = mTextDisplayLists[blockIndex] =
- mTextView.getHardwareRenderer().createDisplayList("Text " + blockIndex);
+ DisplayList.create("Text " + blockIndex);
} else {
if (blockIsInvalid) blockDisplayList.clear();
}
@@ -2321,8 +2319,8 @@ public class Editor {
private final HashMap<SuggestionSpan, Integer> mSpansLengths;
private class CustomPopupWindow extends PopupWindow {
- public CustomPopupWindow(Context context, int defStyle) {
- super(context, null, defStyle);
+ public CustomPopupWindow(Context context, int defStyleAttr) {
+ super(context, null, defStyleAttr);
}
@Override
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 7b81aa8..70089e0 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -227,12 +227,16 @@ public class ExpandableListView extends ListView {
this(context, attrs, com.android.internal.R.attr.expandableListViewStyle);
}
- public ExpandableListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public ExpandableListView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ExpandableListView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a =
- context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.ExpandableListView, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.ExpandableListView, defStyleAttr, defStyleRes);
mGroupIndicator = a.getDrawable(
com.android.internal.R.styleable.ExpandableListView_groupIndicator);
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index 01ac8fd..6d38c8f 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -24,7 +24,6 @@ import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.ColorStateList;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -43,7 +42,7 @@ import android.view.ViewConfiguration;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroupOverlay;
import android.widget.AbsListView.OnScrollListener;
-import com.android.internal.R;
+import android.widget.ImageView.ScaleType;
/**
* Helper class for AbsListView to draw and control the Fast Scroll thumb
@@ -76,24 +75,6 @@ class FastScroller {
/** Scroll thumb and preview being dragged by user. */
private static final int STATE_DRAGGING = 2;
- /** Styleable attributes. */
- private static final int[] ATTRS = new int[] {
- android.R.attr.fastScrollTextColor,
- android.R.attr.fastScrollThumbDrawable,
- android.R.attr.fastScrollTrackDrawable,
- android.R.attr.fastScrollPreviewBackgroundLeft,
- android.R.attr.fastScrollPreviewBackgroundRight,
- android.R.attr.fastScrollOverlayPosition
- };
-
- // Styleable attribute indices.
- private static final int TEXT_COLOR = 0;
- private static final int THUMB_DRAWABLE = 1;
- private static final int TRACK_DRAWABLE = 2;
- private static final int PREVIEW_BACKGROUND_LEFT = 3;
- private static final int PREVIEW_BACKGROUND_RIGHT = 4;
- private static final int OVERLAY_POSITION = 5;
-
// Positions for preview image and text.
private static final int OVERLAY_FLOATING = 0;
private static final int OVERLAY_AT_THUMB = 1;
@@ -115,7 +96,7 @@ class FastScroller {
private final TextView mSecondaryText;
private final ImageView mThumbImage;
private final ImageView mTrackImage;
- private final ImageView mPreviewImage;
+ private final View mPreviewImage;
/**
* Preview image resource IDs for left- and right-aligned layouts. See
@@ -127,13 +108,25 @@ class FastScroller {
* Padding in pixels around the preview text. Applied as layout margins to
* the preview text and padding to the preview image.
*/
- private final int mPreviewPadding;
+ private int mPreviewPadding;
+
+ private int mPreviewMinWidth;
+ private int mPreviewMinHeight;
+ private int mThumbMinWidth;
+ private int mThumbMinHeight;
+
+ /** Theme-specified text size. Used only if text appearance is not set. */
+ private float mTextSize;
- /** Whether there is a track image to display. */
- private final boolean mHasTrackImage;
+ /** Theme-specified text color. Used only if text appearance is not set. */
+ private ColorStateList mTextColor;
+
+ private Drawable mThumbDrawable;
+ private Drawable mTrackDrawable;
+ private int mTextAppearance;
/** Total width of decorations. */
- private final int mWidth;
+ private int mWidth;
/** Set containing decoration transition animations. */
private AnimatorSet mDecorAnimation;
@@ -245,89 +238,143 @@ class FastScroller {
}
};
- public FastScroller(AbsListView listView) {
+ public FastScroller(AbsListView listView, int styleResId) {
mList = listView;
- mOverlay = listView.getOverlay();
final Context context = listView.getContext();
mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mScrollBarStyle = listView.getScrollBarStyle();
+
+ mScrollCompleted = true;
+ mState = STATE_VISIBLE;
+ mMatchDragPosition =
+ context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB;
+
+ mTrackImage = new ImageView(context);
+ mTrackImage.setScaleType(ScaleType.FIT_XY);
+ mThumbImage = new ImageView(context);
+ mThumbImage.setScaleType(ScaleType.FIT_XY);
+ mPreviewImage = new View(context);
+ mPreviewImage.setAlpha(0f);
+
+ mPrimaryText = createPreviewTextView(context);
+ mSecondaryText = createPreviewTextView(context);
+
+ setStyle(styleResId);
- final Resources res = context.getResources();
- final TypedArray ta = context.getTheme().obtainStyledAttributes(ATTRS);
+ final ViewGroupOverlay overlay = listView.getOverlay();
+ mOverlay = overlay;
+ overlay.add(mTrackImage);
+ overlay.add(mThumbImage);
+ overlay.add(mPreviewImage);
+ overlay.add(mPrimaryText);
+ overlay.add(mSecondaryText);
- final ImageView trackImage = new ImageView(context);
- mTrackImage = trackImage;
+ getSectionsFromIndexer();
+ updateLongList(listView.getChildCount(), listView.getCount());
+ setScrollbarPosition(listView.getVerticalScrollbarPosition());
+ postAutoHide();
+ }
+ private void updateAppearance() {
+ final Context context = mList.getContext();
int width = 0;
// Add track to overlay if it has an image.
- final Drawable trackDrawable = ta.getDrawable(TRACK_DRAWABLE);
- if (trackDrawable != null) {
- mHasTrackImage = true;
- trackImage.setBackground(trackDrawable);
- mOverlay.add(trackImage);
- width = Math.max(width, trackDrawable.getIntrinsicWidth());
- } else {
- mHasTrackImage = false;
+ mTrackImage.setImageDrawable(mTrackDrawable);
+ if (mTrackDrawable != null) {
+ width = Math.max(width, mTrackDrawable.getIntrinsicWidth());
}
- final ImageView thumbImage = new ImageView(context);
- mThumbImage = thumbImage;
-
// Add thumb to overlay if it has an image.
- final Drawable thumbDrawable = ta.getDrawable(THUMB_DRAWABLE);
- if (thumbDrawable != null) {
- thumbImage.setImageDrawable(thumbDrawable);
- mOverlay.add(thumbImage);
- width = Math.max(width, thumbDrawable.getIntrinsicWidth());
+ mThumbImage.setImageDrawable(mThumbDrawable);
+ mThumbImage.setMinimumWidth(mThumbMinWidth);
+ mThumbImage.setMinimumHeight(mThumbMinHeight);
+ if (mThumbDrawable != null) {
+ width = Math.max(width, mThumbDrawable.getIntrinsicWidth());
}
- // If necessary, apply minimum thumb width and height.
- if (thumbDrawable.getIntrinsicWidth() <= 0 || thumbDrawable.getIntrinsicHeight() <= 0) {
- final int minWidth = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_width);
- thumbImage.setMinimumWidth(minWidth);
- thumbImage.setMinimumHeight(
- res.getDimensionPixelSize(R.dimen.fastscroll_thumb_height));
- width = Math.max(width, minWidth);
- }
+ // Account for minimum thumb width.
+ mWidth = Math.max(width, mThumbMinWidth);
- mWidth = width;
+ mPreviewImage.setMinimumWidth(mPreviewMinWidth);
+ mPreviewImage.setMinimumHeight(mPreviewMinHeight);
- final int previewSize = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_size);
- mPreviewImage = new ImageView(context);
- mPreviewImage.setMinimumWidth(previewSize);
- mPreviewImage.setMinimumHeight(previewSize);
- mPreviewImage.setAlpha(0f);
- mOverlay.add(mPreviewImage);
+ if (mTextAppearance != 0) {
+ mPrimaryText.setTextAppearance(context, mTextAppearance);
+ mSecondaryText.setTextAppearance(context, mTextAppearance);
+ }
- mPreviewPadding = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_padding);
+ if (mTextColor != null) {
+ mPrimaryText.setTextColor(mTextColor);
+ mSecondaryText.setTextColor(mTextColor);
+ }
+
+ if (mTextSize > 0) {
+ mPrimaryText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
+ mSecondaryText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
+ }
- final int textMinSize = Math.max(0, previewSize - mPreviewPadding);
- mPrimaryText = createPreviewTextView(context, ta);
+ final int textMinSize = Math.max(0, mPreviewMinHeight);
mPrimaryText.setMinimumWidth(textMinSize);
mPrimaryText.setMinimumHeight(textMinSize);
- mOverlay.add(mPrimaryText);
- mSecondaryText = createPreviewTextView(context, ta);
mSecondaryText.setMinimumWidth(textMinSize);
mSecondaryText.setMinimumHeight(textMinSize);
- mOverlay.add(mSecondaryText);
- mPreviewResId[PREVIEW_LEFT] = ta.getResourceId(PREVIEW_BACKGROUND_LEFT, 0);
- mPreviewResId[PREVIEW_RIGHT] = ta.getResourceId(PREVIEW_BACKGROUND_RIGHT, 0);
- mOverlayPosition = ta.getInt(OVERLAY_POSITION, OVERLAY_FLOATING);
- ta.recycle();
+ refreshDrawablePressedState();
+ }
- mScrollBarStyle = listView.getScrollBarStyle();
- mScrollCompleted = true;
- mState = STATE_VISIBLE;
- mMatchDragPosition = context.getApplicationInfo().targetSdkVersion
- >= Build.VERSION_CODES.HONEYCOMB;
+ public void setStyle(int resId) {
+ final Context context = mList.getContext();
+ final TypedArray ta = context.obtainStyledAttributes(null,
+ com.android.internal.R.styleable.FastScroll, android.R.attr.fastScrollStyle, resId);
+ final int N = ta.getIndexCount();
+ for (int i = 0; i < N; i++) {
+ final int index = ta.getIndex(i);
+ switch (index) {
+ case com.android.internal.R.styleable.FastScroll_position:
+ mOverlayPosition = ta.getInt(index, OVERLAY_FLOATING);
+ break;
+ case com.android.internal.R.styleable.FastScroll_backgroundLeft:
+ mPreviewResId[PREVIEW_LEFT] = ta.getResourceId(index, 0);
+ break;
+ case com.android.internal.R.styleable.FastScroll_backgroundRight:
+ mPreviewResId[PREVIEW_RIGHT] = ta.getResourceId(index, 0);
+ break;
+ case com.android.internal.R.styleable.FastScroll_thumbDrawable:
+ mThumbDrawable = ta.getDrawable(index);
+ break;
+ case com.android.internal.R.styleable.FastScroll_trackDrawable:
+ mTrackDrawable = ta.getDrawable(index);
+ break;
+ case com.android.internal.R.styleable.FastScroll_textAppearance:
+ mTextAppearance = ta.getResourceId(index, 0);
+ break;
+ case com.android.internal.R.styleable.FastScroll_textColor:
+ mTextColor = ta.getColorStateList(index);
+ break;
+ case com.android.internal.R.styleable.FastScroll_textSize:
+ mTextSize = ta.getDimensionPixelSize(index, 0);
+ break;
+ case com.android.internal.R.styleable.FastScroll_minWidth:
+ mPreviewMinWidth = ta.getDimensionPixelSize(index, 0);
+ break;
+ case com.android.internal.R.styleable.FastScroll_minHeight:
+ mPreviewMinHeight = ta.getDimensionPixelSize(index, 0);
+ break;
+ case com.android.internal.R.styleable.FastScroll_thumbMinWidth:
+ mThumbMinWidth = ta.getDimensionPixelSize(index, 0);
+ break;
+ case com.android.internal.R.styleable.FastScroll_thumbMinHeight:
+ mThumbMinHeight = ta.getDimensionPixelSize(index, 0);
+ break;
+ case com.android.internal.R.styleable.FastScroll_padding:
+ mPreviewPadding = ta.getDimensionPixelSize(index, 0);
+ break;
+ }
+ }
- getSectionsFromIndexer();
- refreshDrawablePressedState();
- updateLongList(listView.getChildCount(), listView.getCount());
- setScrollbarPosition(mList.getVerticalScrollbarPosition());
- postAutoHide();
+ updateAppearance();
}
/**
@@ -469,17 +516,11 @@ class FastScroller {
/**
* Creates a view into which preview text can be placed.
*/
- private TextView createPreviewTextView(Context context, TypedArray ta) {
+ private TextView createPreviewTextView(Context context) {
final LayoutParams params = new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
- final Resources res = context.getResources();
- final int minSize = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_size);
- final ColorStateList textColor = ta.getColorStateList(TEXT_COLOR);
- final float textSize = res.getDimensionPixelSize(R.dimen.fastscroll_overlay_text_size);
final TextView textView = new TextView(context);
textView.setLayoutParams(params);
- textView.setTextColor(textColor);
- textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
textView.setSingleLine(true);
textView.setEllipsize(TruncateAt.MIDDLE);
textView.setGravity(Gravity.CENTER);
@@ -603,7 +644,7 @@ class FastScroller {
view.measure(widthMeasureSpec, heightMeasureSpec);
// Align to the left or right.
- final int width = view.getMeasuredWidth();
+ final int width = Math.min(adjMaxWidth, view.getMeasuredWidth());
final int left;
final int right;
if (mLayoutFromRight) {
@@ -1020,7 +1061,7 @@ class FastScroller {
}
final Rect bounds = mTempBounds;
- final ImageView preview = mPreviewImage;
+ final View preview = mPreviewImage;
final TextView showing;
final TextView target;
if (mShowingPrimary) {
@@ -1046,10 +1087,10 @@ class FastScroller {
hideShowing.addListener(mSwitchPrimaryListener);
// Apply preview image padding and animate bounds, if necessary.
- bounds.left -= mPreviewImage.getPaddingLeft();
- bounds.top -= mPreviewImage.getPaddingTop();
- bounds.right += mPreviewImage.getPaddingRight();
- bounds.bottom += mPreviewImage.getPaddingBottom();
+ bounds.left -= preview.getPaddingLeft();
+ bounds.top -= preview.getPaddingTop();
+ bounds.right += preview.getPaddingRight();
+ bounds.bottom += preview.getPaddingBottom();
final Animator resizePreview = animateBounds(preview, bounds);
resizePreview.setDuration(DURATION_RESIZE);
@@ -1097,8 +1138,8 @@ class FastScroller {
final int top = container.top;
final int bottom = container.bottom;
- final ImageView trackImage = mTrackImage;
- final ImageView thumbImage = mThumbImage;
+ final View trackImage = mTrackImage;
+ final View thumbImage = mThumbImage;
final float min = trackImage.getTop();
final float max = trackImage.getBottom();
final float offset = min;
@@ -1109,7 +1150,7 @@ class FastScroller {
final float previewPos = mOverlayPosition == OVERLAY_AT_THUMB ? thumbMiddle : 0;
// Center the preview on the thumb, constrained to the list bounds.
- final ImageView previewImage = mPreviewImage;
+ final View previewImage = mPreviewImage;
final float previewHalfHeight = previewImage.getHeight() / 2f;
final float minP = top + previewHalfHeight;
final float maxP = bottom - previewHalfHeight;
@@ -1122,11 +1163,7 @@ class FastScroller {
}
private float getPosFromMotionEvent(float y) {
- final Rect container = mContainerRect;
- final int top = container.top;
- final int bottom = container.bottom;
-
- final ImageView trackImage = mTrackImage;
+ final View trackImage = mTrackImage;
final float min = trackImage.getTop();
final float max = trackImage.getBottom();
final float offset = min;
@@ -1393,7 +1430,7 @@ class FastScroller {
* @return Whether the coordinate is inside the scroller's activation area.
*/
private boolean isPointInside(float x, float y) {
- return isPointInsideX(x) && (mHasTrackImage || isPointInsideY(y));
+ return isPointInsideX(x) && (mTrackDrawable != null || isPointInsideY(y));
}
private boolean isPointInsideX(float x) {
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index d9d4ad7..b029328 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -97,11 +97,15 @@ public class FrameLayout extends ViewGroup {
this(context, attrs, 0);
}
- public FrameLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public FrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public FrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout,
- defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.FrameLayout, defStyleAttr, defStyleRes);
mForegroundGravity = a.getInt(
com.android.internal.R.styleable.FrameLayout_foregroundGravity, mForegroundGravity);
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index 78ba6e0..f7c839f 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -196,14 +196,18 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
this(context, attrs, R.attr.galleryStyle);
}
- public Gallery(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public Gallery(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public Gallery(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
mGestureDetector = new GestureDetector(context, this);
mGestureDetector.setIsLongpressEnabled(true);
-
- TypedArray a = context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.Gallery, defStyle, 0);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.Gallery, defStyleAttr, defStyleRes);
int index = a.getInt(com.android.internal.R.styleable.Gallery_gravity, -1);
if (index >= 0) {
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 54cc3f4..8511601 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.IntDef;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -35,6 +36,8 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
import com.android.internal.R;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
@@ -165,6 +168,11 @@ public class GridLayout extends ViewGroup {
// Public constants
+ /** @hide */
+ @IntDef({HORIZONTAL, VERTICAL})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Orientation {}
+
/**
* The horizontal orientation.
*/
@@ -186,6 +194,11 @@ public class GridLayout extends ViewGroup {
*/
public static final int UNDEFINED = Integer.MIN_VALUE;
+ /** @hide */
+ @IntDef({ALIGN_BOUNDS, ALIGN_MARGINS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AlignmentMode {}
+
/**
* This constant is an {@link #setAlignmentMode(int) alignmentMode}.
* When the {@code alignmentMode} is set to {@link #ALIGN_BOUNDS}, alignment
@@ -262,13 +275,23 @@ public class GridLayout extends ViewGroup {
// Constructors
- /**
- * {@inheritDoc}
- */
- public GridLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public GridLayout(Context context) {
+ this(context, null);
+ }
+
+ public GridLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public GridLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public GridLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
mDefaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap);
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.GridLayout, defStyleAttr, defStyleRes);
try {
setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT));
setColumnCount(a.getInt(COLUMN_COUNT, DEFAULT_COUNT));
@@ -282,21 +305,6 @@ public class GridLayout extends ViewGroup {
}
}
- /**
- * {@inheritDoc}
- */
- public GridLayout(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- /**
- * {@inheritDoc}
- */
- public GridLayout(Context context) {
- //noinspection NullableProblems
- this(context, null);
- }
-
// Implementation
/**
@@ -308,6 +316,7 @@ public class GridLayout extends ViewGroup {
*
* @attr ref android.R.styleable#GridLayout_orientation
*/
+ @Orientation
public int getOrientation() {
return mOrientation;
}
@@ -348,7 +357,7 @@ public class GridLayout extends ViewGroup {
*
* @attr ref android.R.styleable#GridLayout_orientation
*/
- public void setOrientation(int orientation) {
+ public void setOrientation(@Orientation int orientation) {
if (this.mOrientation != orientation) {
this.mOrientation = orientation;
invalidateStructure();
@@ -479,6 +488,7 @@ public class GridLayout extends ViewGroup {
*
* @attr ref android.R.styleable#GridLayout_alignmentMode
*/
+ @AlignmentMode
public int getAlignmentMode() {
return mAlignmentMode;
}
@@ -498,7 +508,7 @@ public class GridLayout extends ViewGroup {
*
* @attr ref android.R.styleable#GridLayout_alignmentMode
*/
- public void setAlignmentMode(int alignmentMode) {
+ public void setAlignmentMode(@AlignmentMode int alignmentMode) {
this.mAlignmentMode = alignmentMode;
requestLayout();
}
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 15daf83..acd711d 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -16,12 +16,14 @@
package android.widget;
+import android.annotation.IntDef;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.os.Trace;
import android.util.AttributeSet;
+import android.util.MathUtils;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.SoundEffectConstants;
@@ -33,9 +35,11 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
import android.view.animation.GridLayoutAnimationController;
-import android.widget.AbsListView.LayoutParams;
import android.widget.RemoteViews.RemoteView;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A view that shows items in two-dimensional scrolling grid. The items in the
@@ -53,6 +57,11 @@ import android.widget.RemoteViews.RemoteView;
*/
@RemoteView
public class GridView extends AbsListView {
+ /** @hide */
+ @IntDef({NO_STRETCH, STRETCH_SPACING, STRETCH_COLUMN_WIDTH, STRETCH_SPACING_UNIFORM})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StretchMode {}
+
/**
* Disables stretching.
*
@@ -110,11 +119,15 @@ public class GridView extends AbsListView {
this(context, attrs, com.android.internal.R.attr.gridViewStyle);
}
- public GridView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public GridView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public GridView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.GridView, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.GridView, defStyleAttr, defStyleRes);
int hSpacing = a.getDimensionPixelOffset(
com.android.internal.R.styleable.GridView_horizontalSpacing, 0);
@@ -1202,6 +1215,24 @@ public class GridView extends AbsListView {
setSelectedPositionInt(mNextSelectedPosition);
+ // Remember which child, if any, had accessibility focus.
+ final int accessibilityFocusPosition;
+ final View accessFocusedChild = getAccessibilityFocusedChild();
+ if (accessFocusedChild != null) {
+ accessibilityFocusPosition = getPositionForView(accessFocusedChild);
+ accessFocusedChild.setHasTransientState(true);
+ } else {
+ accessibilityFocusPosition = INVALID_POSITION;
+ }
+
+ // Ensure the child containing focus, if any, has transient state.
+ // If the list data hasn't changed, or if the adapter has stable
+ // IDs, this will maintain focus.
+ final View focusedChild = getFocusedChild();
+ if (focusedChild != null) {
+ focusedChild.setHasTransientState(true);
+ }
+
// Pull all children into the RecycleBin.
// These views will be reused if possible
final int firstPosition = mFirstPosition;
@@ -1216,7 +1247,6 @@ public class GridView extends AbsListView {
}
// Clear out old views
- //removeAllViewsInLayout();
detachAllViewsFromParent();
recycleBin.removeSkippedScrap();
@@ -1287,6 +1317,27 @@ public class GridView extends AbsListView {
mSelectorRect.setEmpty();
}
+ if (accessFocusedChild != null) {
+ accessFocusedChild.setHasTransientState(false);
+
+ // If we failed to maintain accessibility focus on the previous
+ // view, attempt to restore it to the previous position.
+ if (!accessFocusedChild.isAccessibilityFocused()
+ && accessibilityFocusPosition != INVALID_POSITION) {
+ // Bound the position within the visible children.
+ final int position = MathUtils.constrain(
+ accessibilityFocusPosition - mFirstPosition, 0, getChildCount() - 1);
+ final View restoreView = getChildAt(position);
+ if (restoreView != null) {
+ restoreView.requestAccessibilityFocus();
+ }
+ }
+ }
+
+ if (focusedChild != null) {
+ focusedChild.setHasTransientState(false);
+ }
+
mLayoutMode = LAYOUT_NORMAL;
mDataChanged = false;
if (mPositionScrollAfterLayout != null) {
@@ -2056,13 +2107,14 @@ public class GridView extends AbsListView {
*
* @attr ref android.R.styleable#GridView_stretchMode
*/
- public void setStretchMode(int stretchMode) {
+ public void setStretchMode(@StretchMode int stretchMode) {
if (stretchMode != mStretchMode) {
mStretchMode = stretchMode;
requestLayoutIfNecessary();
}
}
+ @StretchMode
public int getStretchMode() {
return mStretchMode;
}
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index dab0962..25d4f42 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -146,12 +146,17 @@ public class HorizontalScrollView extends FrameLayout {
this(context, attrs, com.android.internal.R.attr.horizontalScrollViewStyle);
}
- public HorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public HorizontalScrollView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
initScrollView();
- TypedArray a = context.obtainStyledAttributes(attrs,
- android.R.styleable.HorizontalScrollView, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, android.R.styleable.HorizontalScrollView, defStyleAttr, defStyleRes);
setFillViewport(a.getBoolean(android.R.styleable.HorizontalScrollView_fillViewport, false));
diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java
index 379354c..3a20628 100644
--- a/core/java/android/widget/ImageButton.java
+++ b/core/java/android/widget/ImageButton.java
@@ -17,16 +17,11 @@
package android.widget;
import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
import android.util.AttributeSet;
-import android.view.MotionEvent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
-import java.util.Map;
-
/**
* <p>
* Displays a button with an image (instead of text) that can be pressed
@@ -83,8 +78,12 @@ public class ImageButton extends ImageView {
this(context, attrs, com.android.internal.R.attr.imageButtonStyle);
}
- public ImageButton(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public ImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ImageButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
setFocusable(true);
}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 7daf798..79d5e5d 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -119,12 +119,17 @@ public class ImageView extends View {
this(context, attrs, 0);
}
- public ImageView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public ImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
initImageView();
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.ImageView, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.ImageView, defStyleAttr, defStyleRes);
Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);
if (d != null) {
diff --git a/core/java/android/widget/LegacyTimePickerDelegate.java b/core/java/android/widget/LegacyTimePickerDelegate.java
new file mode 100644
index 0000000..1634d5f
--- /dev/null
+++ b/core/java/android/widget/LegacyTimePickerDelegate.java
@@ -0,0 +1,638 @@
+/*
+ * Copyright (C) 2013 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.Configuration;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import com.android.internal.R;
+
+import java.text.DateFormatSymbols;
+import java.util.Calendar;
+import java.util.Locale;
+
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
+
+/**
+ * A delegate implementing the basic TimePicker
+ */
+class LegacyTimePickerDelegate extends TimePicker.AbstractTimePickerDelegate {
+
+ private static final boolean DEFAULT_ENABLED_STATE = true;
+
+ private static final int HOURS_IN_HALF_DAY = 12;
+
+ // state
+ private boolean mIs24HourView;
+
+ private boolean mIsAm;
+
+ // ui components
+ private final NumberPicker mHourSpinner;
+
+ private final NumberPicker mMinuteSpinner;
+
+ private final NumberPicker mAmPmSpinner;
+
+ private final EditText mHourSpinnerInput;
+
+ private final EditText mMinuteSpinnerInput;
+
+ private final EditText mAmPmSpinnerInput;
+
+ private final TextView mDivider;
+
+ // Note that the legacy implementation of the TimePicker is
+ // using a button for toggling between AM/PM while the new
+ // version uses a NumberPicker spinner. Therefore the code
+ // accommodates these two cases to be backwards compatible.
+ private final Button mAmPmButton;
+
+ private final String[] mAmPmStrings;
+
+ private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
+
+ private Calendar mTempCalendar;
+
+ private boolean mHourWithTwoDigit;
+ private char mHourFormat;
+
+ /**
+ * A no-op callback used in the constructor to avoid null checks later in
+ * the code.
+ */
+ private static final TimePicker.OnTimeChangedListener NO_OP_CHANGE_LISTENER =
+ new TimePicker.OnTimeChangedListener() {
+ public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
+ }
+ };
+
+ public LegacyTimePickerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(delegator, context);
+
+ // process style attributes
+ final TypedArray attributesArray = mContext.obtainStyledAttributes(
+ attrs, R.styleable.TimePicker, defStyleAttr, defStyleRes);
+ final int layoutResourceId = attributesArray.getResourceId(
+ R.styleable.TimePicker_legacyLayout, R.layout.time_picker_legacy);
+ attributesArray.recycle();
+
+ final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(layoutResourceId, mDelegator, true);
+
+ // hour
+ mHourSpinner = (NumberPicker) delegator.findViewById(R.id.hour);
+ mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
+ public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
+ updateInputState();
+ if (!is24HourView()) {
+ if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) ||
+ (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) {
+ mIsAm = !mIsAm;
+ updateAmPmControl();
+ }
+ }
+ onTimeChanged();
+ }
+ });
+ mHourSpinnerInput = (EditText) mHourSpinner.findViewById(R.id.numberpicker_input);
+ mHourSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
+
+ // divider (only for the new widget style)
+ mDivider = (TextView) mDelegator.findViewById(R.id.divider);
+ if (mDivider != null) {
+ setDividerText();
+ }
+
+ // minute
+ mMinuteSpinner = (NumberPicker) mDelegator.findViewById(R.id.minute);
+ mMinuteSpinner.setMinValue(0);
+ mMinuteSpinner.setMaxValue(59);
+ mMinuteSpinner.setOnLongPressUpdateInterval(100);
+ mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
+ mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
+ public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
+ updateInputState();
+ int minValue = mMinuteSpinner.getMinValue();
+ int maxValue = mMinuteSpinner.getMaxValue();
+ if (oldVal == maxValue && newVal == minValue) {
+ int newHour = mHourSpinner.getValue() + 1;
+ if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) {
+ mIsAm = !mIsAm;
+ updateAmPmControl();
+ }
+ mHourSpinner.setValue(newHour);
+ } else if (oldVal == minValue && newVal == maxValue) {
+ int newHour = mHourSpinner.getValue() - 1;
+ if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) {
+ mIsAm = !mIsAm;
+ updateAmPmControl();
+ }
+ mHourSpinner.setValue(newHour);
+ }
+ onTimeChanged();
+ }
+ });
+ mMinuteSpinnerInput = (EditText) mMinuteSpinner.findViewById(R.id.numberpicker_input);
+ mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
+
+ /* Get the localized am/pm strings and use them in the spinner */
+ mAmPmStrings = new DateFormatSymbols().getAmPmStrings();
+
+ // am/pm
+ View amPmView = mDelegator.findViewById(R.id.amPm);
+ if (amPmView instanceof Button) {
+ mAmPmSpinner = null;
+ mAmPmSpinnerInput = null;
+ mAmPmButton = (Button) amPmView;
+ mAmPmButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View button) {
+ button.requestFocus();
+ mIsAm = !mIsAm;
+ updateAmPmControl();
+ onTimeChanged();
+ }
+ });
+ } else {
+ mAmPmButton = null;
+ mAmPmSpinner = (NumberPicker) amPmView;
+ mAmPmSpinner.setMinValue(0);
+ mAmPmSpinner.setMaxValue(1);
+ mAmPmSpinner.setDisplayedValues(mAmPmStrings);
+ mAmPmSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
+ public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
+ updateInputState();
+ picker.requestFocus();
+ mIsAm = !mIsAm;
+ updateAmPmControl();
+ onTimeChanged();
+ }
+ });
+ mAmPmSpinnerInput = (EditText) mAmPmSpinner.findViewById(R.id.numberpicker_input);
+ mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
+ }
+
+ if (isAmPmAtStart()) {
+ // Move the am/pm view to the beginning
+ ViewGroup amPmParent = (ViewGroup) delegator.findViewById(R.id.timePickerLayout);
+ amPmParent.removeView(amPmView);
+ amPmParent.addView(amPmView, 0);
+ // Swap layout margins if needed. They may be not symmetrical (Old Standard Theme
+ // for example and not for Holo Theme)
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) amPmView.getLayoutParams();
+ final int startMargin = lp.getMarginStart();
+ final int endMargin = lp.getMarginEnd();
+ if (startMargin != endMargin) {
+ lp.setMarginStart(endMargin);
+ lp.setMarginEnd(startMargin);
+ }
+ }
+
+ getHourFormatData();
+
+ // update controls to initial state
+ updateHourControl();
+ updateMinuteControl();
+ updateAmPmControl();
+
+ setOnTimeChangedListener(NO_OP_CHANGE_LISTENER);
+
+ // set to current time
+ setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
+ setCurrentMinute(mTempCalendar.get(Calendar.MINUTE));
+
+ if (!isEnabled()) {
+ setEnabled(false);
+ }
+
+ // set the content descriptions
+ setContentDescriptions();
+
+ // If not explicitly specified this view is important for accessibility.
+ if (mDelegator.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+ }
+
+ private void getHourFormatData() {
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+ (mIs24HourView) ? "Hm" : "hm");
+ final int lengthPattern = bestDateTimePattern.length();
+ mHourWithTwoDigit = false;
+ char hourFormat = '\0';
+ // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
+ // the hour format that we found.
+ for (int i = 0; i < lengthPattern; i++) {
+ final char c = bestDateTimePattern.charAt(i);
+ if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
+ mHourFormat = c;
+ if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
+ mHourWithTwoDigit = true;
+ }
+ break;
+ }
+ }
+ }
+
+ private boolean isAmPmAtStart() {
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+ "hm" /* skeleton */);
+
+ return bestDateTimePattern.startsWith("a");
+ }
+
+ /**
+ * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":".
+ *
+ * See http://unicode.org/cldr/trac/browser/trunk/common/main
+ *
+ * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the
+ * separator as the character which is just after the hour marker in the returned pattern.
+ */
+ private void setDividerText() {
+ final String skeleton = (mIs24HourView) ? "Hm" : "hm";
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+ skeleton);
+ final String separatorText;
+ int hourIndex = bestDateTimePattern.lastIndexOf('H');
+ if (hourIndex == -1) {
+ hourIndex = bestDateTimePattern.lastIndexOf('h');
+ }
+ if (hourIndex == -1) {
+ // Default case
+ separatorText = ":";
+ } else {
+ int minuteIndex = bestDateTimePattern.indexOf('m', hourIndex + 1);
+ if (minuteIndex == -1) {
+ separatorText = Character.toString(bestDateTimePattern.charAt(hourIndex + 1));
+ } else {
+ separatorText = bestDateTimePattern.substring(hourIndex + 1, minuteIndex);
+ }
+ }
+ mDivider.setText(separatorText);
+ }
+
+ @Override
+ public void setCurrentHour(Integer currentHour) {
+ setCurrentHour(currentHour, true);
+ }
+
+ private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) {
+ // why was Integer used in the first place?
+ if (currentHour == null || currentHour == getCurrentHour()) {
+ return;
+ }
+ if (!is24HourView()) {
+ // convert [0,23] ordinal to wall clock display
+ if (currentHour >= HOURS_IN_HALF_DAY) {
+ mIsAm = false;
+ if (currentHour > HOURS_IN_HALF_DAY) {
+ currentHour = currentHour - HOURS_IN_HALF_DAY;
+ }
+ } else {
+ mIsAm = true;
+ if (currentHour == 0) {
+ currentHour = HOURS_IN_HALF_DAY;
+ }
+ }
+ updateAmPmControl();
+ }
+ mHourSpinner.setValue(currentHour);
+ if (notifyTimeChanged) {
+ onTimeChanged();
+ }
+ }
+
+ @Override
+ public Integer getCurrentHour() {
+ int currentHour = mHourSpinner.getValue();
+ if (is24HourView()) {
+ return currentHour;
+ } else if (mIsAm) {
+ return currentHour % HOURS_IN_HALF_DAY;
+ } else {
+ return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
+ }
+ }
+
+ @Override
+ public void setCurrentMinute(Integer currentMinute) {
+ if (currentMinute == getCurrentMinute()) {
+ return;
+ }
+ mMinuteSpinner.setValue(currentMinute);
+ onTimeChanged();
+ }
+
+ @Override
+ public Integer getCurrentMinute() {
+ return mMinuteSpinner.getValue();
+ }
+
+ @Override
+ public void setIs24HourView(Boolean is24HourView) {
+ if (mIs24HourView == is24HourView) {
+ return;
+ }
+ // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!!
+ int currentHour = getCurrentHour();
+ // Order is important here.
+ mIs24HourView = is24HourView;
+ getHourFormatData();
+ updateHourControl();
+ // set value after spinner range is updated
+ setCurrentHour(currentHour, false);
+ updateMinuteControl();
+ updateAmPmControl();
+ }
+
+ @Override
+ public boolean is24HourView() {
+ return mIs24HourView;
+ }
+
+ @Override
+ public void setOnTimeChangedListener(TimePicker.OnTimeChangedListener onTimeChangedListener) {
+ mOnTimeChangedListener = onTimeChangedListener;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ mMinuteSpinner.setEnabled(enabled);
+ if (mDivider != null) {
+ mDivider.setEnabled(enabled);
+ }
+ mHourSpinner.setEnabled(enabled);
+ if (mAmPmSpinner != null) {
+ mAmPmSpinner.setEnabled(enabled);
+ } else {
+ mAmPmButton.setEnabled(enabled);
+ }
+ mIsEnabled = enabled;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ @Override
+ public void setShowDoneButton(boolean showDoneButton) {
+ // Nothing to do
+ }
+
+ @Override
+ public void setDismissCallback(TimePicker.TimePickerDismissCallback callback) {
+ // Nothing to do
+ }
+
+ @Override
+ public int getBaseline() {
+ return mHourSpinner.getBaseline();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ setCurrentLocale(newConfig.locale);
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState(Parcelable superState) {
+ return new SavedState(superState, getCurrentHour(), getCurrentMinute());
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+ setCurrentHour(ss.getHour());
+ setCurrentMinute(ss.getMinute());
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ onPopulateAccessibilityEvent(event);
+ return true;
+ }
+
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ int flags = DateUtils.FORMAT_SHOW_TIME;
+ if (mIs24HourView) {
+ flags |= DateUtils.FORMAT_24HOUR;
+ } else {
+ flags |= DateUtils.FORMAT_12HOUR;
+ }
+ mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
+ mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
+ String selectedDateUtterance = DateUtils.formatDateTime(mContext,
+ mTempCalendar.getTimeInMillis(), flags);
+ event.getText().add(selectedDateUtterance);
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ event.setClassName(TimePicker.class.getName());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ info.setClassName(TimePicker.class.getName());
+ }
+
+ private void updateInputState() {
+ // Make sure that if the user changes the value and the IME is active
+ // for one of the inputs if this widget, the IME is closed. If the user
+ // changed the value via the IME and there is a next input the IME will
+ // be shown, otherwise the user chose another means of changing the
+ // value and having the IME up makes no sense.
+ InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
+ if (inputMethodManager != null) {
+ if (inputMethodManager.isActive(mHourSpinnerInput)) {
+ mHourSpinnerInput.clearFocus();
+ inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
+ } else if (inputMethodManager.isActive(mMinuteSpinnerInput)) {
+ mMinuteSpinnerInput.clearFocus();
+ inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
+ } else if (inputMethodManager.isActive(mAmPmSpinnerInput)) {
+ mAmPmSpinnerInput.clearFocus();
+ inputMethodManager.hideSoftInputFromWindow(mDelegator.getWindowToken(), 0);
+ }
+ }
+ }
+
+ private void updateAmPmControl() {
+ if (is24HourView()) {
+ if (mAmPmSpinner != null) {
+ mAmPmSpinner.setVisibility(View.GONE);
+ } else {
+ mAmPmButton.setVisibility(View.GONE);
+ }
+ } else {
+ int index = mIsAm ? Calendar.AM : Calendar.PM;
+ if (mAmPmSpinner != null) {
+ mAmPmSpinner.setValue(index);
+ mAmPmSpinner.setVisibility(View.VISIBLE);
+ } else {
+ mAmPmButton.setText(mAmPmStrings[index]);
+ mAmPmButton.setVisibility(View.VISIBLE);
+ }
+ }
+ mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ }
+
+ /**
+ * Sets the current locale.
+ *
+ * @param locale The current locale.
+ */
+ @Override
+ public void setCurrentLocale(Locale locale) {
+ super.setCurrentLocale(locale);
+ mTempCalendar = Calendar.getInstance(locale);
+ }
+
+ private void onTimeChanged() {
+ mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ if (mOnTimeChangedListener != null) {
+ mOnTimeChangedListener.onTimeChanged(mDelegator, getCurrentHour(),
+ getCurrentMinute());
+ }
+ }
+
+ private void updateHourControl() {
+ if (is24HourView()) {
+ // 'k' means 1-24 hour
+ if (mHourFormat == 'k') {
+ mHourSpinner.setMinValue(1);
+ mHourSpinner.setMaxValue(24);
+ } else {
+ mHourSpinner.setMinValue(0);
+ mHourSpinner.setMaxValue(23);
+ }
+ } else {
+ // 'K' means 0-11 hour
+ if (mHourFormat == 'K') {
+ mHourSpinner.setMinValue(0);
+ mHourSpinner.setMaxValue(11);
+ } else {
+ mHourSpinner.setMinValue(1);
+ mHourSpinner.setMaxValue(12);
+ }
+ }
+ mHourSpinner.setFormatter(mHourWithTwoDigit ? NumberPicker.getTwoDigitFormatter() : null);
+ }
+
+ private void updateMinuteControl() {
+ if (is24HourView()) {
+ mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
+ } else {
+ mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
+ }
+ }
+
+ private void setContentDescriptions() {
+ // Minute
+ trySetContentDescription(mMinuteSpinner, R.id.increment,
+ R.string.time_picker_increment_minute_button);
+ trySetContentDescription(mMinuteSpinner, R.id.decrement,
+ R.string.time_picker_decrement_minute_button);
+ // Hour
+ trySetContentDescription(mHourSpinner, R.id.increment,
+ R.string.time_picker_increment_hour_button);
+ trySetContentDescription(mHourSpinner, R.id.decrement,
+ R.string.time_picker_decrement_hour_button);
+ // AM/PM
+ if (mAmPmSpinner != null) {
+ trySetContentDescription(mAmPmSpinner, R.id.increment,
+ R.string.time_picker_increment_set_pm_button);
+ trySetContentDescription(mAmPmSpinner, R.id.decrement,
+ R.string.time_picker_decrement_set_am_button);
+ }
+ }
+
+ private void trySetContentDescription(View root, int viewId, int contDescResId) {
+ View target = root.findViewById(viewId);
+ if (target != null) {
+ target.setContentDescription(mContext.getString(contDescResId));
+ }
+ }
+
+ /**
+ * Used to save / restore state of time picker
+ */
+ private static class SavedState extends View.BaseSavedState {
+
+ private final int mHour;
+
+ private final int mMinute;
+
+ private SavedState(Parcelable superState, int hour, int minute) {
+ super(superState);
+ mHour = hour;
+ mMinute = minute;
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ mHour = in.readInt();
+ mMinute = in.readInt();
+ }
+
+ public int getHour() {
+ return mHour;
+ }
+
+ public int getMinute() {
+ return mMinute;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mHour);
+ dest.writeInt(mMinute);
+ }
+
+ @SuppressWarnings({"unused", "hiding"})
+ public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
+
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index ad60a95..65f1ab7 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -18,6 +18,7 @@ package android.widget;
import com.android.internal.R;
+import android.annotation.IntDef;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -31,6 +32,9 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A Layout that arranges its children in a single column or a single row. The direction of
@@ -57,9 +61,25 @@ import android.widget.RemoteViews.RemoteView;
*/
@RemoteView
public class LinearLayout extends ViewGroup {
+ /** @hide */
+ @IntDef({HORIZONTAL, VERTICAL})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OrientationMode {}
+
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
+ /** @hide */
+ @IntDef(flag = true,
+ value = {
+ SHOW_DIVIDER_NONE,
+ SHOW_DIVIDER_BEGINNING,
+ SHOW_DIVIDER_MIDDLE,
+ SHOW_DIVIDER_END
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DividerMode {}
+
/**
* Don't show any dividers.
*/
@@ -165,18 +185,22 @@ public class LinearLayout extends ViewGroup {
private int mDividerPadding;
public LinearLayout(Context context) {
- super(context);
+ this(context, null);
}
public LinearLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
- public LinearLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public LinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public LinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.LinearLayout, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.LinearLayout, defStyleAttr, defStyleRes);
int index = a.getInt(com.android.internal.R.styleable.LinearLayout_orientation, -1);
if (index >= 0) {
@@ -214,7 +238,7 @@ public class LinearLayout extends ViewGroup {
* {@link #SHOW_DIVIDER_MIDDLE}, or {@link #SHOW_DIVIDER_END},
* or {@link #SHOW_DIVIDER_NONE} to show no dividers.
*/
- public void setShowDividers(int showDividers) {
+ public void setShowDividers(@DividerMode int showDividers) {
if (showDividers != mShowDividers) {
requestLayout();
}
@@ -230,6 +254,7 @@ public class LinearLayout extends ViewGroup {
* @return A flag set indicating how dividers should be shown around items.
* @see #setShowDividers(int)
*/
+ @DividerMode
public int getShowDividers() {
return mShowDividers;
}
@@ -1673,12 +1698,12 @@ public class LinearLayout extends ViewGroup {
/**
* Should the layout be a column or a row.
- * @param orientation Pass HORIZONTAL or VERTICAL. Default
- * value is HORIZONTAL.
+ * @param orientation Pass {@link #HORIZONTAL} or {@link #VERTICAL}. Default
+ * value is {@link #HORIZONTAL}.
*
* @attr ref android.R.styleable#LinearLayout_orientation
*/
- public void setOrientation(int orientation) {
+ public void setOrientation(@OrientationMode int orientation) {
if (mOrientation != orientation) {
mOrientation = orientation;
requestLayout();
@@ -1690,6 +1715,7 @@ public class LinearLayout extends ViewGroup {
*
* @return either {@link #HORIZONTAL} or {@link #VERTICAL}
*/
+ @OrientationMode
public int getOrientation() {
return mOrientation;
}
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index 66fe46f..64953f8 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -33,7 +33,6 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
-import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 78237c3..0cd35e8 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -39,7 +39,6 @@ import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewParent;
-import android.view.ViewRootImpl;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
@@ -142,11 +141,15 @@ public class ListView extends AbsListView {
this(context, attrs, com.android.internal.R.attr.listViewStyle);
}
- public ListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public ListView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.ListView, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.ListView, defStyleAttr, defStyleRes);
CharSequence[] entries = a.getTextArray(
com.android.internal.R.styleable.ListView_entries);
@@ -1729,34 +1732,6 @@ public class ListView extends AbsListView {
}
/**
- * @return the direct child that contains accessibility focus, or null if no
- * child contains accessibility focus
- */
- private View getAccessibilityFocusedChild() {
- final ViewRootImpl viewRootImpl = getViewRootImpl();
- if (viewRootImpl == null) {
- return null;
- }
-
- View focusedView = viewRootImpl.getAccessibilityFocusedHost();
- if (focusedView == null) {
- return null;
- }
-
- ViewParent viewParent = focusedView.getParent();
- while ((viewParent instanceof View) && (viewParent != this)) {
- focusedView = (View) viewParent;
- viewParent = viewParent.getParent();
- }
-
- if (!(viewParent instanceof View)) {
- return null;
- }
-
- return focusedView;
- }
-
- /**
* Obtain the view and add it to our list of children. The view can be made
* fresh, converted from an unused view, or used as is if it was in the
* recycle bin.
diff --git a/core/java/android/widget/MultiAutoCompleteTextView.java b/core/java/android/widget/MultiAutoCompleteTextView.java
index 0b30c84..cbd01b0 100644
--- a/core/java/android/widget/MultiAutoCompleteTextView.java
+++ b/core/java/android/widget/MultiAutoCompleteTextView.java
@@ -67,8 +67,13 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView {
this(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle);
}
- public MultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public MultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public MultiAutoCompleteTextView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
/* package */ void finishInit() { }
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index c0fde2e..44c4987 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.IntDef;
import android.annotation.Widget;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -53,6 +54,8 @@ import android.view.inputmethod.InputMethodManager;
import com.android.internal.R;
import libcore.icu.LocaleData;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -493,6 +496,10 @@ public class NumberPicker extends LinearLayout {
* Interface to listen for the picker scroll state.
*/
public interface OnScrollListener {
+ /** @hide */
+ @IntDef({SCROLL_STATE_IDLE, SCROLL_STATE_TOUCH_SCROLL, SCROLL_STATE_FLING})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScrollState {}
/**
* The view is not scrolling.
@@ -518,7 +525,7 @@ public class NumberPicker extends LinearLayout {
* {@link #SCROLL_STATE_TOUCH_SCROLL} or
* {@link #SCROLL_STATE_IDLE}.
*/
- public void onScrollStateChange(NumberPicker view, int scrollState);
+ public void onScrollStateChange(NumberPicker view, @ScrollState int scrollState);
}
/**
@@ -559,14 +566,33 @@ public class NumberPicker extends LinearLayout {
*
* @param context the application environment.
* @param attrs a collection of attributes.
- * @param defStyle The default style to apply to this view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
*/
- public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public NumberPicker(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ /**
+ * Create a new number picker
+ *
+ * @param context the application environment.
+ * @param attrs a collection of attributes.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ */
+ public NumberPicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
// process style attributes
- TypedArray attributesArray = context.obtainStyledAttributes(
- attrs, R.styleable.NumberPicker, defStyle, 0);
+ final TypedArray attributesArray = context.obtainStyledAttributes(
+ attrs, R.styleable.NumberPicker, defStyleAttr, defStyleRes);
final int layoutResId = attributesArray.getResourceId(
R.styleable.NumberPicker_internalLayout, DEFAULT_LAYOUT_RESOURCE_ID);
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
index f218199..7b3dd31 100644
--- a/core/java/android/widget/OverScroller.java
+++ b/core/java/android/widget/OverScroller.java
@@ -70,7 +70,11 @@ public class OverScroller {
* @hide
*/
public OverScroller(Context context, Interpolator interpolator, boolean flywheel) {
- mInterpolator = interpolator;
+ if (interpolator == null) {
+ mInterpolator = new Scroller.ViscousFluidInterpolator();
+ } else {
+ mInterpolator = interpolator;
+ }
mFlywheel = flywheel;
mScrollerX = new SplineOverScroller(context);
mScrollerY = new SplineOverScroller(context);
@@ -112,7 +116,11 @@ public class OverScroller {
}
void setInterpolator(Interpolator interpolator) {
- mInterpolator = interpolator;
+ if (interpolator == null) {
+ mInterpolator = new Scroller.ViscousFluidInterpolator();
+ } else {
+ mInterpolator = interpolator;
+ }
}
/**
@@ -302,14 +310,7 @@ public class OverScroller {
final int duration = mScrollerX.mDuration;
if (elapsedTime < duration) {
- float q = (float) (elapsedTime) / duration;
-
- if (mInterpolator == null) {
- q = Scroller.viscousFluid(q);
- } else {
- q = mInterpolator.getInterpolation(q);
- }
-
+ final float q = mInterpolator.getInterpolation(elapsedTime / (float) duration);
mScrollerX.updateScroll(q);
mScrollerY.updateScroll(q);
} else {
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 5663959..e77a810 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -170,8 +170,8 @@ public class PopupWindow {
*
* <p>The popup does provide a background.</p>
*/
- public PopupWindow(Context context, AttributeSet attrs, int defStyle) {
- this(context, attrs, defStyle, 0);
+ public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
/**
@@ -183,8 +183,7 @@ public class PopupWindow {
mContext = context;
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
- TypedArray a =
- context.obtainStyledAttributes(
+ final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.PopupWindow, defStyleAttr, defStyleRes);
mBackground = a.getDrawable(R.styleable.PopupWindow_popupBackground);
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 5392a96..1fbcbcf 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -242,29 +242,26 @@ public class ProgressBar extends View {
this(context, attrs, com.android.internal.R.attr.progressBarStyle);
}
- public ProgressBar(Context context, AttributeSet attrs, int defStyle) {
- this(context, attrs, defStyle, 0);
+ public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
- /**
- * @hide
- */
- public ProgressBar(Context context, AttributeSet attrs, int defStyle, int styleRes) {
- super(context, attrs, defStyle);
+ public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
mUiThreadId = Thread.currentThread().getId();
initProgressBar();
- TypedArray a =
- context.obtainStyledAttributes(attrs, R.styleable.ProgressBar, defStyle, styleRes);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes);
mNoInvalidate = true;
Drawable drawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
if (drawable != null) {
- drawable = tileify(drawable, false);
// Calling this method can set mMaxHeight, make sure the corresponding
// XML attribute for mMaxHeight is read after calling this method
- setProgressDrawable(drawable);
+ setProgressDrawableTiled(drawable);
}
@@ -293,8 +290,7 @@ public class ProgressBar extends View {
drawable = a.getDrawable(R.styleable.ProgressBar_indeterminateDrawable);
if (drawable != null) {
- drawable = tileifyIndeterminate(drawable);
- setIndeterminateDrawable(drawable);
+ setIndeterminateDrawableTiled(drawable);
}
mOnlyIndeterminate = a.getBoolean(
@@ -467,11 +463,9 @@ public class ProgressBar extends View {
}
/**
- * <p>Define the drawable used to draw the progress bar in
- * indeterminate mode.</p>
+ * Define the drawable used to draw the progress bar in indeterminate mode.
*
* @param d the new drawable
- *
* @see #getIndeterminateDrawable()
* @see #setIndeterminate(boolean)
*/
@@ -488,6 +482,25 @@ public class ProgressBar extends View {
postInvalidate();
}
}
+
+ /**
+ * Define the tileable drawable used to draw the progress bar in
+ * indeterminate mode.
+ * <p>
+ * If the drawable is a BitmapDrawable or contains BitmapDrawables, a
+ * tiled copy will be generated for display as a progress bar.
+ *
+ * @param d the new drawable
+ * @see #getIndeterminateDrawable()
+ * @see #setIndeterminate(boolean)
+ */
+ public void setIndeterminateDrawableTiled(Drawable d) {
+ if (d != null) {
+ d = tileifyIndeterminate(d);
+ }
+
+ setIndeterminateDrawable(d);
+ }
/**
* <p>Get the drawable used to draw the progress bar in
@@ -503,11 +516,9 @@ public class ProgressBar extends View {
}
/**
- * <p>Define the drawable used to draw the progress bar in
- * progress mode.</p>
+ * Define the drawable used to draw the progress bar in progress mode.
*
* @param d the new drawable
- *
* @see #getProgressDrawable()
* @see #setIndeterminate(boolean)
*/
@@ -546,6 +557,25 @@ public class ProgressBar extends View {
doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false);
}
}
+
+ /**
+ * Define the tileable drawable used to draw the progress bar in
+ * progress mode.
+ * <p>
+ * If the drawable is a BitmapDrawable or contains BitmapDrawables, a
+ * tiled copy will be generated for display as a progress bar.
+ *
+ * @param d the new drawable
+ * @see #getProgressDrawable()
+ * @see #setIndeterminate(boolean)
+ */
+ public void setProgressDrawableTiled(Drawable d) {
+ if (d != null) {
+ d = tileify(d, false);
+ }
+
+ setProgressDrawable(d);
+ }
/**
* @return The drawable currently used to draw the progress bar
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index fd2f754..a4f758c 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -84,8 +84,13 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
this(context, attrs, 0);
}
- public QuickContactBadge(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public QuickContactBadge(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public QuickContactBadge(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme);
mOverlay = styledAttributes.getDrawable(
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
new file mode 100644
index 0000000..1c9ab61
--- /dev/null
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -0,0 +1,1396 @@
+/*
+ * Copyright (C) 2013 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.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import com.android.internal.R;
+
+import java.text.DateFormatSymbols;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Locale;
+
+/**
+ * View to show a clock circle picker (with one or two picking circles)
+ *
+ * @hide
+ */
+public class RadialTimePickerView extends View implements View.OnTouchListener {
+ private static final String TAG = "ClockView";
+
+ private static final boolean DEBUG = false;
+
+ private static final int DEBUG_COLOR = 0x20FF0000;
+ private static final int DEBUG_TEXT_COLOR = 0x60FF0000;
+ private static final int DEBUG_STROKE_WIDTH = 2;
+
+ private static final int HOURS = 0;
+ private static final int MINUTES = 1;
+ private static final int HOURS_INNER = 2;
+ private static final int AMPM = 3;
+
+ private static final int SELECTOR_CIRCLE = 0;
+ private static final int SELECTOR_DOT = 1;
+ private static final int SELECTOR_LINE = 2;
+
+ private static final int AM = 0;
+ private static final int PM = 1;
+
+ // Opaque alpha level
+ private static final int ALPHA_OPAQUE = 255;
+
+ // Transparent alpha level
+ private static final int ALPHA_TRANSPARENT = 0;
+
+ // Alpha level of color for selector.
+ private static final int ALPHA_SELECTOR = 51;
+
+ // Alpha level of color for selected circle.
+ private static final int ALPHA_AMPM_SELECTED = ALPHA_SELECTOR;
+
+ // Alpha level of color for pressed circle.
+ private static final int ALPHA_AMPM_PRESSED = 175;
+
+ private static final float COSINE_30_DEGREES = ((float) Math.sqrt(3)) * 0.5f;
+ private static final float SINE_30_DEGREES = 0.5f;
+
+ private static final int DEGREES_FOR_ONE_HOUR = 30;
+ private static final int DEGREES_FOR_ONE_MINUTE = 6;
+
+ private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+ private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
+ private static final int[] MINUTES_NUMBERS = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55};
+
+ private static final int CENTER_RADIUS = 2;
+
+ private static int[] sSnapPrefer30sMap = new int[361];
+
+ private final String[] mHours12Texts = new String[12];
+ private final String[] mOuterHours24Texts = new String[12];
+ private final String[] mInnerHours24Texts = new String[12];
+ private final String[] mMinutesTexts = new String[12];
+
+ private final String[] mAmPmText = new String[2];
+
+ private final Paint[] mPaint = new Paint[2];
+ private final Paint mPaintCenter = new Paint();
+ private final Paint[][] mPaintSelector = new Paint[2][3];
+ private final Paint mPaintAmPmText = new Paint();
+ private final Paint[] mPaintAmPmCircle = new Paint[2];
+
+ private final Paint mPaintBackground = new Paint();
+ private final Paint mPaintDisabled = new Paint();
+ private final Paint mPaintDebug = new Paint();
+
+ private Typeface mTypeface;
+
+ private boolean mIs24HourMode;
+ private boolean mShowHours;
+ private boolean mIsOnInnerCircle;
+
+ private int mXCenter;
+ private int mYCenter;
+
+ private float[] mCircleRadius = new float[3];
+
+ private int mMinHypotenuseForInnerNumber;
+ private int mMaxHypotenuseForOuterNumber;
+ private int mHalfwayHypotenusePoint;
+
+ private float[] mTextSize = new float[2];
+ private float mInnerTextSize;
+
+ private float[][] mTextGridHeights = new float[2][7];
+ private float[][] mTextGridWidths = new float[2][7];
+
+ private float[] mInnerTextGridHeights = new float[7];
+ private float[] mInnerTextGridWidths = new float[7];
+
+ private String[] mOuterTextHours;
+ private String[] mInnerTextHours;
+ private String[] mOuterTextMinutes;
+
+ private float[] mCircleRadiusMultiplier = new float[2];
+ private float[] mNumbersRadiusMultiplier = new float[3];
+
+ private float[] mTextSizeMultiplier = new float[3];
+
+ private float[] mAnimationRadiusMultiplier = new float[3];
+
+ private float mTransitionMidRadiusMultiplier;
+ private float mTransitionEndRadiusMultiplier;
+
+ private AnimatorSet mTransition;
+ private InvalidateUpdateListener mInvalidateUpdateListener = new InvalidateUpdateListener();
+
+ private int[] mLineLength = new int[3];
+ private int[] mSelectionRadius = new int[3];
+ private float mSelectionRadiusMultiplier;
+ private int[] mSelectionDegrees = new int[3];
+
+ private int mAmPmCircleRadius;
+ private float mAmPmYCenter;
+
+ private float mAmPmCircleRadiusMultiplier;
+ private int mAmPmTextColor;
+
+ private float mLeftIndicatorXCenter;
+ private float mRightIndicatorXCenter;
+
+ private int mAmPmUnselectedColor;
+ private int mAmPmSelectedColor;
+
+ private int mAmOrPm;
+ private int mAmOrPmPressed;
+
+ private RectF mRectF = new RectF();
+ private boolean mInputEnabled = true;
+ private OnValueSelectedListener mListener;
+
+ private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<Animator>();
+ private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<Animator>();
+
+ public interface OnValueSelectedListener {
+ void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
+ }
+
+ static {
+ // Prepare mapping to snap touchable degrees to selectable degrees.
+ preparePrefer30sMap();
+ }
+
+ /**
+ * Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger
+ * selectable area to each of the 12 visible values, such that the ratio of space apportioned
+ * to a visible value : space apportioned to a non-visible value will be 14 : 4.
+ * E.g. the output of 30 degrees should have a higher range of input associated with it than
+ * the output of 24 degrees, because 30 degrees corresponds to a visible number on the clock
+ * circle (5 on the minutes, 1 or 13 on the hours).
+ */
+ private static void preparePrefer30sMap() {
+ // We'll split up the visible output and the non-visible output such that each visible
+ // output will correspond to a range of 14 associated input degrees, and each non-visible
+ // output will correspond to a range of 4 associate input degrees, so visible numbers
+ // are more than 3 times easier to get than non-visible numbers:
+ // {354-359,0-7}:0, {8-11}:6, {12-15}:12, {16-19}:18, {20-23}:24, {24-37}:30, etc.
+ //
+ // If an output of 30 degrees should correspond to a range of 14 associated degrees, then
+ // we'll need any input between 24 - 37 to snap to 30. Working out from there, 20-23 should
+ // snap to 24, while 38-41 should snap to 36. This is somewhat counter-intuitive, that you
+ // can be touching 36 degrees but have the selection snapped to 30 degrees; however, this
+ // inconsistency isn't noticeable at such fine-grained degrees, and it affords us the
+ // ability to aggressively prefer the visible values by a factor of more than 3:1, which
+ // greatly contributes to the selectability of these values.
+
+ // The first output is 0, and each following output will increment by 6 {0, 6, 12, ...}.
+ int snappedOutputDegrees = 0;
+ // Count of how many inputs we've designated to the specified output.
+ int count = 1;
+ // How many input we expect for a specified output. This will be 14 for output divisible
+ // by 30, and 4 for the remaining output. We'll special case the outputs of 0 and 360, so
+ // the caller can decide which they need.
+ int expectedCount = 8;
+ // Iterate through the input.
+ for (int degrees = 0; degrees < 361; degrees++) {
+ // Save the input-output mapping.
+ sSnapPrefer30sMap[degrees] = snappedOutputDegrees;
+ // If this is the last input for the specified output, calculate the next output and
+ // the next expected count.
+ if (count == expectedCount) {
+ snappedOutputDegrees += 6;
+ if (snappedOutputDegrees == 360) {
+ expectedCount = 7;
+ } else if (snappedOutputDegrees % 30 == 0) {
+ expectedCount = 14;
+ } else {
+ expectedCount = 4;
+ }
+ count = 1;
+ } else {
+ count++;
+ }
+ }
+ }
+
+ /**
+ * Returns mapping of any input degrees (0 to 360) to one of 60 selectable output degrees,
+ * where the degrees corresponding to visible numbers (i.e. those divisible by 30) will be
+ * weighted heavier than the degrees corresponding to non-visible numbers.
+ * See {@link #preparePrefer30sMap()} documentation for the rationale and generation of the
+ * mapping.
+ */
+ private static int snapPrefer30s(int degrees) {
+ if (sSnapPrefer30sMap == null) {
+ return -1;
+ }
+ return sSnapPrefer30sMap[degrees];
+ }
+
+ /**
+ * Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all
+ * multiples of 30), where the input will be "snapped" to the closest visible degrees.
+ * @param degrees The input degrees
+ * @param forceHigherOrLower The output may be forced to either the higher or lower step, or may
+ * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force
+ * strictly lower, and 0 to snap to the closer one.
+ * @return output degrees, will be a multiple of 30
+ */
+ private static int snapOnly30s(int degrees, int forceHigherOrLower) {
+ final int stepSize = DEGREES_FOR_ONE_HOUR;
+ int floor = (degrees / stepSize) * stepSize;
+ final int ceiling = floor + stepSize;
+ if (forceHigherOrLower == 1) {
+ degrees = ceiling;
+ } else if (forceHigherOrLower == -1) {
+ if (degrees == floor) {
+ floor -= stepSize;
+ }
+ degrees = floor;
+ } else {
+ if ((degrees - floor) < (ceiling - degrees)) {
+ degrees = floor;
+ } else {
+ degrees = ceiling;
+ }
+ }
+ return degrees;
+ }
+
+ public RadialTimePickerView(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.timePickerStyle);
+ }
+
+ public RadialTimePickerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs);
+
+ // process style attributes
+ final TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TimePicker,
+ defStyle, 0);
+
+ final Resources res = getResources();
+
+ mAmPmUnselectedColor = a.getColor(R.styleable.TimePicker_amPmUnselectedBackgroundColor,
+ res.getColor(
+ R.color.timepicker_default_ampm_unselected_background_color_holo_light));
+
+ mAmPmSelectedColor = a.getColor(R.styleable.TimePicker_amPmSelectedBackgroundColor,
+ res.getColor(R.color.timepicker_default_ampm_selected_background_color_holo_light));
+
+ mAmPmTextColor = a.getColor(R.styleable.TimePicker_amPmTextColor,
+ res.getColor(R.color.timepicker_default_text_color_holo_light));
+
+ final int numbersTextColor = a.getColor(R.styleable.TimePicker_numbersTextColor,
+ res.getColor(R.color.timepicker_default_text_color_holo_light));
+
+ mTypeface = Typeface.create("sans-serif", Typeface.NORMAL);
+
+ mPaint[HOURS] = new Paint();
+ mPaint[HOURS].setColor(numbersTextColor);
+ mPaint[HOURS].setAntiAlias(true);
+ mPaint[HOURS].setTextAlign(Paint.Align.CENTER);
+
+ mPaint[MINUTES] = new Paint();
+ mPaint[MINUTES].setColor(numbersTextColor);
+ mPaint[MINUTES].setAntiAlias(true);
+ mPaint[MINUTES].setTextAlign(Paint.Align.CENTER);
+
+ mPaintCenter.setColor(numbersTextColor);
+ mPaintCenter.setAntiAlias(true);
+ mPaintCenter.setTextAlign(Paint.Align.CENTER);
+
+ mPaintSelector[HOURS][SELECTOR_CIRCLE] = new Paint();
+ mPaintSelector[HOURS][SELECTOR_CIRCLE].setColor(
+ a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light));
+ mPaintSelector[HOURS][SELECTOR_CIRCLE].setAntiAlias(true);
+
+ mPaintSelector[HOURS][SELECTOR_DOT] = new Paint();
+ mPaintSelector[HOURS][SELECTOR_DOT].setColor(
+ a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light));
+ mPaintSelector[HOURS][SELECTOR_DOT].setAntiAlias(true);
+
+ mPaintSelector[HOURS][SELECTOR_LINE] = new Paint();
+ mPaintSelector[HOURS][SELECTOR_LINE].setColor(
+ a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light));
+ mPaintSelector[HOURS][SELECTOR_LINE].setAntiAlias(true);
+ mPaintSelector[HOURS][SELECTOR_LINE].setStrokeWidth(2);
+
+ mPaintSelector[MINUTES][SELECTOR_CIRCLE] = new Paint();
+ mPaintSelector[MINUTES][SELECTOR_CIRCLE].setColor(
+ a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light));
+ mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAntiAlias(true);
+
+ mPaintSelector[MINUTES][SELECTOR_DOT] = new Paint();
+ mPaintSelector[MINUTES][SELECTOR_DOT].setColor(
+ a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light));
+ mPaintSelector[MINUTES][SELECTOR_DOT].setAntiAlias(true);
+
+ mPaintSelector[MINUTES][SELECTOR_LINE] = new Paint();
+ mPaintSelector[MINUTES][SELECTOR_LINE].setColor(
+ a.getColor(R.styleable.TimePicker_numbersSelectorColor, R.color.holo_blue_light));
+ mPaintSelector[MINUTES][SELECTOR_LINE].setAntiAlias(true);
+ mPaintSelector[MINUTES][SELECTOR_LINE].setStrokeWidth(2);
+
+ mPaintAmPmText.setColor(mAmPmTextColor);
+ mPaintAmPmText.setTypeface(mTypeface);
+ mPaintAmPmText.setAntiAlias(true);
+ mPaintAmPmText.setTextAlign(Paint.Align.CENTER);
+
+ mPaintAmPmCircle[AM] = new Paint();
+ mPaintAmPmCircle[AM].setAntiAlias(true);
+ mPaintAmPmCircle[PM] = new Paint();
+ mPaintAmPmCircle[PM].setAntiAlias(true);
+
+ mPaintBackground.setColor(
+ a.getColor(R.styleable.TimePicker_numbersBackgroundColor, Color.WHITE));
+ mPaintBackground.setAntiAlias(true);
+
+ final int disabledColor = a.getColor(R.styleable.TimePicker_disabledColor,
+ res.getColor(R.color.timepicker_default_disabled_color_holo_light));
+ mPaintDisabled.setColor(disabledColor);
+ mPaintDisabled.setAntiAlias(true);
+
+ if (DEBUG) {
+ mPaintDebug.setColor(DEBUG_COLOR);
+ mPaintDebug.setAntiAlias(true);
+ mPaintDebug.setStrokeWidth(DEBUG_STROKE_WIDTH);
+ mPaintDebug.setStyle(Paint.Style.STROKE);
+ mPaintDebug.setTextAlign(Paint.Align.CENTER);
+ }
+
+ mShowHours = true;
+ mIs24HourMode = false;
+ mAmOrPm = AM;
+ mAmOrPmPressed = -1;
+
+ initHoursAndMinutesText();
+ initData();
+
+ mTransitionMidRadiusMultiplier = Float.parseFloat(
+ res.getString(R.string.timepicker_transition_mid_radius_multiplier));
+ mTransitionEndRadiusMultiplier = Float.parseFloat(
+ res.getString(R.string.timepicker_transition_end_radius_multiplier));
+
+ mTextGridHeights[HOURS] = new float[7];
+ mTextGridHeights[MINUTES] = new float[7];
+
+ mSelectionRadiusMultiplier = Float.parseFloat(
+ res.getString(R.string.timepicker_selection_radius_multiplier));
+
+ setOnTouchListener(this);
+
+ // Initial values
+ final Calendar calendar = Calendar.getInstance(Locale.getDefault());
+ final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
+ final int currentMinute = calendar.get(Calendar.MINUTE);
+
+ setCurrentHour(currentHour);
+ setCurrentMinute(currentMinute);
+
+ setHapticFeedbackEnabled(true);
+ }
+
+ /**
+ * Measure the view to end up as a square, based on the minimum of the height and width.
+ */
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int minDimension = Math.min(measuredWidth, measuredHeight);
+
+ super.onMeasure(MeasureSpec.makeMeasureSpec(minDimension, widthMode),
+ MeasureSpec.makeMeasureSpec(minDimension, heightMode));
+ }
+
+ public void initialize(int hour, int minute, boolean is24HourMode) {
+ mIs24HourMode = is24HourMode;
+ setCurrentHour(hour);
+ setCurrentMinute(minute);
+ }
+
+ public void setCurrentItemShowing(int item, boolean animate) {
+ switch (item){
+ case HOURS:
+ showHours(animate);
+ break;
+ case MINUTES:
+ showMinutes(animate);
+ break;
+ default:
+ Log.e(TAG, "ClockView does not support showing item " + item);
+ }
+ }
+
+ public int getCurrentItemShowing() {
+ return mShowHours ? HOURS : MINUTES;
+ }
+
+ public void setOnValueSelectedListener(OnValueSelectedListener listener) {
+ mListener = listener;
+ }
+
+ public void setCurrentHour(int hour) {
+ final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR;
+ mSelectionDegrees[HOURS] = degrees;
+ mSelectionDegrees[HOURS_INNER] = degrees;
+ mAmOrPm = ((hour % 24) < 12) ? AM : PM;
+ if (mIs24HourMode) {
+ mIsOnInnerCircle = (mAmOrPm == AM);
+ } else {
+ mIsOnInnerCircle = false;
+ }
+ initData();
+ updateLayoutData();
+ invalidate();
+ }
+
+ // Return hours in 0-23 range
+ public int getCurrentHour() {
+ int hours =
+ mSelectionDegrees[mIsOnInnerCircle ? HOURS_INNER : HOURS] / DEGREES_FOR_ONE_HOUR;
+ if (mIs24HourMode) {
+ if (mIsOnInnerCircle) {
+ hours = hours % 12;
+ } else {
+ if (hours != 0) {
+ hours += 12;
+ }
+ }
+ } else {
+ hours = hours % 12;
+ if (hours == 0) {
+ if (mAmOrPm == PM) {
+ hours = 12;
+ }
+ } else {
+ if (mAmOrPm == PM) {
+ hours += 12;
+ }
+ }
+ }
+ return hours;
+ }
+
+ public void setCurrentMinute(int minute) {
+ mSelectionDegrees[MINUTES] = (minute % 60) * DEGREES_FOR_ONE_MINUTE;
+ invalidate();
+ }
+
+ // Returns minutes in 0-59 range
+ public int getCurrentMinute() {
+ return (mSelectionDegrees[MINUTES] / DEGREES_FOR_ONE_MINUTE);
+ }
+
+ public void setAmOrPm(int val) {
+ mAmOrPm = (val % 2);
+ invalidate();
+ }
+
+ public int getAmOrPm() {
+ return mAmOrPm;
+ }
+
+ public void swapAmPm() {
+ mAmOrPm = (mAmOrPm == AM) ? PM : AM;
+ invalidate();
+ }
+
+ public void showHours(boolean animate) {
+ if (mShowHours) return;
+ mShowHours = true;
+ if (animate) {
+ startMinutesToHoursAnimation();
+ }
+ initData();
+ updateLayoutData();
+ invalidate();
+ }
+
+ public void showMinutes(boolean animate) {
+ if (!mShowHours) return;
+ mShowHours = false;
+ if (animate) {
+ startHoursToMinutesAnimation();
+ }
+ initData();
+ updateLayoutData();
+ invalidate();
+ }
+
+ private void initHoursAndMinutesText() {
+ // Initialize the hours and minutes numbers.
+ for (int i = 0; i < 12; i++) {
+ mHours12Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
+ mOuterHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]);
+ mInnerHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
+ mMinutesTexts[i] = String.format("%02d", MINUTES_NUMBERS[i]);
+ }
+
+ String[] amPmTexts = new DateFormatSymbols().getAmPmStrings();
+ mAmPmText[AM] = amPmTexts[0];
+ mAmPmText[PM] = amPmTexts[1];
+ }
+
+ private void initData() {
+ if (mIs24HourMode) {
+ mOuterTextHours = mOuterHours24Texts;
+ mInnerTextHours = mInnerHours24Texts;
+ } else {
+ mOuterTextHours = mHours12Texts;
+ mInnerTextHours = null;
+ }
+
+ mOuterTextMinutes = mMinutesTexts;
+
+ final Resources res = getResources();
+
+ if (mShowHours) {
+ if (mIs24HourMode) {
+ mCircleRadiusMultiplier[HOURS] = Float.parseFloat(
+ res.getString(R.string.timepicker_circle_radius_multiplier_24HourMode));
+ mNumbersRadiusMultiplier[HOURS] = Float.parseFloat(
+ res.getString(R.string.timepicker_numbers_radius_multiplier_outer));
+ mTextSizeMultiplier[HOURS] = Float.parseFloat(
+ res.getString(R.string.timepicker_text_size_multiplier_outer));
+
+ mNumbersRadiusMultiplier[HOURS_INNER] = Float.parseFloat(
+ res.getString(R.string.timepicker_numbers_radius_multiplier_inner));
+ mTextSizeMultiplier[HOURS_INNER] = Float.parseFloat(
+ res.getString(R.string.timepicker_text_size_multiplier_inner));
+ } else {
+ mCircleRadiusMultiplier[HOURS] = Float.parseFloat(
+ res.getString(R.string.timepicker_circle_radius_multiplier));
+ mNumbersRadiusMultiplier[HOURS] = Float.parseFloat(
+ res.getString(R.string.timepicker_numbers_radius_multiplier_normal));
+ mTextSizeMultiplier[HOURS] = Float.parseFloat(
+ res.getString(R.string.timepicker_text_size_multiplier_normal));
+ }
+ } else {
+ mCircleRadiusMultiplier[MINUTES] = Float.parseFloat(
+ res.getString(R.string.timepicker_circle_radius_multiplier));
+ mNumbersRadiusMultiplier[MINUTES] = Float.parseFloat(
+ res.getString(R.string.timepicker_numbers_radius_multiplier_normal));
+ mTextSizeMultiplier[MINUTES] = Float.parseFloat(
+ res.getString(R.string.timepicker_text_size_multiplier_normal));
+ }
+
+ mAnimationRadiusMultiplier[HOURS] = 1;
+ mAnimationRadiusMultiplier[HOURS_INNER] = 1;
+ mAnimationRadiusMultiplier[MINUTES] = 1;
+
+ mAmPmCircleRadiusMultiplier = Float.parseFloat(
+ res.getString(R.string.timepicker_ampm_circle_radius_multiplier));
+
+ mPaint[HOURS].setAlpha(mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT);
+ mPaint[MINUTES].setAlpha(mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE);
+
+ mPaintSelector[HOURS][SELECTOR_CIRCLE].setAlpha(
+ mShowHours ?ALPHA_SELECTOR : ALPHA_TRANSPARENT);
+ mPaintSelector[HOURS][SELECTOR_DOT].setAlpha(
+ mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT);
+ mPaintSelector[HOURS][SELECTOR_LINE].setAlpha(
+ mShowHours ? ALPHA_SELECTOR : ALPHA_TRANSPARENT);
+
+ mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAlpha(
+ mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR);
+ mPaintSelector[MINUTES][SELECTOR_DOT].setAlpha(
+ mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE);
+ mPaintSelector[MINUTES][SELECTOR_LINE].setAlpha(
+ mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ updateLayoutData();
+ }
+
+ private void updateLayoutData() {
+ mXCenter = getWidth() / 2;
+ mYCenter = getHeight() / 2;
+
+ final int min = Math.min(mXCenter, mYCenter);
+
+ mCircleRadius[HOURS] = min * mCircleRadiusMultiplier[HOURS];
+ mCircleRadius[HOURS_INNER] = min * mCircleRadiusMultiplier[HOURS];
+ mCircleRadius[MINUTES] = min * mCircleRadiusMultiplier[MINUTES];
+
+ if (!mIs24HourMode) {
+ // We'll need to draw the AM/PM circles, so the main circle will need to have
+ // a slightly higher center. To keep the entire view centered vertically, we'll
+ // have to push it up by half the radius of the AM/PM circles.
+ int amPmCircleRadius = (int) (mCircleRadius[HOURS] * mAmPmCircleRadiusMultiplier);
+ mYCenter -= amPmCircleRadius / 2;
+ }
+
+ mMinHypotenuseForInnerNumber = (int) (mCircleRadius[HOURS]
+ * mNumbersRadiusMultiplier[HOURS_INNER]) - mSelectionRadius[HOURS];
+ mMaxHypotenuseForOuterNumber = (int) (mCircleRadius[HOURS]
+ * mNumbersRadiusMultiplier[HOURS]) + mSelectionRadius[HOURS];
+ mHalfwayHypotenusePoint = (int) (mCircleRadius[HOURS]
+ * ((mNumbersRadiusMultiplier[HOURS] + mNumbersRadiusMultiplier[HOURS_INNER]) / 2));
+
+ mTextSize[HOURS] = mCircleRadius[HOURS] * mTextSizeMultiplier[HOURS];
+ mTextSize[MINUTES] = mCircleRadius[MINUTES] * mTextSizeMultiplier[MINUTES];
+
+ if (mIs24HourMode) {
+ mInnerTextSize = mCircleRadius[HOURS] * mTextSizeMultiplier[HOURS_INNER];
+ }
+
+ calculateGridSizesHours();
+ calculateGridSizesMinutes();
+
+ mSelectionRadius[HOURS] = (int) (mCircleRadius[HOURS] * mSelectionRadiusMultiplier);
+ mSelectionRadius[HOURS_INNER] = mSelectionRadius[HOURS];
+ mSelectionRadius[MINUTES] = (int) (mCircleRadius[MINUTES] * mSelectionRadiusMultiplier);
+
+ mAmPmCircleRadius = (int) (mCircleRadius[HOURS] * mAmPmCircleRadiusMultiplier);
+ mPaintAmPmText.setTextSize(mAmPmCircleRadius * 3 / 4);
+
+ // Line up the vertical center of the AM/PM circles with the bottom of the main circle.
+ mAmPmYCenter = mYCenter + mCircleRadius[HOURS];
+
+ // Line up the horizontal edges of the AM/PM circles with the horizontal edges
+ // of the main circle
+ mLeftIndicatorXCenter = mXCenter - mCircleRadius[HOURS] + mAmPmCircleRadius;
+ mRightIndicatorXCenter = mXCenter + mCircleRadius[HOURS] - mAmPmCircleRadius;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ canvas.save();
+
+ calculateGridSizesHours();
+ calculateGridSizesMinutes();
+
+ drawCircleBackground(canvas);
+
+ drawTextElements(canvas, mTextSize[HOURS], mTypeface, mOuterTextHours,
+ mTextGridWidths[HOURS], mTextGridHeights[HOURS], mPaint[HOURS]);
+
+ if (mIs24HourMode && mInnerTextHours != null) {
+ drawTextElements(canvas, mInnerTextSize, mTypeface, mInnerTextHours,
+ mInnerTextGridWidths, mInnerTextGridHeights, mPaint[HOURS]);
+ }
+
+ drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mOuterTextMinutes,
+ mTextGridWidths[MINUTES], mTextGridHeights[MINUTES], mPaint[MINUTES]);
+
+ drawCenter(canvas);
+ drawSelector(canvas);
+ if (!mIs24HourMode) {
+ drawAmPm(canvas);
+ }
+
+ if(!mInputEnabled) {
+ // Draw outer view rectangle
+ mRectF.set(0, 0, getWidth(), getHeight());
+ canvas.drawRect(mRectF, mPaintDisabled);
+ }
+
+ if (DEBUG) {
+ drawDebug(canvas);
+ }
+
+ canvas.restore();
+ }
+
+ private void drawCircleBackground(Canvas canvas) {
+ canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintBackground);
+ }
+
+ private void drawCenter(Canvas canvas) {
+ canvas.drawCircle(mXCenter, mYCenter, CENTER_RADIUS, mPaintCenter);
+ }
+
+ private void drawSelector(Canvas canvas) {
+ drawSelector(canvas, mIsOnInnerCircle ? HOURS_INNER : HOURS);
+ drawSelector(canvas, MINUTES);
+ }
+
+ private void drawAmPm(Canvas canvas) {
+ final boolean isLayoutRtl = isLayoutRtl();
+
+ int amColor = mAmPmUnselectedColor;
+ int amAlpha = ALPHA_OPAQUE;
+ int pmColor = mAmPmUnselectedColor;
+ int pmAlpha = ALPHA_OPAQUE;
+ if (mAmOrPm == AM) {
+ amColor = mAmPmSelectedColor;
+ amAlpha = ALPHA_AMPM_SELECTED;
+ } else if (mAmOrPm == PM) {
+ pmColor = mAmPmSelectedColor;
+ pmAlpha = ALPHA_AMPM_SELECTED;
+ }
+ if (mAmOrPmPressed == AM) {
+ amColor = mAmPmSelectedColor;
+ amAlpha = ALPHA_AMPM_PRESSED;
+ } else if (mAmOrPmPressed == PM) {
+ pmColor = mAmPmSelectedColor;
+ pmAlpha = ALPHA_AMPM_PRESSED;
+ }
+
+ // Draw the two circles
+ mPaintAmPmCircle[AM].setColor(amColor);
+ mPaintAmPmCircle[AM].setAlpha(amAlpha);
+ canvas.drawCircle(isLayoutRtl ? mRightIndicatorXCenter : mLeftIndicatorXCenter,
+ mAmPmYCenter, mAmPmCircleRadius, mPaintAmPmCircle[AM]);
+
+ mPaintAmPmCircle[PM].setColor(pmColor);
+ mPaintAmPmCircle[PM].setAlpha(pmAlpha);
+ canvas.drawCircle(isLayoutRtl ? mLeftIndicatorXCenter : mRightIndicatorXCenter,
+ mAmPmYCenter, mAmPmCircleRadius, mPaintAmPmCircle[PM]);
+
+ // Draw the AM/PM texts on top
+ mPaintAmPmText.setColor(mAmPmTextColor);
+ float textYCenter = mAmPmYCenter -
+ (int) (mPaintAmPmText.descent() + mPaintAmPmText.ascent()) / 2;
+
+ canvas.drawText(isLayoutRtl ? mAmPmText[PM] : mAmPmText[AM], mLeftIndicatorXCenter,
+ textYCenter, mPaintAmPmText);
+ canvas.drawText(isLayoutRtl ? mAmPmText[AM] : mAmPmText[PM], mRightIndicatorXCenter,
+ textYCenter, mPaintAmPmText);
+ }
+
+ private void drawSelector(Canvas canvas, int index) {
+ // Calculate the current radius at which to place the selection circle.
+ mLineLength[index] = (int) (mCircleRadius[index]
+ * mNumbersRadiusMultiplier[index] * mAnimationRadiusMultiplier[index]);
+
+ double selectionRadians = Math.toRadians(mSelectionDegrees[index]);
+
+ int pointX = mXCenter + (int) (mLineLength[index] * Math.sin(selectionRadians));
+ int pointY = mYCenter - (int) (mLineLength[index] * Math.cos(selectionRadians));
+
+ // Draw the selection circle
+ canvas.drawCircle(pointX, pointY, mSelectionRadius[index],
+ mPaintSelector[index % 2][SELECTOR_CIRCLE]);
+
+ // Draw the dot if needed
+ if (mSelectionDegrees[index] % 30 != 0) {
+ // We're not on a direct tick
+ canvas.drawCircle(pointX, pointY, (mSelectionRadius[index] * 2 / 7),
+ mPaintSelector[index % 2][SELECTOR_DOT]);
+ } else {
+ // We're not drawing the dot, so shorten the line to only go as far as the edge of the
+ // selection circle
+ int lineLength = mLineLength[index] - mSelectionRadius[index];
+ pointX = mXCenter + (int) (lineLength * Math.sin(selectionRadians));
+ pointY = mYCenter - (int) (lineLength * Math.cos(selectionRadians));
+ }
+
+ // Draw the line
+ canvas.drawLine(mXCenter, mYCenter, pointX, pointY,
+ mPaintSelector[index % 2][SELECTOR_LINE]);
+ }
+
+ private void drawDebug(Canvas canvas) {
+ // Draw outer numbers circle
+ final float outerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS];
+ canvas.drawCircle(mXCenter, mYCenter, outerRadius, mPaintDebug);
+
+ // Draw inner numbers circle
+ final float innerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS_INNER];
+ canvas.drawCircle(mXCenter, mYCenter, innerRadius, mPaintDebug);
+
+ // Draw outer background circle
+ canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintDebug);
+
+ // Draw outer rectangle for circles
+ float left = mXCenter - outerRadius;
+ float top = mYCenter - outerRadius;
+ float right = mXCenter + outerRadius;
+ float bottom = mYCenter + outerRadius;
+ mRectF = new RectF(left, top, right, bottom);
+ canvas.drawRect(mRectF, mPaintDebug);
+
+ // Draw outer rectangle for background
+ left = mXCenter - mCircleRadius[HOURS];
+ top = mYCenter - mCircleRadius[HOURS];
+ right = mXCenter + mCircleRadius[HOURS];
+ bottom = mYCenter + mCircleRadius[HOURS];
+ mRectF.set(left, top, right, bottom);
+ canvas.drawRect(mRectF, mPaintDebug);
+
+ // Draw outer view rectangle
+ mRectF.set(0, 0, getWidth(), getHeight());
+ canvas.drawRect(mRectF, mPaintDebug);
+
+ // Draw selected time
+ final String selected = String.format("%02d:%02d", getCurrentHour(), getCurrentMinute());
+
+ ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ TextView tv = new TextView(getContext());
+ tv.setLayoutParams(lp);
+ tv.setText(selected);
+ tv.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ Paint paint = tv.getPaint();
+ paint.setColor(DEBUG_TEXT_COLOR);
+
+ final int width = tv.getMeasuredWidth();
+
+ float height = paint.descent() - paint.ascent();
+ float x = mXCenter - width / 2;
+ float y = mYCenter + 1.5f * height;
+
+ canvas.drawText(selected.toString(), x, y, paint);
+ }
+
+ private void calculateGridSizesHours() {
+ // Calculate the text positions
+ float numbersRadius = mCircleRadius[HOURS]
+ * mNumbersRadiusMultiplier[HOURS] * mAnimationRadiusMultiplier[HOURS];
+
+ // Calculate the positions for the 12 numbers in the main circle.
+ calculateGridSizes(mPaint[HOURS], numbersRadius, mXCenter, mYCenter,
+ mTextSize[HOURS], mTextGridHeights[HOURS], mTextGridWidths[HOURS]);
+
+ // If we have an inner circle, calculate those positions too.
+ if (mIs24HourMode) {
+ float innerNumbersRadius = mCircleRadius[HOURS_INNER]
+ * mNumbersRadiusMultiplier[HOURS_INNER]
+ * mAnimationRadiusMultiplier[HOURS_INNER];
+
+ calculateGridSizes(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter,
+ mInnerTextSize, mInnerTextGridHeights, mInnerTextGridWidths);
+ }
+ }
+
+ private void calculateGridSizesMinutes() {
+ // Calculate the text positions
+ float numbersRadius = mCircleRadius[MINUTES]
+ * mNumbersRadiusMultiplier[MINUTES] * mAnimationRadiusMultiplier[MINUTES];
+
+ // Calculate the positions for the 12 numbers in the main circle.
+ calculateGridSizes(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter,
+ mTextSize[MINUTES], mTextGridHeights[MINUTES], mTextGridWidths[MINUTES]);
+ }
+
+
+ /**
+ * Using the trigonometric Unit Circle, calculate the positions that the text will need to be
+ * drawn at based on the specified circle radius. Place the values in the textGridHeights and
+ * textGridWidths parameters.
+ */
+ private static void calculateGridSizes(Paint paint, float numbersRadius, float xCenter,
+ float yCenter, float textSize, float[] textGridHeights, float[] textGridWidths) {
+ /*
+ * The numbers need to be drawn in a 7x7 grid, representing the points on the Unit Circle.
+ */
+ final float offset1 = numbersRadius;
+ // cos(30) = a / r => r * cos(30)
+ final float offset2 = numbersRadius * COSINE_30_DEGREES;
+ // sin(30) = o / r => r * sin(30)
+ final float offset3 = numbersRadius * SINE_30_DEGREES;
+
+ paint.setTextSize(textSize);
+ // We'll need yTextBase to be slightly lower to account for the text's baseline.
+ yCenter -= (paint.descent() + paint.ascent()) / 2;
+
+ textGridHeights[0] = yCenter - offset1;
+ textGridWidths[0] = xCenter - offset1;
+ textGridHeights[1] = yCenter - offset2;
+ textGridWidths[1] = xCenter - offset2;
+ textGridHeights[2] = yCenter - offset3;
+ textGridWidths[2] = xCenter - offset3;
+ textGridHeights[3] = yCenter;
+ textGridWidths[3] = xCenter;
+ textGridHeights[4] = yCenter + offset3;
+ textGridWidths[4] = xCenter + offset3;
+ textGridHeights[5] = yCenter + offset2;
+ textGridWidths[5] = xCenter + offset2;
+ textGridHeights[6] = yCenter + offset1;
+ textGridWidths[6] = xCenter + offset1;
+ }
+
+ /**
+ * Draw the 12 text values at the positions specified by the textGrid parameters.
+ */
+ private void drawTextElements(Canvas canvas, float textSize, Typeface typeface, String[] texts,
+ float[] textGridWidths, float[] textGridHeights, Paint paint) {
+ paint.setTextSize(textSize);
+ paint.setTypeface(typeface);
+ canvas.drawText(texts[0], textGridWidths[3], textGridHeights[0], paint);
+ canvas.drawText(texts[1], textGridWidths[4], textGridHeights[1], paint);
+ canvas.drawText(texts[2], textGridWidths[5], textGridHeights[2], paint);
+ canvas.drawText(texts[3], textGridWidths[6], textGridHeights[3], paint);
+ canvas.drawText(texts[4], textGridWidths[5], textGridHeights[4], paint);
+ canvas.drawText(texts[5], textGridWidths[4], textGridHeights[5], paint);
+ canvas.drawText(texts[6], textGridWidths[3], textGridHeights[6], paint);
+ canvas.drawText(texts[7], textGridWidths[2], textGridHeights[5], paint);
+ canvas.drawText(texts[8], textGridWidths[1], textGridHeights[4], paint);
+ canvas.drawText(texts[9], textGridWidths[0], textGridHeights[3], paint);
+ canvas.drawText(texts[10], textGridWidths[1], textGridHeights[2], paint);
+ canvas.drawText(texts[11], textGridWidths[2], textGridHeights[1], paint);
+ }
+
+ // Used for animating the hours by changing their radius
+ private void setAnimationRadiusMultiplierHours(float animationRadiusMultiplier) {
+ mAnimationRadiusMultiplier[HOURS] = animationRadiusMultiplier;
+ mAnimationRadiusMultiplier[HOURS_INNER] = animationRadiusMultiplier;
+ }
+
+ // Used for animating the minutes by changing their radius
+ private void setAnimationRadiusMultiplierMinutes(float animationRadiusMultiplier) {
+ mAnimationRadiusMultiplier[MINUTES] = animationRadiusMultiplier;
+ }
+
+ private static ObjectAnimator getRadiusDisappearAnimator(Object target,
+ String radiusPropertyName, InvalidateUpdateListener updateListener,
+ float midRadiusMultiplier, float endRadiusMultiplier) {
+ Keyframe kf0, kf1, kf2;
+ float midwayPoint = 0.2f;
+ int duration = 500;
+
+ kf0 = Keyframe.ofFloat(0f, 1);
+ kf1 = Keyframe.ofFloat(midwayPoint, midRadiusMultiplier);
+ kf2 = Keyframe.ofFloat(1f, endRadiusMultiplier);
+ PropertyValuesHolder radiusDisappear = PropertyValuesHolder.ofKeyframe(
+ radiusPropertyName, kf0, kf1, kf2);
+
+ ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
+ target, radiusDisappear).setDuration(duration);
+ animator.addUpdateListener(updateListener);
+ return animator;
+ }
+
+ private static ObjectAnimator getRadiusReappearAnimator(Object target,
+ String radiusPropertyName, InvalidateUpdateListener updateListener,
+ float midRadiusMultiplier, float endRadiusMultiplier) {
+ Keyframe kf0, kf1, kf2, kf3;
+ float midwayPoint = 0.2f;
+ int duration = 500;
+
+ // Set up animator for reappearing.
+ float delayMultiplier = 0.25f;
+ float transitionDurationMultiplier = 1f;
+ float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
+ int totalDuration = (int) (duration * totalDurationMultiplier);
+ float delayPoint = (delayMultiplier * duration) / totalDuration;
+ midwayPoint = 1 - (midwayPoint * (1 - delayPoint));
+
+ kf0 = Keyframe.ofFloat(0f, endRadiusMultiplier);
+ kf1 = Keyframe.ofFloat(delayPoint, endRadiusMultiplier);
+ kf2 = Keyframe.ofFloat(midwayPoint, midRadiusMultiplier);
+ kf3 = Keyframe.ofFloat(1f, 1);
+ PropertyValuesHolder radiusReappear = PropertyValuesHolder.ofKeyframe(
+ radiusPropertyName, kf0, kf1, kf2, kf3);
+
+ ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
+ target, radiusReappear).setDuration(totalDuration);
+ animator.addUpdateListener(updateListener);
+ return animator;
+ }
+
+ private static ObjectAnimator getFadeOutAnimator(Object target, int startAlpha, int endAlpha,
+ InvalidateUpdateListener updateListener) {
+ int duration = 500;
+ ObjectAnimator animator = ObjectAnimator.ofInt(target, "alpha", startAlpha, endAlpha);
+ animator.setDuration(duration);
+ animator.addUpdateListener(updateListener);
+
+ return animator;
+ }
+
+ private static ObjectAnimator getFadeInAnimator(Object target, int startAlpha, int endAlpha,
+ InvalidateUpdateListener updateListener) {
+ Keyframe kf0, kf1, kf2;
+ int duration = 500;
+
+ // Set up animator for reappearing.
+ float delayMultiplier = 0.25f;
+ float transitionDurationMultiplier = 1f;
+ float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
+ int totalDuration = (int) (duration * totalDurationMultiplier);
+ float delayPoint = (delayMultiplier * duration) / totalDuration;
+
+ kf0 = Keyframe.ofInt(0f, startAlpha);
+ kf1 = Keyframe.ofInt(delayPoint, startAlpha);
+ kf2 = Keyframe.ofInt(1f, endAlpha);
+ PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("alpha", kf0, kf1, kf2);
+
+ ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
+ target, fadeIn).setDuration(totalDuration);
+ animator.addUpdateListener(updateListener);
+ return animator;
+ }
+
+ private class InvalidateUpdateListener implements ValueAnimator.AnimatorUpdateListener {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ RadialTimePickerView.this.invalidate();
+ }
+ }
+
+ private void startHoursToMinutesAnimation() {
+ if (mHoursToMinutesAnims.size() == 0) {
+ mHoursToMinutesAnims.add(getRadiusDisappearAnimator(this,
+ "animationRadiusMultiplierHours", mInvalidateUpdateListener,
+ mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
+ mHoursToMinutesAnims.add(getFadeOutAnimator(mPaint[HOURS],
+ ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+ mHoursToMinutesAnims.add(getFadeOutAnimator(mPaintSelector[HOURS][SELECTOR_CIRCLE],
+ ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+ mHoursToMinutesAnims.add(getFadeOutAnimator(mPaintSelector[HOURS][SELECTOR_DOT],
+ ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+ mHoursToMinutesAnims.add(getFadeOutAnimator(mPaintSelector[HOURS][SELECTOR_LINE],
+ ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+
+ mHoursToMinutesAnims.add(getRadiusReappearAnimator(this,
+ "animationRadiusMultiplierMinutes", mInvalidateUpdateListener,
+ mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
+ mHoursToMinutesAnims.add(getFadeInAnimator(mPaint[MINUTES],
+ ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
+ mHoursToMinutesAnims.add(getFadeInAnimator(mPaintSelector[MINUTES][SELECTOR_CIRCLE],
+ ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
+ mHoursToMinutesAnims.add(getFadeInAnimator(mPaintSelector[MINUTES][SELECTOR_DOT],
+ ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
+ mHoursToMinutesAnims.add(getFadeInAnimator(mPaintSelector[MINUTES][SELECTOR_LINE],
+ ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
+ }
+
+ if (mTransition != null && mTransition.isRunning()) {
+ mTransition.end();
+ }
+ mTransition = new AnimatorSet();
+ mTransition.playTogether(mHoursToMinutesAnims);
+ mTransition.start();
+ }
+
+ private void startMinutesToHoursAnimation() {
+ if (mMinuteToHoursAnims.size() == 0) {
+ mMinuteToHoursAnims.add(getRadiusDisappearAnimator(this,
+ "animationRadiusMultiplierMinutes", mInvalidateUpdateListener,
+ mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
+ mMinuteToHoursAnims.add(getFadeOutAnimator(mPaint[MINUTES],
+ ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+ mMinuteToHoursAnims.add(getFadeOutAnimator(mPaintSelector[MINUTES][SELECTOR_CIRCLE],
+ ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+ mMinuteToHoursAnims.add(getFadeOutAnimator(mPaintSelector[MINUTES][SELECTOR_DOT],
+ ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+ mMinuteToHoursAnims.add(getFadeOutAnimator(mPaintSelector[MINUTES][SELECTOR_LINE],
+ ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+
+ mMinuteToHoursAnims.add(getRadiusReappearAnimator(this,
+ "animationRadiusMultiplierHours", mInvalidateUpdateListener,
+ mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
+ mMinuteToHoursAnims.add(getFadeInAnimator(mPaint[HOURS],
+ ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
+ mMinuteToHoursAnims.add(getFadeInAnimator(mPaintSelector[HOURS][SELECTOR_CIRCLE],
+ ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
+ mMinuteToHoursAnims.add(getFadeInAnimator(mPaintSelector[HOURS][SELECTOR_DOT],
+ ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
+ mMinuteToHoursAnims.add(getFadeInAnimator(mPaintSelector[HOURS][SELECTOR_LINE],
+ ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
+ }
+
+ if (mTransition != null && mTransition.isRunning()) {
+ mTransition.end();
+ }
+ mTransition = new AnimatorSet();
+ mTransition.playTogether(mMinuteToHoursAnims);
+ mTransition.start();
+ }
+
+ private int getDegreesFromXY(float x, float y) {
+ final double hypotenuse = Math.sqrt(
+ (y - mYCenter) * (y - mYCenter) + (x - mXCenter) * (x - mXCenter));
+
+ // Basic check if we're outside the range of the disk
+ if (hypotenuse > mCircleRadius[HOURS]) {
+ return -1;
+ }
+ // Check
+ if (mIs24HourMode && mShowHours) {
+ if (hypotenuse >= mMinHypotenuseForInnerNumber
+ && hypotenuse <= mHalfwayHypotenusePoint) {
+ mIsOnInnerCircle = true;
+ } else if (hypotenuse <= mMaxHypotenuseForOuterNumber
+ && hypotenuse >= mHalfwayHypotenusePoint) {
+ mIsOnInnerCircle = false;
+ } else {
+ return -1;
+ }
+ } else {
+ final int index = (mShowHours) ? HOURS : MINUTES;
+ final float length = (mCircleRadius[index] * mNumbersRadiusMultiplier[index]);
+ final int distanceToNumber = (int) Math.abs(hypotenuse - length);
+ final int maxAllowedDistance =
+ (int) (mCircleRadius[index] * (1 - mNumbersRadiusMultiplier[index]));
+ if (distanceToNumber > maxAllowedDistance) {
+ return -1;
+ }
+ }
+
+ final float opposite = Math.abs(y - mYCenter);
+ double degrees = Math.toDegrees(Math.asin(opposite / hypotenuse));
+
+ // Now we have to translate to the correct quadrant.
+ boolean rightSide = (x > mXCenter);
+ boolean topSide = (y < mYCenter);
+ if (rightSide && topSide) {
+ degrees = 90 - degrees;
+ } else if (rightSide && !topSide) {
+ degrees = 90 + degrees;
+ } else if (!rightSide && !topSide) {
+ degrees = 270 - degrees;
+ } else if (!rightSide && topSide) {
+ degrees = 270 + degrees;
+ }
+ return (int) degrees;
+ }
+
+ private int getIsTouchingAmOrPm(float x, float y) {
+ final boolean isLayoutRtl = isLayoutRtl();
+ int squaredYDistance = (int) ((y - mAmPmYCenter) * (y - mAmPmYCenter));
+
+ int distanceToAmCenter = (int) Math.sqrt(
+ (x - mLeftIndicatorXCenter) * (x - mLeftIndicatorXCenter) + squaredYDistance);
+ if (distanceToAmCenter <= mAmPmCircleRadius) {
+ return (isLayoutRtl ? PM : AM);
+ }
+
+ int distanceToPmCenter = (int) Math.sqrt(
+ (x - mRightIndicatorXCenter) * (x - mRightIndicatorXCenter) + squaredYDistance);
+ if (distanceToPmCenter <= mAmPmCircleRadius) {
+ return (isLayoutRtl ? AM : PM);
+ }
+
+ // Neither was close enough.
+ return -1;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if(!mInputEnabled) {
+ return true;
+ }
+
+ final float eventX = event.getX();
+ final float eventY = event.getY();
+
+ int degrees;
+ int snapDegrees;
+ boolean result = false;
+
+ switch(event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ mAmOrPmPressed = getIsTouchingAmOrPm(eventX, eventY);
+ if (mAmOrPmPressed != -1) {
+ result = true;
+ } else {
+ degrees = getDegreesFromXY(eventX, eventY);
+ if (degrees != -1) {
+ snapDegrees = (mShowHours ?
+ snapOnly30s(degrees, 0) : snapPrefer30s(degrees)) % 360;
+ if (mShowHours) {
+ mSelectionDegrees[HOURS] = snapDegrees;
+ mSelectionDegrees[HOURS_INNER] = snapDegrees;
+ } else {
+ mSelectionDegrees[MINUTES] = snapDegrees;
+ }
+ performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
+ if (mListener != null) {
+ if (mShowHours) {
+ mListener.onValueSelected(HOURS, getCurrentHour(), false);
+ } else {
+ mListener.onValueSelected(MINUTES, getCurrentMinute(), false);
+ }
+ }
+ result = true;
+ }
+ }
+ invalidate();
+ return result;
+
+ case MotionEvent.ACTION_UP:
+ mAmOrPmPressed = getIsTouchingAmOrPm(eventX, eventY);
+ if (mAmOrPmPressed != -1) {
+ if (mAmOrPm != mAmOrPmPressed) {
+ swapAmPm();
+ }
+ mAmOrPmPressed = -1;
+ if (mListener != null) {
+ mListener.onValueSelected(AMPM, getCurrentHour(), true);
+ }
+ result = true;
+ } else {
+ degrees = getDegreesFromXY(eventX, eventY);
+ if (degrees != -1) {
+ snapDegrees = (mShowHours ?
+ snapOnly30s(degrees, 0) : snapPrefer30s(degrees)) % 360;
+ if (mShowHours) {
+ mSelectionDegrees[HOURS] = snapDegrees;
+ mSelectionDegrees[HOURS_INNER] = snapDegrees;
+ } else {
+ mSelectionDegrees[MINUTES] = snapDegrees;
+ }
+ if (mListener != null) {
+ if (mShowHours) {
+ mListener.onValueSelected(HOURS, getCurrentHour(), true);
+ } else {
+ mListener.onValueSelected(MINUTES, getCurrentMinute(), true);
+ }
+ }
+ result = true;
+ }
+ }
+ if (result) {
+ invalidate();
+ }
+ return result;
+
+ default:
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * Necessary for accessibility, to ensure we support "scrolling" forward and backward
+ * in the circle.
+ */
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ }
+
+ /**
+ * Announce the currently-selected time when launched.
+ */
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ // Clear the event's current text so that only the current time will be spoken.
+ event.getText().clear();
+ Time time = new Time();
+ time.hour = getCurrentHour();
+ time.minute = getCurrentMinute();
+ long millis = time.normalize(true);
+ int flags = DateUtils.FORMAT_SHOW_TIME;
+ if (mIs24HourMode) {
+ flags |= DateUtils.FORMAT_24HOUR;
+ }
+ String timeString = DateUtils.formatDateTime(getContext(), millis, flags);
+ event.getText().add(timeString);
+ return true;
+ }
+ return super.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ /**
+ * When scroll forward/backward events are received, jump the time to the higher/lower
+ * discrete, visible value on the circle.
+ */
+ @SuppressLint("NewApi")
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (super.performAccessibilityAction(action, arguments)) {
+ return true;
+ }
+
+ int changeMultiplier = 0;
+ if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
+ changeMultiplier = 1;
+ } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+ changeMultiplier = -1;
+ }
+ if (changeMultiplier != 0) {
+ int value = 0;
+ int stepSize = 0;
+ if (mShowHours) {
+ stepSize = DEGREES_FOR_ONE_HOUR;
+ value = getCurrentHour() % 12;
+ } else {
+ stepSize = DEGREES_FOR_ONE_MINUTE;
+ value = getCurrentMinute();
+ }
+
+ int degrees = value * stepSize;
+ degrees = snapOnly30s(degrees, changeMultiplier);
+ value = degrees / stepSize;
+ int maxValue = 0;
+ int minValue = 0;
+ if (mShowHours) {
+ if (mIs24HourMode) {
+ maxValue = 23;
+ } else {
+ maxValue = 12;
+ minValue = 1;
+ }
+ } else {
+ maxValue = 55;
+ }
+ if (value > maxValue) {
+ // If we scrolled forward past the highest number, wrap around to the lowest.
+ value = minValue;
+ } else if (value < minValue) {
+ // If we scrolled backward past the lowest number, wrap around to the highest.
+ value = maxValue;
+ }
+ if (mShowHours) {
+ setCurrentHour(value);
+ if (mListener != null) {
+ mListener.onValueSelected(HOURS, value, false);
+ }
+ } else {
+ setCurrentMinute(value);
+ if (mListener != null) {
+ mListener.onValueSelected(MINUTES, value, false);
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ public void setInputEnabled(boolean inputEnabled) {
+ mInputEnabled = inputEnabled;
+ invalidate();
+ }
+}
diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java
index a0fef7d..afc4830 100644
--- a/core/java/android/widget/RadioButton.java
+++ b/core/java/android/widget/RadioButton.java
@@ -21,8 +21,6 @@ import android.util.AttributeSet;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
-import com.android.internal.R;
-
/**
* <p>
@@ -59,8 +57,12 @@ public class RadioButton extends CompoundButton {
this(context, attrs, com.android.internal.R.attr.radioButtonStyle);
}
- public RadioButton(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public RadioButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public RadioButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
/**
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
index 4d3c56c..82b490e 100644
--- a/core/java/android/widget/RatingBar.java
+++ b/core/java/android/widget/RatingBar.java
@@ -82,11 +82,15 @@ public class RatingBar extends AbsSeekBar {
private OnRatingBarChangeListener mOnRatingBarChangeListener;
- public RatingBar(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RatingBar,
- defStyle, 0);
+ public RatingBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public RatingBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.RatingBar, defStyleAttr, defStyleRes);
final int numStars = a.getInt(R.styleable.RatingBar_numStars, mNumStars);
setIsIndicator(a.getBoolean(R.styleable.RatingBar_isIndicator, !mIsUserSeekable));
final float rating = a.getFloat(R.styleable.RatingBar_rating, -1);
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index e03e83d..70ef10b 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -228,24 +228,27 @@ public class RelativeLayout extends ViewGroup {
private static final int DEFAULT_WIDTH = 0x00010000;
public RelativeLayout(Context context) {
- super(context);
- queryCompatibilityModes(context);
+ this(context, null);
}
public RelativeLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- initFromAttributes(context, attrs);
- queryCompatibilityModes(context);
+ this(context, attrs, 0);
+ }
+
+ public RelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
- public RelativeLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- initFromAttributes(context, attrs);
+ public RelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
queryCompatibilityModes(context);
}
- private void initFromAttributes(Context context, AttributeSet attrs) {
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RelativeLayout);
+ private void initFromAttributes(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.RelativeLayout, defStyleAttr, defStyleRes);
mIgnoreGravity = a.getResourceId(R.styleable.RelativeLayout_ignoreGravity, View.NO_ID);
mGravity = a.getInt(R.styleable.RelativeLayout_gravity, mGravity);
a.recycle();
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 3ff0cee..bbe6f9e 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -32,7 +32,6 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
@@ -45,7 +44,6 @@ import android.widget.RemoteViews.OnClickHandler;
import com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory;
-import com.android.internal.widget.LockPatternUtils;
/**
* An adapter to a RemoteViewsService which fetches and caches RemoteViews
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 6680393..082d728 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -162,12 +162,16 @@ public class ScrollView extends FrameLayout {
this(context, attrs, com.android.internal.R.attr.scrollViewStyle);
}
- public ScrollView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public ScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
initScrollView();
- TypedArray a =
- context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ScrollView, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.ScrollView, defStyleAttr, defStyleRes);
setFillViewport(a.getBoolean(R.styleable.ScrollView_fillViewport, false));
diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java
index 3bfd39d..1a0ce9c 100644
--- a/core/java/android/widget/Scroller.java
+++ b/core/java/android/widget/Scroller.java
@@ -61,6 +61,8 @@ import android.view.animation.Interpolator;
* }</pre>
*/
public class Scroller {
+ private final Interpolator mInterpolator;
+
private int mMode;
private int mStartX;
@@ -81,7 +83,6 @@ public class Scroller {
private float mDeltaX;
private float mDeltaY;
private boolean mFinished;
- private Interpolator mInterpolator;
private boolean mFlywheel;
private float mVelocity;
@@ -142,18 +143,8 @@ public class Scroller {
SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
}
SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
-
- // This controls the viscous fluid effect (how much of it)
- sViscousFluidScale = 8.0f;
- // must be set to 1.0 (used in viscousFluid())
- sViscousFluidNormalize = 1.0f;
- sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
-
}
- private static float sViscousFluidScale;
- private static float sViscousFluidNormalize;
-
/**
* Create a Scroller with the default duration and interpolator.
*/
@@ -178,7 +169,11 @@ public class Scroller {
*/
public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
mFinished = true;
- mInterpolator = interpolator;
+ if (interpolator == null) {
+ mInterpolator = new ViscousFluidInterpolator();
+ } else {
+ mInterpolator = interpolator;
+ }
mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
mFlywheel = flywheel;
@@ -312,13 +307,7 @@ public class Scroller {
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
- float x = timePassed * mDurationReciprocal;
-
- if (mInterpolator == null)
- x = viscousFluid(x);
- else
- x = mInterpolator.getInterpolation(x);
-
+ final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
@@ -499,20 +488,6 @@ public class Scroller {
return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
}
- static float viscousFluid(float x)
- {
- x *= sViscousFluidScale;
- if (x < 1.0f) {
- x -= (1.0f - (float)Math.exp(-x));
- } else {
- float start = 0.36787944117f; // 1/e == exp(-1)
- x = 1.0f - (float)Math.exp(1.0f - x);
- x = start + x * (1.0f - start);
- }
- x *= sViscousFluidNormalize;
- return x;
- }
-
/**
* Stops the animation. Contrary to {@link #forceFinished(boolean)},
* aborting the animating cause the scroller to move to the final x and y
@@ -583,4 +558,41 @@ public class Scroller {
return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) &&
Math.signum(yvel) == Math.signum(mFinalY - mStartY);
}
+
+ static class ViscousFluidInterpolator implements Interpolator {
+ /** Controls the viscous fluid effect (how much of it). */
+ private static final float VISCOUS_FLUID_SCALE = 8.0f;
+
+ private static final float VISCOUS_FLUID_NORMALIZE;
+ private static final float VISCOUS_FLUID_OFFSET;
+
+ static {
+
+ // must be set to 1.0 (used in viscousFluid())
+ VISCOUS_FLUID_NORMALIZE = 1.0f / viscousFluid(1.0f);
+ // account for very small floating-point error
+ VISCOUS_FLUID_OFFSET = 1.0f - VISCOUS_FLUID_NORMALIZE * viscousFluid(1.0f);
+ }
+
+ private static float viscousFluid(float x) {
+ x *= VISCOUS_FLUID_SCALE;
+ if (x < 1.0f) {
+ x -= (1.0f - (float)Math.exp(-x));
+ } else {
+ float start = 0.36787944117f; // 1/e == exp(-1)
+ x = 1.0f - (float)Math.exp(1.0f - x);
+ x = start + x * (1.0f - start);
+ }
+ return x;
+ }
+
+ @Override
+ public float getInterpolation(float input) {
+ final float interpolated = VISCOUS_FLUID_NORMALIZE * viscousFluid(input);
+ if (input > 0) {
+ return input + VISCOUS_FLUID_OFFSET;
+ }
+ return input;
+ }
+ }
}
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 0281602..3791258 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -242,7 +242,15 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
public SearchView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
+ }
+
+ public SearchView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SearchView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -281,7 +289,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
});
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SearchView, 0, 0);
+ TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.SearchView, defStyleAttr, defStyleRes);
setIconifiedByDefault(a.getBoolean(R.styleable.SearchView_iconifiedByDefault, true));
int maxWidth = a.getDimensionPixelSize(R.styleable.SearchView_maxWidth, -1);
if (maxWidth != -1) {
@@ -304,7 +313,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
boolean focusable = true;
- a = context.obtainStyledAttributes(attrs, R.styleable.View, 0, 0);
+ a = context.obtainStyledAttributes(attrs, R.styleable.View, defStyleAttr, defStyleRes);
focusable = a.getBoolean(R.styleable.View_focusable, focusable);
a.recycle();
setFocusable(focusable);
@@ -1661,8 +1670,14 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
mThreshold = getThreshold();
}
- public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public SearchAutoComplete(Context context, AttributeSet attrs, int defStyleAttrs) {
+ super(context, attrs, defStyleAttrs);
+ mThreshold = getThreshold();
+ }
+
+ public SearchAutoComplete(
+ Context context, AttributeSet attrs, int defStyleAttrs, int defStyleRes) {
+ super(context, attrs, defStyleAttrs, defStyleRes);
mThreshold = getThreshold();
}
diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java
index 2737f94..dc7c04c 100644
--- a/core/java/android/widget/SeekBar.java
+++ b/core/java/android/widget/SeekBar.java
@@ -79,8 +79,12 @@ public class SeekBar extends AbsSeekBar {
this(context, attrs, com.android.internal.R.attr.seekBarStyle);
}
- public SeekBar(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public SeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
diff --git a/core/java/android/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java
index 517246b..ec06c02 100644
--- a/core/java/android/widget/SlidingDrawer.java
+++ b/core/java/android/widget/SlidingDrawer.java
@@ -192,11 +192,32 @@ public class SlidingDrawer extends ViewGroup {
*
* @param context The application's environment.
* @param attrs The attributes defined in XML.
- * @param defStyle The style to apply to this widget.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
*/
- public SlidingDrawer(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingDrawer, defStyle, 0);
+ public SlidingDrawer(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ /**
+ * Creates a new SlidingDrawer from a specified set of attributes defined in XML.
+ *
+ * @param context The application's environment.
+ * @param attrs The attributes defined in XML.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ */
+ public SlidingDrawer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.SlidingDrawer, defStyleAttr, defStyleRes);
int orientation = a.getInt(R.styleable.SlidingDrawer_orientation, ORIENTATION_VERTICAL);
mVertical = orientation == ORIENTATION_VERTICAL;
diff --git a/core/java/android/widget/Space.java b/core/java/android/widget/Space.java
index bb53a77..c4eaeb7 100644
--- a/core/java/android/widget/Space.java
+++ b/core/java/android/widget/Space.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
-import android.view.ViewGroup;
/**
* Space is a lightweight View subclass that may be used to create gaps between components
@@ -30,8 +29,8 @@ public final class Space extends View {
/**
* {@inheritDoc}
*/
- public Space(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public Space(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
if (getVisibility() == VISIBLE) {
setVisibility(INVISIBLE);
}
@@ -40,6 +39,13 @@ public final class Space extends View {
/**
* {@inheritDoc}
*/
+ public Space(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public Space(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index b75d36f..afe5804 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -130,18 +130,17 @@ public class Spinner extends AbsSpinner implements OnClickListener {
/**
* Construct a new spinner with the given context's theme, the supplied attribute set,
- * and default style.
+ * and default style attribute.
*
* @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.
- * @param defStyle The default style to apply to this view. If 0, no style
- * will be applied (beyond what is included in the theme). This may
- * either be an attribute resource, whose value will be retrieved
- * from the current theme, or an explicit style resource.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
*/
- public Spinner(Context context, AttributeSet attrs, int defStyle) {
- this(context, attrs, defStyle, MODE_THEME);
+ public Spinner(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0, MODE_THEME);
}
/**
@@ -152,20 +151,44 @@ public class Spinner extends AbsSpinner implements OnClickListener {
* @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.
- * @param defStyle The default style to apply to this view. If 0, no style
- * will be applied (beyond what is included in the theme). This may
- * either be an attribute resource, whose value will be retrieved
- * from the current theme, or an explicit style resource.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
* @param mode Constant describing how the user will select choices from the spinner.
- *
+ *
+ * @see #MODE_DIALOG
+ * @see #MODE_DROPDOWN
+ */
+ public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
+ this(context, attrs, defStyleAttr, 0, mode);
+ }
+
+ /**
+ * Construct a new spinner with the given context's theme, the supplied attribute set,
+ * and default style. <code>mode</code> may be one of {@link #MODE_DIALOG} or
+ * {@link #MODE_DROPDOWN} and determines how the user will select choices from the spinner.
+ *
+ * @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.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ * @param mode Constant describing how the user will select choices from the spinner.
+ *
* @see #MODE_DIALOG
* @see #MODE_DROPDOWN
*/
- public Spinner(Context context, AttributeSet attrs, int defStyle, int mode) {
- super(context, attrs, defStyle);
+ public Spinner(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.Spinner, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.Spinner, defStyleAttr, defStyleRes);
if (mode == MODE_THEME) {
mode = a.getInt(com.android.internal.R.styleable.Spinner_spinnerMode, MODE_DIALOG);
@@ -178,7 +201,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
}
case MODE_DROPDOWN: {
- final DropdownPopup popup = new DropdownPopup(context, attrs, defStyle);
+ final DropdownPopup popup = new DropdownPopup(context, attrs, defStyleAttr, defStyleRes);
mDropDownWidth = a.getLayoutDimension(
com.android.internal.R.styleable.Spinner_dropDownWidth,
@@ -1031,8 +1054,9 @@ public class Spinner extends AbsSpinner implements OnClickListener {
private CharSequence mHintText;
private ListAdapter mAdapter;
- public DropdownPopup(Context context, AttributeSet attrs, int defStyleRes) {
- super(context, attrs, 0, defStyleRes);
+ public DropdownPopup(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
setAnchorView(Spinner.this);
setModal(true);
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 6853660..d2e718c 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -168,9 +168,16 @@ public class StackView extends AdapterViewAnimator {
* {@inheritDoc}
*/
public StackView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.StackView, defStyleAttr, 0);
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public StackView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.StackView, defStyleAttr, defStyleRes);
mResOutColor = a.getColor(
com.android.internal.R.styleable.StackView_resOutColor, 0);
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index e754c17..c9a1ca4 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -32,6 +33,8 @@ import android.text.TextUtils;
import android.text.method.AllCapsTransformationMethod;
import android.text.method.TransformationMethod2;
import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.MathUtils;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -66,6 +69,8 @@ import com.android.internal.R;
* @attr ref android.R.styleable#Switch_track
*/
public class Switch extends CompoundButton {
+ private static final int THUMB_ANIMATION_DURATION = 250;
+
private static final int TOUCH_MODE_IDLE = 0;
private static final int TOUCH_MODE_DOWN = 1;
private static final int TOUCH_MODE_DRAGGING = 2;
@@ -105,6 +110,7 @@ public class Switch extends CompoundButton {
private Layout mOnLayout;
private Layout mOffLayout;
private TransformationMethod2 mSwitchTransformationMethod;
+ private ObjectAnimator mPositionAnimator;
@SuppressWarnings("hiding")
private final Rect mTempRect = new Rect();
@@ -139,19 +145,41 @@ public class Switch extends CompoundButton {
*
* @param context The Context that will determine this widget's theming.
* @param attrs Specification of attributes that should deviate from the default styling.
- * @param defStyle An attribute ID within the active theme containing a reference to the
- * default style for this widget. e.g. android.R.attr.switchStyle.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
*/
- public Switch(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public Switch(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+
+ /**
+ * Construct a new Switch with a default style determined by the given theme
+ * attribute or style resource, overriding specific style attributes as
+ * requested.
+ *
+ * @param context The Context that will determine this widget's theming.
+ * @param attrs Specification of attributes that should deviate from the
+ * default styling.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme. Can be 0
+ * to not look for defaults.
+ */
+ public Switch(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
Resources res = getResources();
mTextPaint.density = res.getDisplayMetrics().density;
mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.Switch, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.Switch, defStyleAttr, defStyleRes);
mThumbDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_thumb);
mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track);
@@ -528,9 +556,12 @@ public class Switch extends CompoundButton {
* @return true if (x, y) is within the target area of the switch thumb
*/
private boolean hitThumb(float x, float y) {
+ // Relies on mTempRect, MUST be called first!
+ final int thumbOffset = getThumbOffset();
+
mThumbDrawable.getPadding(mTempRect);
final int thumbTop = mSwitchTop - mTouchSlop;
- final int thumbLeft = mSwitchLeft + (int) (mThumbPosition + 0.5f) - mTouchSlop;
+ final int thumbLeft = mSwitchLeft + thumbOffset - mTouchSlop;
final int thumbRight = thumbLeft + mThumbWidth +
mTempRect.left + mTempRect.right + mTouchSlop;
final int thumbBottom = mSwitchBottom + mTouchSlop;
@@ -575,13 +606,23 @@ public class Switch extends CompoundButton {
case TOUCH_MODE_DRAGGING: {
final float x = ev.getX();
- final float dx = x - mTouchX;
- float newPos = Math.max(0,
- Math.min(mThumbPosition + dx, getThumbScrollRange()));
+ final int thumbScrollRange = getThumbScrollRange();
+ final float thumbScrollOffset = x - mTouchX;
+ float dPos;
+ if (thumbScrollRange != 0) {
+ dPos = thumbScrollOffset / thumbScrollRange;
+ } else {
+ // If the thumb scroll range is empty, just use the
+ // movement direction to snap on or off.
+ dPos = thumbScrollOffset > 0 ? 1 : -1;
+ }
+ if (isLayoutRtl()) {
+ dPos = -dPos;
+ }
+ final float newPos = MathUtils.constrain(mThumbPosition + dPos, 0, 1);
if (newPos != mThumbPosition) {
- mThumbPosition = newPos;
mTouchX = x;
- invalidate();
+ setThumbPosition(newPos);
}
return true;
}
@@ -618,62 +659,77 @@ public class Switch extends CompoundButton {
*/
private void stopDrag(MotionEvent ev) {
mTouchMode = TOUCH_MODE_IDLE;
- // Up and not canceled, also checks the switch has not been disabled during the drag
- boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
-
- cancelSuperTouch(ev);
+ // Commit the change if the event is up and not canceled and the switch
+ // has not been disabled during the drag.
+ final boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
+ final boolean newState;
if (commitChange) {
- boolean newState;
mVelocityTracker.computeCurrentVelocity(1000);
- float xvel = mVelocityTracker.getXVelocity();
+ final float xvel = mVelocityTracker.getXVelocity();
if (Math.abs(xvel) > mMinFlingVelocity) {
newState = isLayoutRtl() ? (xvel < 0) : (xvel > 0);
} else {
newState = getTargetCheckedState();
}
- animateThumbToCheckedState(newState);
} else {
- animateThumbToCheckedState(isChecked());
+ newState = isChecked();
}
+
+ setChecked(newState);
+ cancelSuperTouch(ev);
}
private void animateThumbToCheckedState(boolean newCheckedState) {
- // TODO animate!
- //float targetPos = newCheckedState ? 0 : getThumbScrollRange();
- //mThumbPosition = targetPos;
- setChecked(newCheckedState);
+ final float targetPosition = newCheckedState ? 1 : 0;
+ mPositionAnimator = ObjectAnimator.ofFloat(this, THUMB_POS, targetPosition);
+ mPositionAnimator.setDuration(THUMB_ANIMATION_DURATION);
+ mPositionAnimator.setAutoCancel(true);
+ mPositionAnimator.start();
}
- private boolean getTargetCheckedState() {
- if (isLayoutRtl()) {
- return mThumbPosition <= getThumbScrollRange() / 2;
- } else {
- return mThumbPosition >= getThumbScrollRange() / 2;
+ private void cancelPositionAnimator() {
+ if (mPositionAnimator != null) {
+ mPositionAnimator.cancel();
}
}
- private void setThumbPosition(boolean checked) {
- if (isLayoutRtl()) {
- mThumbPosition = checked ? 0 : getThumbScrollRange();
- } else {
- mThumbPosition = checked ? getThumbScrollRange() : 0;
- }
+ private boolean getTargetCheckedState() {
+ return mThumbPosition > 0.5f;
+ }
+
+ /**
+ * Sets the thumb position as a decimal value between 0 (off) and 1 (on).
+ *
+ * @param position new position between [0,1]
+ */
+ private void setThumbPosition(float position) {
+ mThumbPosition = position;
+ invalidate();
+ }
+
+ @Override
+ public void toggle() {
+ setChecked(!isChecked());
}
@Override
public void setChecked(boolean checked) {
super.setChecked(checked);
- setThumbPosition(isChecked());
- invalidate();
+
+ if (isAttachedToWindow() && isLaidOut()) {
+ animateThumbToCheckedState(checked);
+ } else {
+ // Immediately move the thumb to the new position.
+ cancelPositionAnimator();
+ setThumbPosition(checked ? 1 : 0);
+ }
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- setThumbPosition(isChecked());
-
int switchRight;
int switchLeft;
@@ -734,11 +790,12 @@ public class Switch extends CompoundButton {
int switchInnerBottom = switchBottom - mTempRect.bottom;
canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
+ // Relies on mTempRect, MUST be called first!
+ final int thumbPos = getThumbOffset();
+
mThumbDrawable.getPadding(mTempRect);
- final int thumbPos = (int) (mThumbPosition + 0.5f);
int thumbLeft = switchInnerLeft - mTempRect.left + thumbPos;
int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + mTempRect.right;
-
mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
mThumbDrawable.draw(canvas);
@@ -783,6 +840,22 @@ public class Switch extends CompoundButton {
return padding;
}
+ /**
+ * Translates thumb position to offset according to current RTL setting and
+ * thumb scroll range.
+ *
+ * @return thumb offset
+ */
+ private int getThumbOffset() {
+ final float thumbPosition;
+ if (isLayoutRtl()) {
+ thumbPosition = 1 - mThumbPosition;
+ } else {
+ thumbPosition = mThumbPosition;
+ }
+ return (int) (thumbPosition * getThumbScrollRange() + 0.5f);
+ }
+
private int getThumbScrollRange() {
if (mTrackDrawable == null) {
return 0;
@@ -848,4 +921,16 @@ public class Switch extends CompoundButton {
}
}
}
+
+ private static final FloatProperty<Switch> THUMB_POS = new FloatProperty<Switch>("thumbPos") {
+ @Override
+ public Float get(Switch object) {
+ return object.mThumbPosition;
+ }
+
+ @Override
+ public void setValue(Switch object, float value) {
+ object.setThumbPosition(value);
+ }
+ };
}
diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java
index 238dc55..89df51a 100644
--- a/core/java/android/widget/TabHost.java
+++ b/core/java/android/widget/TabHost.java
@@ -77,11 +77,18 @@ public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchMode
}
public TabHost(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
+ }
+
+ public TabHost(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public TabHost(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.TabWidget,
- com.android.internal.R.attr.tabWidgetStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.TabWidget, defStyleAttr, defStyleRes);
mTabLayoutId = a.getResourceId(R.styleable.TabWidget_tabLayout, 0);
a.recycle();
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 6bced1c..568b3e6 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -74,11 +74,15 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
this(context, attrs, com.android.internal.R.attr.tabWidgetStyle);
}
- public TabWidget(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public TabWidget(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public TabWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
final TypedArray a = context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.TabWidget, defStyle, 0);
+ attrs, com.android.internal.R.styleable.TabWidget, defStyleAttr, defStyleRes);
setStripEnabled(a.getBoolean(R.styleable.TabWidget_tabStripEnabled, true));
setLeftStripDrawable(a.getDrawable(R.styleable.TabWidget_tabStripLeft));
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index b3b95d9..4c5c71d 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -198,15 +198,19 @@ public class TextClock extends TextView {
* @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
- * @param defStyle The default style to apply to this view. If 0, no style
- * will be applied (beyond what is included in the theme). This may
- * either be an attribute resource, whose value will be retrieved
- * from the current theme, or an explicit style resource
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default values for
+ * the view. Can be 0 to not look for defaults.
*/
- public TextClock(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public TextClock(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public TextClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextClock, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.TextClock, defStyleAttr, defStyleRes);
try {
mFormat12 = a.getText(R.styleable.TextClock_format12Hour);
mFormat24 = a.getText(R.styleable.TextClock_format24Hour);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7a9809f..5d42589 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -136,7 +136,6 @@ import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Locale;
-import java.util.concurrent.locks.ReentrantLock;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
@@ -618,9 +617,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
this(context, attrs, com.android.internal.R.attr.textViewStyle);
}
+ public TextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
@SuppressWarnings("deprecation")
- public TextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public TextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
mText = "";
final Resources res = getResources();
@@ -657,8 +661,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* to be able to parse the appearance first and then let specific tags
* for this View override it.
*/
- TypedArray a = theme.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.TextViewAppearance, defStyle, 0);
+ TypedArray a = theme.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
TypedArray appearance = null;
int ap = a.getResourceId(
com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
@@ -751,7 +755,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int inputType = EditorInfo.TYPE_NULL;
a = theme.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.TextView, defStyle, 0);
+ attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
@@ -1275,9 +1279,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* However, TextViews that have input or movement methods *are*
* focusable by default.
*/
- a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.View,
- defStyle, 0);
+ a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
boolean focusable = mMovement != null || getKeyListener() != null;
boolean clickable = focusable;
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index c26cb24..8e4ba0d 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -20,26 +20,17 @@ import android.annotation.Widget;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
-import android.os.Parcel;
import android.os.Parcelable;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.NumberPicker.OnValueChangeListener;
import com.android.internal.R;
-import java.text.DateFormatSymbols;
-import java.util.Calendar;
import java.util.Locale;
+import static android.os.Build.VERSION_CODES.KITKAT;
+
/**
* A view for selecting the time of day, in either 24 hour or AM/PM mode. The
* hour, each minute digit, and AM/PM (if applicable) can be conrolled by
@@ -57,58 +48,12 @@ import java.util.Locale;
@Widget
public class TimePicker extends FrameLayout {
- private static final boolean DEFAULT_ENABLED_STATE = true;
+ private TimePickerDelegate mDelegate;
- private static final int HOURS_IN_HALF_DAY = 12;
-
- /**
- * A no-op callback used in the constructor to avoid null checks later in
- * the code.
- */
- private static final OnTimeChangedListener NO_OP_CHANGE_LISTENER = new OnTimeChangedListener() {
- public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
- }
- };
-
- // state
- private boolean mIs24HourView;
-
- private boolean mIsAm;
-
- // ui components
- private final NumberPicker mHourSpinner;
-
- private final NumberPicker mMinuteSpinner;
-
- private final NumberPicker mAmPmSpinner;
-
- private final EditText mHourSpinnerInput;
-
- private final EditText mMinuteSpinnerInput;
-
- private final EditText mAmPmSpinnerInput;
-
- private final TextView mDivider;
-
- // Note that the legacy implementation of the TimePicker is
- // using a button for toggling between AM/PM while the new
- // version uses a NumberPicker spinner. Therefore the code
- // accommodates these two cases to be backwards compatible.
- private final Button mAmPmButton;
-
- private final String[] mAmPmStrings;
-
- private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
-
- // callbacks
- private OnTimeChangedListener mOnTimeChangedListener;
-
- private Calendar mTempCalendar;
-
- private Locale mCurrentLocale;
-
- private boolean mHourWithTwoDigit;
- private char mHourFormat;
+ private AttributeSet mAttrs;
+ private int mDefStyleAttr;
+ private int mDefStyleRes;
+ private Context mContext;
/**
* The callback interface used to indicate the time has been adjusted.
@@ -131,345 +76,79 @@ public class TimePicker extends FrameLayout {
this(context, attrs, R.attr.timePickerStyle);
}
- public TimePicker(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- // initialization based on locale
- setCurrentLocale(Locale.getDefault());
-
- // process style attributes
- TypedArray attributesArray = context.obtainStyledAttributes(
- attrs, R.styleable.TimePicker, defStyle, 0);
- int layoutResourceId = attributesArray.getResourceId(
- R.styleable.TimePicker_internalLayout, R.layout.time_picker);
- attributesArray.recycle();
-
- LayoutInflater inflater = (LayoutInflater) context.getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(layoutResourceId, this, true);
-
- // hour
- mHourSpinner = (NumberPicker) findViewById(R.id.hour);
- mHourSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
- public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
- updateInputState();
- if (!is24HourView()) {
- if ((oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY)
- || (oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1)) {
- mIsAm = !mIsAm;
- updateAmPmControl();
- }
- }
- onTimeChanged();
- }
- });
- mHourSpinnerInput = (EditText) mHourSpinner.findViewById(R.id.numberpicker_input);
- mHourSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
-
- // divider (only for the new widget style)
- mDivider = (TextView) findViewById(R.id.divider);
- if (mDivider != null) {
- setDividerText();
- }
-
- // minute
- mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
- mMinuteSpinner.setMinValue(0);
- mMinuteSpinner.setMaxValue(59);
- mMinuteSpinner.setOnLongPressUpdateInterval(100);
- mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
- mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
- public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
- updateInputState();
- int minValue = mMinuteSpinner.getMinValue();
- int maxValue = mMinuteSpinner.getMaxValue();
- if (oldVal == maxValue && newVal == minValue) {
- int newHour = mHourSpinner.getValue() + 1;
- if (!is24HourView() && newHour == HOURS_IN_HALF_DAY) {
- mIsAm = !mIsAm;
- updateAmPmControl();
- }
- mHourSpinner.setValue(newHour);
- } else if (oldVal == minValue && newVal == maxValue) {
- int newHour = mHourSpinner.getValue() - 1;
- if (!is24HourView() && newHour == HOURS_IN_HALF_DAY - 1) {
- mIsAm = !mIsAm;
- updateAmPmControl();
- }
- mHourSpinner.setValue(newHour);
- }
- onTimeChanged();
- }
- });
- mMinuteSpinnerInput = (EditText) mMinuteSpinner.findViewById(R.id.numberpicker_input);
- mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
-
- /* Get the localized am/pm strings and use them in the spinner */
- mAmPmStrings = new DateFormatSymbols().getAmPmStrings();
-
- // am/pm
- View amPmView = findViewById(R.id.amPm);
- if (amPmView instanceof Button) {
- mAmPmSpinner = null;
- mAmPmSpinnerInput = null;
- mAmPmButton = (Button) amPmView;
- mAmPmButton.setOnClickListener(new OnClickListener() {
- public void onClick(View button) {
- button.requestFocus();
- mIsAm = !mIsAm;
- updateAmPmControl();
- onTimeChanged();
- }
- });
- } else {
- mAmPmButton = null;
- mAmPmSpinner = (NumberPicker) amPmView;
- mAmPmSpinner.setMinValue(0);
- mAmPmSpinner.setMaxValue(1);
- mAmPmSpinner.setDisplayedValues(mAmPmStrings);
- mAmPmSpinner.setOnValueChangedListener(new OnValueChangeListener() {
- public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
- updateInputState();
- picker.requestFocus();
- mIsAm = !mIsAm;
- updateAmPmControl();
- onTimeChanged();
- }
- });
- mAmPmSpinnerInput = (EditText) mAmPmSpinner.findViewById(R.id.numberpicker_input);
- mAmPmSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
- }
-
- if (isAmPmAtStart()) {
- // Move the am/pm view to the beginning
- ViewGroup amPmParent = (ViewGroup) findViewById(R.id.timePickerLayout);
- amPmParent.removeView(amPmView);
- amPmParent.addView(amPmView, 0);
- // Swap layout margins if needed. They may be not symmetrical (Old Standard Theme for
- // example and not for Holo Theme)
- ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) amPmView.getLayoutParams();
- final int startMargin = lp.getMarginStart();
- final int endMargin = lp.getMarginEnd();
- if (startMargin != endMargin) {
- lp.setMarginStart(endMargin);
- lp.setMarginEnd(startMargin);
- }
- }
-
- getHourFormatData();
-
- // update controls to initial state
- updateHourControl();
- updateMinuteControl();
- updateAmPmControl();
-
- setOnTimeChangedListener(NO_OP_CHANGE_LISTENER);
-
- // set to current time
- setCurrentHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
- setCurrentMinute(mTempCalendar.get(Calendar.MINUTE));
-
- if (!isEnabled()) {
- setEnabled(false);
- }
-
- // set the content descriptions
- setContentDescriptions();
-
- // If not explicitly specified this view is important for accessibility.
- if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
- setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
+ public TimePicker(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
- private void getHourFormatData() {
- final Locale defaultLocale = Locale.getDefault();
- final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale,
- (mIs24HourView) ? "Hm" : "hm");
- final int lengthPattern = bestDateTimePattern.length();
- mHourWithTwoDigit = false;
- char hourFormat = '\0';
- // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
- // the hour format that we found.
- for (int i = 0; i < lengthPattern; i++) {
- final char c = bestDateTimePattern.charAt(i);
- if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
- mHourFormat = c;
- if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
- mHourWithTwoDigit = true;
- }
- break;
- }
- }
- }
+ public TimePicker(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- private boolean isAmPmAtStart() {
- final Locale defaultLocale = Locale.getDefault();
- final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale,
- "hm" /* skeleton */);
+ mContext = context;
+ mAttrs = attrs;
+ mDefStyleAttr = defStyleAttr;
+ mDefStyleRes = defStyleRes;
- return bestDateTimePattern.startsWith("a");
- }
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TimePicker,
+ mDefStyleAttr, mDefStyleRes);
- @Override
- public void setEnabled(boolean enabled) {
- if (mIsEnabled == enabled) {
- return;
- }
- super.setEnabled(enabled);
- mMinuteSpinner.setEnabled(enabled);
- if (mDivider != null) {
- mDivider.setEnabled(enabled);
- }
- mHourSpinner.setEnabled(enabled);
- if (mAmPmSpinner != null) {
- mAmPmSpinner.setEnabled(enabled);
- } else {
- mAmPmButton.setEnabled(enabled);
- }
- mIsEnabled = enabled;
+ // Create the correct UI delegate. Default is the legacy one.
+ final boolean isLegacyMode = shouldForceLegacyMode() ?
+ true : a.getBoolean(R.styleable.TimePicker_legacyMode, true);
+ setLegacyMode(isLegacyMode);
}
- @Override
- public boolean isEnabled() {
- return mIsEnabled;
+ private boolean shouldForceLegacyMode() {
+ final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+ return targetSdkVersion < KITKAT;
}
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- setCurrentLocale(newConfig.locale);
+ private TimePickerDelegate createLegacyUIDelegate(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ return new LegacyTimePickerDelegate(this, context, attrs, defStyleAttr, defStyleRes);
}
- /**
- * Sets the current locale.
- *
- * @param locale The current locale.
- */
- private void setCurrentLocale(Locale locale) {
- if (locale.equals(mCurrentLocale)) {
- return;
- }
- mCurrentLocale = locale;
- mTempCalendar = Calendar.getInstance(locale);
+ private TimePickerDelegate createNewUIDelegate(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ return new android.widget.TimePickerDelegate(this, context, attrs, defStyleAttr,
+ defStyleRes);
}
/**
- * Used to save / restore state of time picker
+ * @hide
*/
- private static class SavedState extends BaseSavedState {
-
- private final int mHour;
-
- private final int mMinute;
-
- private SavedState(Parcelable superState, int hour, int minute) {
- super(superState);
- mHour = hour;
- mMinute = minute;
- }
-
- private SavedState(Parcel in) {
- super(in);
- mHour = in.readInt();
- mMinute = in.readInt();
- }
-
- public int getHour() {
- return mHour;
- }
-
- public int getMinute() {
- return mMinute;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeInt(mHour);
- dest.writeInt(mMinute);
- }
-
- @SuppressWarnings({"unused", "hiding"})
- public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
-
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
- }
-
- @Override
- protected Parcelable onSaveInstanceState() {
- Parcelable superState = super.onSaveInstanceState();
- return new SavedState(superState, getCurrentHour(), getCurrentMinute());
- }
-
- @Override
- protected void onRestoreInstanceState(Parcelable state) {
- SavedState ss = (SavedState) state;
- super.onRestoreInstanceState(ss.getSuperState());
- setCurrentHour(ss.getHour());
- setCurrentMinute(ss.getMinute());
+ public void setLegacyMode(boolean isLegacyMode) {
+ removeAllViewsInLayout();
+ mDelegate = isLegacyMode ?
+ createLegacyUIDelegate(mContext, mAttrs, mDefStyleAttr, mDefStyleRes) :
+ createNewUIDelegate(mContext, mAttrs, mDefStyleAttr, mDefStyleRes);
}
/**
- * Set the callback that indicates the time has been adjusted by the user.
- *
- * @param onTimeChangedListener the callback, should not be null.
+ * Set the current hour.
*/
- public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) {
- mOnTimeChangedListener = onTimeChangedListener;
+ public void setCurrentHour(Integer currentHour) {
+ mDelegate.setCurrentHour(currentHour);
}
/**
* @return The current hour in the range (0-23).
*/
public Integer getCurrentHour() {
- int currentHour = mHourSpinner.getValue();
- if (is24HourView()) {
- return currentHour;
- } else if (mIsAm) {
- return currentHour % HOURS_IN_HALF_DAY;
- } else {
- return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
- }
+ return mDelegate.getCurrentHour();
}
/**
- * Set the current hour.
+ * Set the current minute (0-59).
*/
- public void setCurrentHour(Integer currentHour) {
- setCurrentHour(currentHour, true);
+ public void setCurrentMinute(Integer currentMinute) {
+ mDelegate.setCurrentMinute(currentMinute);
}
- private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) {
- // why was Integer used in the first place?
- if (currentHour == null || currentHour == getCurrentHour()) {
- return;
- }
- if (!is24HourView()) {
- // convert [0,23] ordinal to wall clock display
- if (currentHour >= HOURS_IN_HALF_DAY) {
- mIsAm = false;
- if (currentHour > HOURS_IN_HALF_DAY) {
- currentHour = currentHour - HOURS_IN_HALF_DAY;
- }
- } else {
- mIsAm = true;
- if (currentHour == 0) {
- currentHour = HOURS_IN_HALF_DAY;
- }
- }
- updateAmPmControl();
- }
- mHourSpinner.setValue(currentHour);
- if (notifyTimeChanged) {
- onTimeChanged();
- }
+ /**
+ * @return The current minute.
+ */
+ public Integer getCurrentMinute() {
+ return mDelegate.getCurrentMinute();
}
/**
@@ -478,223 +157,174 @@ public class TimePicker extends FrameLayout {
* @param is24HourView True = 24 hour mode. False = AM/PM.
*/
public void setIs24HourView(Boolean is24HourView) {
- if (mIs24HourView == is24HourView) {
- return;
- }
- // cache the current hour since spinner range changes and BEFORE changing mIs24HourView!!
- int currentHour = getCurrentHour();
- // Order is important here.
- mIs24HourView = is24HourView;
- getHourFormatData();
- updateHourControl();
- // set value after spinner range is updated - be aware that because mIs24HourView has
- // changed then getCurrentHour() is not equal to the currentHour we cached before so
- // explicitly ask for *not* propagating any onTimeChanged()
- setCurrentHour(currentHour, false /* no onTimeChanged() */);
- updateMinuteControl();
- updateAmPmControl();
+ mDelegate.setIs24HourView(is24HourView);
}
/**
* @return true if this is in 24 hour view else false.
*/
public boolean is24HourView() {
- return mIs24HourView;
+ return mDelegate.is24HourView();
}
/**
- * @return The current minute.
+ * Set the callback that indicates the time has been adjusted by the user.
+ *
+ * @param onTimeChangedListener the callback, should not be null.
*/
- public Integer getCurrentMinute() {
- return mMinuteSpinner.getValue();
+ public void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener) {
+ mDelegate.setOnTimeChangedListener(onTimeChangedListener);
}
- /**
- * Set the current minute (0-59).
- */
- public void setCurrentMinute(Integer currentMinute) {
- if (currentMinute == getCurrentMinute()) {
+ @Override
+ public void setEnabled(boolean enabled) {
+ if (mDelegate.isEnabled() == enabled) {
return;
}
- mMinuteSpinner.setValue(currentMinute);
- onTimeChanged();
+ super.setEnabled(enabled);
+ mDelegate.setEnabled(enabled);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mDelegate.isEnabled();
}
/**
- * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":".
- *
- * See http://unicode.org/cldr/trac/browser/trunk/common/main
- *
- * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the
- * separator as the character which is just after the hour marker in the returned pattern.
+ * @hide
*/
- private void setDividerText() {
- final Locale defaultLocale = Locale.getDefault();
- final String skeleton = (mIs24HourView) ? "Hm" : "hm";
- final String bestDateTimePattern = DateFormat.getBestDateTimePattern(defaultLocale,
- skeleton);
- final String separatorText;
- int hourIndex = bestDateTimePattern.lastIndexOf('H');
- if (hourIndex == -1) {
- hourIndex = bestDateTimePattern.lastIndexOf('h');
- }
- if (hourIndex == -1) {
- // Default case
- separatorText = ":";
- } else {
- int minuteIndex = bestDateTimePattern.indexOf('m', hourIndex + 1);
- if (minuteIndex == -1) {
- separatorText = Character.toString(bestDateTimePattern.charAt(hourIndex + 1));
- } else {
- separatorText = bestDateTimePattern.substring(hourIndex + 1, minuteIndex);
- }
- }
- mDivider.setText(separatorText);
+ public void setShowDoneButton(boolean showDoneButton) {
+ mDelegate.setShowDoneButton(showDoneButton);
+ }
+
+ /**
+ * @hide
+ */
+ public void setDismissCallback(TimePickerDismissCallback callback) {
+ mDelegate.setDismissCallback(callback);
}
@Override
public int getBaseline() {
- return mHourSpinner.getBaseline();
+ return mDelegate.getBaseline();
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDelegate.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ return mDelegate.onSaveInstanceState(superState);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ BaseSavedState ss = (BaseSavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+ mDelegate.onRestoreInstanceState(ss);
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- onPopulateAccessibilityEvent(event);
- return true;
+ return mDelegate.dispatchPopulateAccessibilityEvent(event);
}
@Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
super.onPopulateAccessibilityEvent(event);
-
- int flags = DateUtils.FORMAT_SHOW_TIME;
- if (mIs24HourView) {
- flags |= DateUtils.FORMAT_24HOUR;
- } else {
- flags |= DateUtils.FORMAT_12HOUR;
- }
- mTempCalendar.set(Calendar.HOUR_OF_DAY, getCurrentHour());
- mTempCalendar.set(Calendar.MINUTE, getCurrentMinute());
- String selectedDateUtterance = DateUtils.formatDateTime(mContext,
- mTempCalendar.getTimeInMillis(), flags);
- event.getText().add(selectedDateUtterance);
+ mDelegate.onPopulateAccessibilityEvent(event);
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
- event.setClassName(TimePicker.class.getName());
+ mDelegate.onInitializeAccessibilityEvent(event);
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(TimePicker.class.getName());
+ mDelegate.onInitializeAccessibilityNodeInfo(info);
}
- private void updateHourControl() {
- if (is24HourView()) {
- // 'k' means 1-24 hour
- if (mHourFormat == 'k') {
- mHourSpinner.setMinValue(1);
- mHourSpinner.setMaxValue(24);
- } else {
- mHourSpinner.setMinValue(0);
- mHourSpinner.setMaxValue(23);
- }
- } else {
- // 'K' means 0-11 hour
- if (mHourFormat == 'K') {
- mHourSpinner.setMinValue(0);
- mHourSpinner.setMaxValue(11);
- } else {
- mHourSpinner.setMinValue(1);
- mHourSpinner.setMaxValue(12);
- }
- }
- mHourSpinner.setFormatter(mHourWithTwoDigit ? NumberPicker.getTwoDigitFormatter() : null);
- }
+ /**
+ * A delegate interface that defined the public API of the TimePicker. Allows different
+ * TimePicker implementations. This would need to be implemented by the TimePicker delegates
+ * for the real behavior.
+ */
+ interface TimePickerDelegate {
+ void setCurrentHour(Integer currentHour);
+ Integer getCurrentHour();
- private void updateMinuteControl() {
- if (is24HourView()) {
- mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
- } else {
- mMinuteSpinnerInput.setImeOptions(EditorInfo.IME_ACTION_NEXT);
- }
- }
+ void setCurrentMinute(Integer currentMinute);
+ Integer getCurrentMinute();
- private void updateAmPmControl() {
- if (is24HourView()) {
- if (mAmPmSpinner != null) {
- mAmPmSpinner.setVisibility(View.GONE);
- } else {
- mAmPmButton.setVisibility(View.GONE);
- }
- } else {
- int index = mIsAm ? Calendar.AM : Calendar.PM;
- if (mAmPmSpinner != null) {
- mAmPmSpinner.setValue(index);
- mAmPmSpinner.setVisibility(View.VISIBLE);
- } else {
- mAmPmButton.setText(mAmPmStrings[index]);
- mAmPmButton.setVisibility(View.VISIBLE);
- }
- }
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- }
+ void setIs24HourView(Boolean is24HourView);
+ boolean is24HourView();
- private void onTimeChanged() {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- if (mOnTimeChangedListener != null) {
- mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute());
- }
+ void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener);
+
+ void setEnabled(boolean enabled);
+ boolean isEnabled();
+
+ void setShowDoneButton(boolean showDoneButton);
+ void setDismissCallback(TimePickerDismissCallback callback);
+
+ int getBaseline();
+
+ void onConfigurationChanged(Configuration newConfig);
+
+ Parcelable onSaveInstanceState(Parcelable superState);
+ void onRestoreInstanceState(Parcelable state);
+
+ boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
+ void onPopulateAccessibilityEvent(AccessibilityEvent event);
+ void onInitializeAccessibilityEvent(AccessibilityEvent event);
+ void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
}
- private void setContentDescriptions() {
- // Minute
- trySetContentDescription(mMinuteSpinner, R.id.increment,
- R.string.time_picker_increment_minute_button);
- trySetContentDescription(mMinuteSpinner, R.id.decrement,
- R.string.time_picker_decrement_minute_button);
- // Hour
- trySetContentDescription(mHourSpinner, R.id.increment,
- R.string.time_picker_increment_hour_button);
- trySetContentDescription(mHourSpinner, R.id.decrement,
- R.string.time_picker_decrement_hour_button);
- // AM/PM
- if (mAmPmSpinner != null) {
- trySetContentDescription(mAmPmSpinner, R.id.increment,
- R.string.time_picker_increment_set_pm_button);
- trySetContentDescription(mAmPmSpinner, R.id.decrement,
- R.string.time_picker_decrement_set_am_button);
- }
+ /**
+ * A callback interface for dismissing the TimePicker when included into a Dialog
+ *
+ * @hide
+ */
+ public static interface TimePickerDismissCallback {
+ void dismiss(TimePicker view, boolean isCancel, int hourOfDay, int minute);
}
- private void trySetContentDescription(View root, int viewId, int contDescResId) {
- View target = root.findViewById(viewId);
- if (target != null) {
- target.setContentDescription(mContext.getString(contDescResId));
+ /**
+ * An abstract class which can be used as a start for TimePicker implementations
+ */
+ abstract static class AbstractTimePickerDelegate implements TimePickerDelegate {
+ // The delegator
+ protected TimePicker mDelegator;
+
+ // The context
+ protected Context mContext;
+
+ // The current locale
+ protected Locale mCurrentLocale;
+
+ // Callbacks
+ protected OnTimeChangedListener mOnTimeChangedListener;
+
+ public AbstractTimePickerDelegate(TimePicker delegator, Context context) {
+ mDelegator = delegator;
+ mContext = context;
+
+ // initialization based on locale
+ setCurrentLocale(Locale.getDefault());
}
- }
- private void updateInputState() {
- // Make sure that if the user changes the value and the IME is active
- // for one of the inputs if this widget, the IME is closed. If the user
- // changed the value via the IME and there is a next input the IME will
- // be shown, otherwise the user chose another means of changing the
- // value and having the IME up makes no sense.
- InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
- if (inputMethodManager != null) {
- if (inputMethodManager.isActive(mHourSpinnerInput)) {
- mHourSpinnerInput.clearFocus();
- inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
- } else if (inputMethodManager.isActive(mMinuteSpinnerInput)) {
- mMinuteSpinnerInput.clearFocus();
- inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
- } else if (inputMethodManager.isActive(mAmPmSpinnerInput)) {
- mAmPmSpinnerInput.clearFocus();
- inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+ public void setCurrentLocale(Locale locale) {
+ if (locale.equals(mCurrentLocale)) {
+ return;
}
+ mCurrentLocale = locale;
}
}
}
diff --git a/core/java/android/widget/TimePickerDelegate.java b/core/java/android/widget/TimePickerDelegate.java
new file mode 100644
index 0000000..182d370
--- /dev/null
+++ b/core/java/android/widget/TimePickerDelegate.java
@@ -0,0 +1,1380 @@
+/*
+ * Copyright (C) 2013 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.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.HapticFeedbackConstants;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.R;
+
+import java.text.DateFormatSymbols;
+import java.util.ArrayList;
+import java.util.Calendar;
+
+/**
+ * A view for selecting the time of day, in either 24 hour or AM/PM mode.
+ */
+class TimePickerDelegate extends TimePicker.AbstractTimePickerDelegate implements
+ RadialTimePickerView.OnValueSelectedListener {
+
+ private static final String TAG = "TimePickerDelegate";
+
+ // Index used by RadialPickerLayout
+ private static final int HOUR_INDEX = 0;
+ private static final int MINUTE_INDEX = 1;
+
+ // NOT a real index for the purpose of what's showing.
+ private static final int AMPM_INDEX = 2;
+
+ // Also NOT a real index, just used for keyboard mode.
+ private static final int ENABLE_PICKER_INDEX = 3;
+
+ private static final int AM = 0;
+ private static final int PM = 1;
+
+ private static final boolean DEFAULT_ENABLED_STATE = true;
+ private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
+
+ private static final int HOURS_IN_HALF_DAY = 12;
+
+ // Delay in ms before starting the pulse animation
+ private static final int PULSE_ANIMATOR_DELAY = 300;
+
+ // Duration in ms of the pulse animation
+ private static final int PULSE_ANIMATOR_DURATION = 544;
+
+ private static int[] TEXT_APPEARANCE_TIME_LABEL_ATTR =
+ new int[] { R.attr.timePickerHeaderTimeLabelTextAppearance };
+
+ private final View mMainView;
+ private TextView mHourView;
+ private TextView mMinuteView;
+ private TextView mAmPmTextView;
+ private RadialTimePickerView mRadialTimePickerView;
+ private TextView mSeparatorView;
+
+ private ViewGroup mLayoutButtons;
+
+ private int mHeaderSelectedColor;
+ private int mHeaderUnSelectedColor;
+ private String mAmText;
+ private String mPmText;
+
+ private boolean mAllowAutoAdvance;
+ private int mInitialHourOfDay;
+ private int mInitialMinute;
+ private boolean mIs24HourView;
+
+ // For hardware IME input.
+ private char mPlaceholderText;
+ private String mDoublePlaceholderText;
+ private String mDeletedKeyFormat;
+ private boolean mInKbMode;
+ private ArrayList<Integer> mTypedTimes = new ArrayList<Integer>();
+ private Node mLegalTimesTree;
+ private int mAmKeyCode;
+ private int mPmKeyCode;
+
+ // For showing the done button when in a Dialog
+ private Button mDoneButton;
+ private boolean mShowDoneButton;
+ private TimePicker.TimePickerDismissCallback mDismissCallback;
+
+ // Accessibility strings.
+ private String mHourPickerDescription;
+ private String mSelectHours;
+ private String mMinutePickerDescription;
+ private String mSelectMinutes;
+
+ public TimePickerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(delegator, context);
+
+ // process style attributes
+ final TypedArray a = mContext.obtainStyledAttributes(attrs,
+ R.styleable.TimePicker, defStyleAttr, defStyleRes);
+
+ final Resources res = mContext.getResources();
+
+ mHourPickerDescription = res.getString(R.string.hour_picker_description);
+ mSelectHours = res.getString(R.string.select_hours);
+ mMinutePickerDescription = res.getString(R.string.minute_picker_description);
+ mSelectMinutes = res.getString(R.string.select_minutes);
+
+ mHeaderSelectedColor = a.getColor(R.styleable.TimePicker_headerSelectedTextColor,
+ android.R.color.holo_blue_light);
+
+ mHeaderUnSelectedColor = getUnselectedColor(
+ R.color.timepicker_default_text_color_holo_light);
+ if (mHeaderUnSelectedColor == -1) {
+ mHeaderUnSelectedColor = a.getColor(R.styleable.TimePicker_headerUnselectedTextColor,
+ R.color.timepicker_default_text_color_holo_light);
+ }
+
+ final int headerBackgroundColor = a.getColor(
+ R.styleable.TimePicker_headerBackgroundColor, 0);
+
+ a.recycle();
+
+ final int layoutResourceId = a.getResourceId(
+ R.styleable.TimePicker_internalLayout, R.layout.time_picker_holo);
+
+ final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ mMainView = inflater.inflate(layoutResourceId, null);
+ mDelegator.addView(mMainView);
+
+ if (headerBackgroundColor != 0) {
+ RelativeLayout header = (RelativeLayout) mMainView.findViewById(R.id.time_header);
+ header.setBackgroundColor(headerBackgroundColor);
+ }
+
+ mHourView = (TextView) mMainView.findViewById(R.id.hours);
+ mMinuteView = (TextView) mMainView.findViewById(R.id.minutes);
+ mAmPmTextView = (TextView) mMainView.findViewById(R.id.ampm_label);
+ mSeparatorView = (TextView) mMainView.findViewById(R.id.separator);
+ mRadialTimePickerView = (RadialTimePickerView) mMainView.findViewById(R.id.radial_picker);
+
+ mLayoutButtons = (ViewGroup) mMainView.findViewById(R.id.layout_buttons);
+ mDoneButton = (Button) mMainView.findViewById(R.id.done_button);
+
+ String[] amPmTexts = new DateFormatSymbols().getAmPmStrings();
+ mAmText = amPmTexts[0];
+ mPmText = amPmTexts[1];
+
+ setupListeners();
+
+ mAllowAutoAdvance = true;
+
+ // Set up for keyboard mode.
+ mDoublePlaceholderText = res.getString(R.string.time_placeholder);
+ mDeletedKeyFormat = res.getString(R.string.deleted_key);
+ mPlaceholderText = mDoublePlaceholderText.charAt(0);
+ mAmKeyCode = mPmKeyCode = -1;
+ generateLegalTimesTree();
+
+ // Initialize with current time
+ final Calendar calendar = Calendar.getInstance(mCurrentLocale);
+ final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
+ final int currentMinute = calendar.get(Calendar.MINUTE);
+ initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX, false);
+ }
+
+ private int getUnselectedColor(int defColor) {
+ int result = -1;
+ final Resources.Theme theme = mContext.getTheme();
+ final TypedValue outValue = new TypedValue();
+ theme.resolveAttribute(R.attr.timePickerHeaderTimeLabelTextAppearance, outValue, true);
+ final int appearanceResId = outValue.resourceId;
+ TypedArray appearance = null;
+ if (appearanceResId != -1) {
+ appearance = theme.obtainStyledAttributes(appearanceResId,
+ com.android.internal.R.styleable.TextAppearance);
+ }
+ if (appearance != null) {
+ result = appearance.getColor(
+ com.android.internal.R.styleable.TextAppearance_textColor, defColor);
+ appearance.recycle();
+ }
+ return result;
+ }
+
+ private void initialize(int hourOfDay, int minute, boolean is24HourView, int index,
+ boolean showDoneButton) {
+ mInitialHourOfDay = hourOfDay;
+ mInitialMinute = minute;
+ mIs24HourView = is24HourView;
+ mInKbMode = false;
+ mShowDoneButton = showDoneButton;
+ updateUI(index);
+ }
+
+ private void setupListeners() {
+ KeyboardListener keyboardListener = new KeyboardListener();
+ mDelegator.setOnKeyListener(keyboardListener);
+
+ mHourView.setOnKeyListener(keyboardListener);
+ mMinuteView.setOnKeyListener(keyboardListener);
+ mAmPmTextView.setOnKeyListener(keyboardListener);
+ mRadialTimePickerView.setOnValueSelectedListener(this);
+ mRadialTimePickerView.setOnKeyListener(keyboardListener);
+
+ mHourView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setCurrentItemShowing(HOUR_INDEX, true, false, true);
+ tryVibrate();
+ }
+ });
+ mMinuteView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setCurrentItemShowing(MINUTE_INDEX, true, false, true);
+ tryVibrate();
+ }
+ });
+ mDoneButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mInKbMode && isTypedTimeFullyLegal()) {
+ finishKbMode(false);
+ } else {
+ tryVibrate();
+ }
+ if (mDismissCallback != null) {
+ mDismissCallback.dismiss(mDelegator, false, getCurrentHour(),
+ getCurrentMinute());
+ }
+ }
+ });
+ mDoneButton.setOnKeyListener(keyboardListener);
+ }
+
+ private void updateUI(int index) {
+ // Update RadialPicker values
+ updateRadialPicker(index);
+ // Enable or disable the AM/PM view.
+ updateHeaderAmPm();
+ // Show or hide Done button
+ updateDoneButton();
+ // Update Hour and Minutes
+ updateHeaderHour(mInitialHourOfDay, true);
+ // Update time separator
+ updateHeaderSeparator();
+ // Update Minutes
+ updateHeaderMinute(mInitialMinute);
+ // Invalidate everything
+ mDelegator.invalidate();
+ }
+
+ private void updateRadialPicker(int index) {
+ mRadialTimePickerView.initialize(mInitialHourOfDay, mInitialMinute, mIs24HourView);
+ setCurrentItemShowing(index, false, true, true);
+ }
+
+ private int computeMaxWidthOfNumbers(int max) {
+ TextView tempView = new TextView(mContext);
+ TypedArray a = mContext.obtainStyledAttributes(TEXT_APPEARANCE_TIME_LABEL_ATTR);
+ final int textAppearanceResId = a.getResourceId(0, 0);
+ tempView.setTextAppearance(mContext, (textAppearanceResId != 0) ?
+ textAppearanceResId : R.style.TextAppearance_Holo_TimePicker_TimeLabel);
+ a.recycle();
+ ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ tempView.setLayoutParams(lp);
+ int maxWidth = 0;
+ for (int minutes = 0; minutes < max; minutes++) {
+ final String text = String.format("%02d", minutes);
+ tempView.setText(text);
+ tempView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+ maxWidth = Math.max(maxWidth, tempView.getMeasuredWidth());
+ }
+ return maxWidth;
+ }
+
+ private void updateHeaderAmPm() {
+ if (mIs24HourView) {
+ mAmPmTextView.setVisibility(View.GONE);
+ } else {
+ mAmPmTextView.setVisibility(View.VISIBLE);
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+ "hm");
+
+ boolean amPmOnLeft = bestDateTimePattern.startsWith("a");
+ if (TextUtils.getLayoutDirectionFromLocale(mCurrentLocale) ==
+ View.LAYOUT_DIRECTION_RTL) {
+ amPmOnLeft = !amPmOnLeft;
+ }
+
+ RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams)
+ mAmPmTextView.getLayoutParams();
+
+ if (amPmOnLeft) {
+ layoutParams.rightMargin = computeMaxWidthOfNumbers(12 /* for hours */);
+ layoutParams.removeRule(RelativeLayout.RIGHT_OF);
+ layoutParams.addRule(RelativeLayout.LEFT_OF, R.id.separator);
+ } else {
+ layoutParams.leftMargin = computeMaxWidthOfNumbers(60 /* for minutes */);
+ layoutParams.removeRule(RelativeLayout.LEFT_OF);
+ layoutParams.addRule(RelativeLayout.RIGHT_OF, R.id.separator);
+ }
+
+ updateAmPmDisplay(mInitialHourOfDay < 12 ? AM : PM);
+ mAmPmTextView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ tryVibrate();
+ int amOrPm = mRadialTimePickerView.getAmOrPm();
+ if (amOrPm == AM) {
+ amOrPm = PM;
+ } else if (amOrPm == PM){
+ amOrPm = AM;
+ }
+ updateAmPmDisplay(amOrPm);
+ mRadialTimePickerView.setAmOrPm(amOrPm);
+ }
+ });
+ }
+ }
+
+ private void updateDoneButton() {
+ mLayoutButtons.setVisibility(mShowDoneButton ? View.VISIBLE : View.GONE);
+ }
+
+ /**
+ * Set the current hour.
+ */
+ @Override
+ public void setCurrentHour(Integer currentHour) {
+ if (mInitialHourOfDay == currentHour) {
+ return;
+ }
+ mInitialHourOfDay = currentHour;
+ updateHeaderHour(currentHour, true /* accessibility announce */);
+ updateHeaderAmPm();
+ mRadialTimePickerView.setCurrentHour(currentHour);
+ mRadialTimePickerView.setAmOrPm(mInitialHourOfDay < 12 ? AM : PM);
+ mDelegator.invalidate();
+ onTimeChanged();
+ }
+
+ /**
+ * @return The current hour in the range (0-23).
+ */
+ @Override
+ public Integer getCurrentHour() {
+ int currentHour = mRadialTimePickerView.getCurrentHour();
+ if (mIs24HourView) {
+ return currentHour;
+ } else {
+ switch(mRadialTimePickerView.getAmOrPm()) {
+ case PM:
+ return (currentHour % HOURS_IN_HALF_DAY) + HOURS_IN_HALF_DAY;
+ case AM:
+ default:
+ return currentHour % HOURS_IN_HALF_DAY;
+ }
+ }
+ }
+
+ /**
+ * Set the current minute (0-59).
+ */
+ @Override
+ public void setCurrentMinute(Integer currentMinute) {
+ if (mInitialMinute == currentMinute) {
+ return;
+ }
+ mInitialMinute = currentMinute;
+ updateHeaderMinute(currentMinute);
+ mRadialTimePickerView.setCurrentMinute(currentMinute);
+ mDelegator.invalidate();
+ onTimeChanged();
+ }
+
+ /**
+ * @return The current minute.
+ */
+ @Override
+ public Integer getCurrentMinute() {
+ return mRadialTimePickerView.getCurrentMinute();
+ }
+
+ /**
+ * Set whether in 24 hour or AM/PM mode.
+ *
+ * @param is24HourView True = 24 hour mode. False = AM/PM.
+ */
+ @Override
+ public void setIs24HourView(Boolean is24HourView) {
+ if (is24HourView == mIs24HourView) {
+ return;
+ }
+ mIs24HourView = is24HourView;
+ generateLegalTimesTree();
+ int hour = mRadialTimePickerView.getCurrentHour();
+ mInitialHourOfDay = hour;
+ updateHeaderHour(hour, false /* no accessibility announce */);
+ updateHeaderAmPm();
+ updateRadialPicker(mRadialTimePickerView.getCurrentItemShowing());
+ mDelegator.invalidate();
+ }
+
+ /**
+ * @return true if this is in 24 hour view else false.
+ */
+ @Override
+ public boolean is24HourView() {
+ return mIs24HourView;
+ }
+
+ @Override
+ public void setOnTimeChangedListener(TimePicker.OnTimeChangedListener callback) {
+ mOnTimeChangedListener = callback;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ mHourView.setEnabled(enabled);
+ mMinuteView.setEnabled(enabled);
+ mAmPmTextView.setEnabled(enabled);
+ mRadialTimePickerView.setEnabled(enabled);
+ mIsEnabled = enabled;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ @Override
+ public void setShowDoneButton(boolean showDoneButton) {
+ mShowDoneButton = showDoneButton;
+ updateDoneButton();
+ }
+
+ @Override
+ public void setDismissCallback(TimePicker.TimePickerDismissCallback callback) {
+ mDismissCallback = callback;
+ }
+
+ @Override
+ public int getBaseline() {
+ // does not support baseline alignment
+ return -1;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ updateUI(mRadialTimePickerView.getCurrentItemShowing());
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState(Parcelable superState) {
+ return new SavedState(superState, getCurrentHour(), getCurrentMinute(),
+ is24HourView(), inKbMode(), getTypedTimes(), getCurrentItemShowing(),
+ isShowDoneButton());
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+ setInKbMode(ss.inKbMode());
+ setTypedTimes(ss.getTypesTimes());
+ initialize(ss.getHour(), ss.getMinute(), ss.is24HourMode(), ss.getCurrentItemShowing(),
+ ss.isShowDoneButton());
+ mRadialTimePickerView.invalidate();
+ if (mInKbMode) {
+ tryStartingKbMode(-1);
+ mHourView.invalidate();
+ }
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ return mRadialTimePickerView.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ @Override
+ public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+ mRadialTimePickerView.onPopulateAccessibilityEvent(event);
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ mRadialTimePickerView.onInitializeAccessibilityEvent(event);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ mRadialTimePickerView.onInitializeAccessibilityNodeInfo(info);
+ }
+
+ /**
+ * Set whether in keyboard mode or not.
+ *
+ * @param inKbMode True means in keyboard mode.
+ */
+ private void setInKbMode(boolean inKbMode) {
+ mInKbMode = inKbMode;
+ }
+
+ /**
+ * @return true if in keyboard mode
+ */
+ private boolean inKbMode() {
+ return mInKbMode;
+ }
+
+ private void setTypedTimes(ArrayList<Integer> typeTimes) {
+ mTypedTimes = typeTimes;
+ }
+
+ /**
+ * @return an array of typed times
+ */
+ private ArrayList<Integer> getTypedTimes() {
+ return mTypedTimes;
+ }
+
+ /**
+ * @return the index of the current item showing
+ */
+ private int getCurrentItemShowing() {
+ return mRadialTimePickerView.getCurrentItemShowing();
+ }
+
+ private boolean isShowDoneButton() {
+ return mShowDoneButton;
+ }
+
+ /**
+ * Propagate the time change
+ */
+ private void onTimeChanged() {
+ mDelegator.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ if (mOnTimeChangedListener != null) {
+ mOnTimeChangedListener.onTimeChanged(mDelegator,
+ getCurrentHour(), getCurrentMinute());
+ }
+ }
+
+ /**
+ * Used to save / restore state of time picker
+ */
+ private static class SavedState extends View.BaseSavedState {
+
+ private final int mHour;
+ private final int mMinute;
+ private final boolean mIs24HourMode;
+ private final boolean mInKbMode;
+ private final ArrayList<Integer> mTypedTimes;
+ private final int mCurrentItemShowing;
+ private final boolean mShowDoneButton;
+
+ private SavedState(Parcelable superState, int hour, int minute, boolean is24HourMode,
+ boolean isKbMode, ArrayList<Integer> typedTimes,
+ int currentItemShowing, boolean showDoneButton) {
+ super(superState);
+ mHour = hour;
+ mMinute = minute;
+ mIs24HourMode = is24HourMode;
+ mInKbMode = isKbMode;
+ mTypedTimes = typedTimes;
+ mCurrentItemShowing = currentItemShowing;
+ mShowDoneButton = showDoneButton;
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ mHour = in.readInt();
+ mMinute = in.readInt();
+ mIs24HourMode = (in.readInt() == 1);
+ mInKbMode = (in.readInt() == 1);
+ mTypedTimes = in.readArrayList(getClass().getClassLoader());
+ mCurrentItemShowing = in.readInt();
+ mShowDoneButton = (in.readInt() == 1);
+ }
+
+ public int getHour() {
+ return mHour;
+ }
+
+ public int getMinute() {
+ return mMinute;
+ }
+
+ public boolean is24HourMode() {
+ return mIs24HourMode;
+ }
+
+ public boolean inKbMode() {
+ return mInKbMode;
+ }
+
+ public ArrayList<Integer> getTypesTimes() {
+ return mTypedTimes;
+ }
+
+ public int getCurrentItemShowing() {
+ return mCurrentItemShowing;
+ }
+
+ public boolean isShowDoneButton() {
+ return mShowDoneButton;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mHour);
+ dest.writeInt(mMinute);
+ dest.writeInt(mIs24HourMode ? 1 : 0);
+ dest.writeInt(mInKbMode ? 1 : 0);
+ dest.writeList(mTypedTimes);
+ dest.writeInt(mCurrentItemShowing);
+ dest.writeInt(mShowDoneButton ? 1 : 0);
+ }
+
+ @SuppressWarnings({"unused", "hiding"})
+ public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ private void tryVibrate() {
+ mDelegator.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
+ }
+
+ private void updateAmPmDisplay(int amOrPm) {
+ if (amOrPm == AM) {
+ mAmPmTextView.setText(mAmText);
+ mRadialTimePickerView.announceForAccessibility(mAmText);
+ } else if (amOrPm == PM){
+ mAmPmTextView.setText(mPmText);
+ mRadialTimePickerView.announceForAccessibility(mPmText);
+ } else {
+ mAmPmTextView.setText(mDoublePlaceholderText);
+ }
+ }
+
+ /**
+ * Called by the picker for updating the header display.
+ */
+ @Override
+ public void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance) {
+ if (pickerIndex == HOUR_INDEX) {
+ updateHeaderHour(newValue, false);
+ String announcement = String.format("%d", newValue);
+ if (mAllowAutoAdvance && autoAdvance) {
+ setCurrentItemShowing(MINUTE_INDEX, true, true, false);
+ announcement += ". " + mSelectMinutes;
+ } else {
+ mRadialTimePickerView.setContentDescription(
+ mHourPickerDescription + ": " + newValue);
+ }
+
+ mRadialTimePickerView.announceForAccessibility(announcement);
+ } else if (pickerIndex == MINUTE_INDEX){
+ updateHeaderMinute(newValue);
+ mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + newValue);
+ } else if (pickerIndex == AMPM_INDEX) {
+ updateAmPmDisplay(newValue);
+ } else if (pickerIndex == ENABLE_PICKER_INDEX) {
+ if (!isTypedTimeFullyLegal()) {
+ mTypedTimes.clear();
+ }
+ finishKbMode(true);
+ }
+ }
+
+ private void updateHeaderHour(int value, boolean announce) {
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+ (mIs24HourView) ? "Hm" : "hm");
+ final int lengthPattern = bestDateTimePattern.length();
+ boolean hourWithTwoDigit = false;
+ char hourFormat = '\0';
+ // Check if the returned pattern is single or double 'H', 'h', 'K', 'k'. We also save
+ // the hour format that we found.
+ for (int i = 0; i < lengthPattern; i++) {
+ final char c = bestDateTimePattern.charAt(i);
+ if (c == 'H' || c == 'h' || c == 'K' || c == 'k') {
+ hourFormat = c;
+ if (i + 1 < lengthPattern && c == bestDateTimePattern.charAt(i + 1)) {
+ hourWithTwoDigit = true;
+ }
+ break;
+ }
+ }
+ final String format;
+ if (hourWithTwoDigit) {
+ format = "%02d";
+ } else {
+ format = "%d";
+ }
+ if (mIs24HourView) {
+ // 'k' means 1-24 hour
+ if (hourFormat == 'k' && value == 0) {
+ value = 24;
+ }
+ } else {
+ // 'K' means 0-11 hour
+ value = modulo12(value, hourFormat == 'K');
+ }
+ CharSequence text = String.format(format, value);
+ mHourView.setText(text);
+ if (announce) {
+ mRadialTimePickerView.announceForAccessibility(text);
+ }
+ }
+
+ private static int modulo12(int n, boolean startWithZero) {
+ int value = n % 12;
+ if (value == 0 && !startWithZero) {
+ value = 12;
+ }
+ return value;
+ }
+
+ /**
+ * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":".
+ *
+ * See http://unicode.org/cldr/trac/browser/trunk/common/main
+ *
+ * We pass the correct "skeleton" depending on 12 or 24 hours view and then extract the
+ * separator as the character which is just after the hour marker in the returned pattern.
+ */
+ private void updateHeaderSeparator() {
+ final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mCurrentLocale,
+ (mIs24HourView) ? "Hm" : "hm");
+ final String separatorText;
+ // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats
+ final char[] hourFormats = {'H', 'h', 'K', 'k'};
+ int hIndex = lastIndexOfAny(bestDateTimePattern, hourFormats);
+ if (hIndex == -1) {
+ // Default case
+ separatorText = ":";
+ } else {
+ separatorText = Character.toString(bestDateTimePattern.charAt(hIndex + 1));
+ }
+ mSeparatorView.setText(separatorText);
+ }
+
+ static private int lastIndexOfAny(String str, char[] any) {
+ final int lengthAny = any.length;
+ if (lengthAny > 0) {
+ for (int i = str.length() - 1; i >= 0; i--) {
+ char c = str.charAt(i);
+ for (int j = 0; j < lengthAny; j++) {
+ if (c == any[j]) {
+ return i;
+ }
+ }
+ }
+ }
+ return -1;
+ }
+
+ private void updateHeaderMinute(int value) {
+ if (value == 60) {
+ value = 0;
+ }
+ CharSequence text = String.format(mCurrentLocale, "%02d", value);
+ mRadialTimePickerView.announceForAccessibility(text);
+ mMinuteView.setText(text);
+ }
+
+ /**
+ * Show either Hours or Minutes.
+ */
+ private void setCurrentItemShowing(int index, boolean animateCircle, boolean delayLabelAnimate,
+ boolean announce) {
+ mRadialTimePickerView.setCurrentItemShowing(index, animateCircle);
+
+ TextView labelToAnimate;
+ if (index == HOUR_INDEX) {
+ int hours = mRadialTimePickerView.getCurrentHour();
+ if (!mIs24HourView) {
+ hours = hours % 12;
+ }
+ mRadialTimePickerView.setContentDescription(mHourPickerDescription + ": " + hours);
+ if (announce) {
+ mRadialTimePickerView.announceForAccessibility(mSelectHours);
+ }
+ labelToAnimate = mHourView;
+ } else {
+ int minutes = mRadialTimePickerView.getCurrentMinute();
+ mRadialTimePickerView.setContentDescription(mMinutePickerDescription + ": " + minutes);
+ if (announce) {
+ mRadialTimePickerView.announceForAccessibility(mSelectMinutes);
+ }
+ labelToAnimate = mMinuteView;
+ }
+
+ int hourColor = (index == HOUR_INDEX) ? mHeaderSelectedColor : mHeaderUnSelectedColor;
+ int minuteColor = (index == MINUTE_INDEX) ? mHeaderSelectedColor : mHeaderUnSelectedColor;
+ mHourView.setTextColor(hourColor);
+ mMinuteView.setTextColor(minuteColor);
+
+ ObjectAnimator pulseAnimator = getPulseAnimator(labelToAnimate, 0.85f, 1.1f);
+ if (delayLabelAnimate) {
+ pulseAnimator.setStartDelay(PULSE_ANIMATOR_DELAY);
+ }
+ pulseAnimator.start();
+ }
+
+ /**
+ * For keyboard mode, processes key events.
+ *
+ * @param keyCode the pressed key.
+ *
+ * @return true if the key was successfully processed, false otherwise.
+ */
+ private boolean processKeyUp(int keyCode) {
+ if (keyCode == KeyEvent.KEYCODE_ESCAPE || keyCode == KeyEvent.KEYCODE_BACK) {
+ if (mDismissCallback != null) {
+ mDismissCallback.dismiss(mDelegator, true, getCurrentHour(), getCurrentMinute());
+ }
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_TAB) {
+ if(mInKbMode) {
+ if (isTypedTimeFullyLegal()) {
+ finishKbMode(true);
+ }
+ return true;
+ }
+ } else if (keyCode == KeyEvent.KEYCODE_ENTER) {
+ if (mInKbMode) {
+ if (!isTypedTimeFullyLegal()) {
+ return true;
+ }
+ finishKbMode(false);
+ }
+ if (mOnTimeChangedListener != null) {
+ mOnTimeChangedListener.onTimeChanged(mDelegator,
+ mRadialTimePickerView.getCurrentHour(),
+ mRadialTimePickerView.getCurrentMinute());
+ }
+ if (mDismissCallback != null) {
+ mDismissCallback.dismiss(mDelegator, false, getCurrentHour(), getCurrentMinute());
+ }
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_DEL) {
+ if (mInKbMode) {
+ if (!mTypedTimes.isEmpty()) {
+ int deleted = deleteLastTypedKey();
+ String deletedKeyStr;
+ if (deleted == getAmOrPmKeyCode(AM)) {
+ deletedKeyStr = mAmText;
+ } else if (deleted == getAmOrPmKeyCode(PM)) {
+ deletedKeyStr = mPmText;
+ } else {
+ deletedKeyStr = String.format("%d", getValFromKeyCode(deleted));
+ }
+ mRadialTimePickerView.announceForAccessibility(
+ String.format(mDeletedKeyFormat, deletedKeyStr));
+ updateDisplay(true);
+ }
+ }
+ } else if (keyCode == KeyEvent.KEYCODE_0 || keyCode == KeyEvent.KEYCODE_1
+ || keyCode == KeyEvent.KEYCODE_2 || keyCode == KeyEvent.KEYCODE_3
+ || keyCode == KeyEvent.KEYCODE_4 || keyCode == KeyEvent.KEYCODE_5
+ || keyCode == KeyEvent.KEYCODE_6 || keyCode == KeyEvent.KEYCODE_7
+ || keyCode == KeyEvent.KEYCODE_8 || keyCode == KeyEvent.KEYCODE_9
+ || (!mIs24HourView &&
+ (keyCode == getAmOrPmKeyCode(AM) || keyCode == getAmOrPmKeyCode(PM)))) {
+ if (!mInKbMode) {
+ if (mRadialTimePickerView == null) {
+ // Something's wrong, because time picker should definitely not be null.
+ Log.e(TAG, "Unable to initiate keyboard mode, TimePicker was null.");
+ return true;
+ }
+ mTypedTimes.clear();
+ tryStartingKbMode(keyCode);
+ return true;
+ }
+ // We're already in keyboard mode.
+ if (addKeyIfLegal(keyCode)) {
+ updateDisplay(false);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Try to start keyboard mode with the specified key.
+ *
+ * @param keyCode The key to use as the first press. Keyboard mode will not be started if the
+ * key is not legal to start with. Or, pass in -1 to get into keyboard mode without a starting
+ * key.
+ */
+ private void tryStartingKbMode(int keyCode) {
+ if (keyCode == -1 || addKeyIfLegal(keyCode)) {
+ mInKbMode = true;
+ mDoneButton.setEnabled(false);
+ updateDisplay(false);
+ mRadialTimePickerView.setInputEnabled(false);
+ }
+ }
+
+ private boolean addKeyIfLegal(int keyCode) {
+ // If we're in 24hour mode, we'll need to check if the input is full. If in AM/PM mode,
+ // we'll need to see if AM/PM have been typed.
+ if ((mIs24HourView && mTypedTimes.size() == 4) ||
+ (!mIs24HourView && isTypedTimeFullyLegal())) {
+ return false;
+ }
+
+ mTypedTimes.add(keyCode);
+ if (!isTypedTimeLegalSoFar()) {
+ deleteLastTypedKey();
+ return false;
+ }
+
+ int val = getValFromKeyCode(keyCode);
+ mRadialTimePickerView.announceForAccessibility(String.format("%d", val));
+ // Automatically fill in 0's if AM or PM was legally entered.
+ if (isTypedTimeFullyLegal()) {
+ if (!mIs24HourView && mTypedTimes.size() <= 3) {
+ mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
+ mTypedTimes.add(mTypedTimes.size() - 1, KeyEvent.KEYCODE_0);
+ }
+ mDoneButton.setEnabled(true);
+ }
+
+ return true;
+ }
+
+ /**
+ * Traverse the tree to see if the keys that have been typed so far are legal as is,
+ * or may become legal as more keys are typed (excluding backspace).
+ */
+ private boolean isTypedTimeLegalSoFar() {
+ Node node = mLegalTimesTree;
+ for (int keyCode : mTypedTimes) {
+ node = node.canReach(keyCode);
+ if (node == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Check if the time that has been typed so far is completely legal, as is.
+ */
+ private boolean isTypedTimeFullyLegal() {
+ if (mIs24HourView) {
+ // For 24-hour mode, the time is legal if the hours and minutes are each legal. Note:
+ // getEnteredTime() will ONLY call isTypedTimeFullyLegal() when NOT in 24hour mode.
+ int[] values = getEnteredTime(null);
+ return (values[0] >= 0 && values[1] >= 0 && values[1] < 60);
+ } else {
+ // For AM/PM mode, the time is legal if it contains an AM or PM, as those can only be
+ // legally added at specific times based on the tree's algorithm.
+ return (mTypedTimes.contains(getAmOrPmKeyCode(AM)) ||
+ mTypedTimes.contains(getAmOrPmKeyCode(PM)));
+ }
+ }
+
+ private int deleteLastTypedKey() {
+ int deleted = mTypedTimes.remove(mTypedTimes.size() - 1);
+ if (!isTypedTimeFullyLegal()) {
+ mDoneButton.setEnabled(false);
+ }
+ return deleted;
+ }
+
+ /**
+ * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time.
+ * @param updateDisplays If true, update the displays with the relevant time.
+ */
+ private void finishKbMode(boolean updateDisplays) {
+ mInKbMode = false;
+ if (!mTypedTimes.isEmpty()) {
+ int values[] = getEnteredTime(null);
+ mRadialTimePickerView.setCurrentHour(values[0]);
+ mRadialTimePickerView.setCurrentMinute(values[1]);
+ if (!mIs24HourView) {
+ mRadialTimePickerView.setAmOrPm(values[2]);
+ }
+ mTypedTimes.clear();
+ }
+ if (updateDisplays) {
+ updateDisplay(false);
+ mRadialTimePickerView.setInputEnabled(true);
+ }
+ }
+
+ /**
+ * Update the hours, minutes, and AM/PM displays with the typed times. If the typedTimes is
+ * empty, either show an empty display (filled with the placeholder text), or update from the
+ * timepicker's values.
+ *
+ * @param allowEmptyDisplay if true, then if the typedTimes is empty, use the placeholder text.
+ * Otherwise, revert to the timepicker's values.
+ */
+ private void updateDisplay(boolean allowEmptyDisplay) {
+ if (!allowEmptyDisplay && mTypedTimes.isEmpty()) {
+ int hour = mRadialTimePickerView.getCurrentHour();
+ int minute = mRadialTimePickerView.getCurrentMinute();
+ updateHeaderHour(hour, true);
+ updateHeaderMinute(minute);
+ if (!mIs24HourView) {
+ updateAmPmDisplay(hour < 12 ? AM : PM);
+ }
+ setCurrentItemShowing(mRadialTimePickerView.getCurrentItemShowing(), true, true, true);
+ mDoneButton.setEnabled(true);
+ } else {
+ boolean[] enteredZeros = {false, false};
+ int[] values = getEnteredTime(enteredZeros);
+ String hourFormat = enteredZeros[0] ? "%02d" : "%2d";
+ String minuteFormat = (enteredZeros[1]) ? "%02d" : "%2d";
+ String hourStr = (values[0] == -1) ? mDoublePlaceholderText :
+ String.format(hourFormat, values[0]).replace(' ', mPlaceholderText);
+ String minuteStr = (values[1] == -1) ? mDoublePlaceholderText :
+ String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
+ mHourView.setText(hourStr);
+ mHourView.setTextColor(mHeaderUnSelectedColor);
+ mMinuteView.setText(minuteStr);
+ mMinuteView.setTextColor(mHeaderUnSelectedColor);
+ if (!mIs24HourView) {
+ updateAmPmDisplay(values[2]);
+ }
+ }
+ }
+
+ private int getValFromKeyCode(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_0:
+ return 0;
+ case KeyEvent.KEYCODE_1:
+ return 1;
+ case KeyEvent.KEYCODE_2:
+ return 2;
+ case KeyEvent.KEYCODE_3:
+ return 3;
+ case KeyEvent.KEYCODE_4:
+ return 4;
+ case KeyEvent.KEYCODE_5:
+ return 5;
+ case KeyEvent.KEYCODE_6:
+ return 6;
+ case KeyEvent.KEYCODE_7:
+ return 7;
+ case KeyEvent.KEYCODE_8:
+ return 8;
+ case KeyEvent.KEYCODE_9:
+ return 9;
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * Get the currently-entered time, as integer values of the hours and minutes typed.
+ *
+ * @param enteredZeros A size-2 boolean array, which the caller should initialize, and which
+ * may then be used for the caller to know whether zeros had been explicitly entered as either
+ * hours of minutes. This is helpful for deciding whether to show the dashes, or actual 0's.
+ *
+ * @return A size-3 int array. The first value will be the hours, the second value will be the
+ * minutes, and the third will be either AM or PM.
+ */
+ private int[] getEnteredTime(boolean[] enteredZeros) {
+ int amOrPm = -1;
+ int startIndex = 1;
+ if (!mIs24HourView && isTypedTimeFullyLegal()) {
+ int keyCode = mTypedTimes.get(mTypedTimes.size() - 1);
+ if (keyCode == getAmOrPmKeyCode(AM)) {
+ amOrPm = AM;
+ } else if (keyCode == getAmOrPmKeyCode(PM)){
+ amOrPm = PM;
+ }
+ startIndex = 2;
+ }
+ int minute = -1;
+ int hour = -1;
+ for (int i = startIndex; i <= mTypedTimes.size(); i++) {
+ int val = getValFromKeyCode(mTypedTimes.get(mTypedTimes.size() - i));
+ if (i == startIndex) {
+ minute = val;
+ } else if (i == startIndex+1) {
+ minute += 10 * val;
+ if (enteredZeros != null && val == 0) {
+ enteredZeros[1] = true;
+ }
+ } else if (i == startIndex+2) {
+ hour = val;
+ } else if (i == startIndex+3) {
+ hour += 10 * val;
+ if (enteredZeros != null && val == 0) {
+ enteredZeros[0] = true;
+ }
+ }
+ }
+
+ int[] ret = {hour, minute, amOrPm};
+ return ret;
+ }
+
+ /**
+ * Get the keycode value for AM and PM in the current language.
+ */
+ private int getAmOrPmKeyCode(int amOrPm) {
+ // Cache the codes.
+ if (mAmKeyCode == -1 || mPmKeyCode == -1) {
+ // Find the first character in the AM/PM text that is unique.
+ KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ char amChar;
+ char pmChar;
+ for (int i = 0; i < Math.max(mAmText.length(), mPmText.length()); i++) {
+ amChar = mAmText.toLowerCase(mCurrentLocale).charAt(i);
+ pmChar = mPmText.toLowerCase(mCurrentLocale).charAt(i);
+ if (amChar != pmChar) {
+ KeyEvent[] events = kcm.getEvents(new char[]{amChar, pmChar});
+ // There should be 4 events: a down and up for both AM and PM.
+ if (events != null && events.length == 4) {
+ mAmKeyCode = events[0].getKeyCode();
+ mPmKeyCode = events[2].getKeyCode();
+ } else {
+ Log.e(TAG, "Unable to find keycodes for AM and PM.");
+ }
+ break;
+ }
+ }
+ }
+ if (amOrPm == AM) {
+ return mAmKeyCode;
+ } else if (amOrPm == PM) {
+ return mPmKeyCode;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Create a tree for deciding what keys can legally be typed.
+ */
+ private void generateLegalTimesTree() {
+ // Create a quick cache of numbers to their keycodes.
+ final int k0 = KeyEvent.KEYCODE_0;
+ final int k1 = KeyEvent.KEYCODE_1;
+ final int k2 = KeyEvent.KEYCODE_2;
+ final int k3 = KeyEvent.KEYCODE_3;
+ final int k4 = KeyEvent.KEYCODE_4;
+ final int k5 = KeyEvent.KEYCODE_5;
+ final int k6 = KeyEvent.KEYCODE_6;
+ final int k7 = KeyEvent.KEYCODE_7;
+ final int k8 = KeyEvent.KEYCODE_8;
+ final int k9 = KeyEvent.KEYCODE_9;
+
+ // The root of the tree doesn't contain any numbers.
+ mLegalTimesTree = new Node();
+ if (mIs24HourView) {
+ // We'll be re-using these nodes, so we'll save them.
+ Node minuteFirstDigit = new Node(k0, k1, k2, k3, k4, k5);
+ Node minuteSecondDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ // The first digit must be followed by the second digit.
+ minuteFirstDigit.addChild(minuteSecondDigit);
+
+ // The first digit may be 0-1.
+ Node firstDigit = new Node(k0, k1);
+ mLegalTimesTree.addChild(firstDigit);
+
+ // When the first digit is 0-1, the second digit may be 0-5.
+ Node secondDigit = new Node(k0, k1, k2, k3, k4, k5);
+ firstDigit.addChild(secondDigit);
+ // We may now be followed by the first minute digit. E.g. 00:09, 15:58.
+ secondDigit.addChild(minuteFirstDigit);
+
+ // When the first digit is 0-1, and the second digit is 0-5, the third digit may be 6-9.
+ Node thirdDigit = new Node(k6, k7, k8, k9);
+ // The time must now be finished. E.g. 0:55, 1:08.
+ secondDigit.addChild(thirdDigit);
+
+ // When the first digit is 0-1, the second digit may be 6-9.
+ secondDigit = new Node(k6, k7, k8, k9);
+ firstDigit.addChild(secondDigit);
+ // We must now be followed by the first minute digit. E.g. 06:50, 18:20.
+ secondDigit.addChild(minuteFirstDigit);
+
+ // The first digit may be 2.
+ firstDigit = new Node(k2);
+ mLegalTimesTree.addChild(firstDigit);
+
+ // When the first digit is 2, the second digit may be 0-3.
+ secondDigit = new Node(k0, k1, k2, k3);
+ firstDigit.addChild(secondDigit);
+ // We must now be followed by the first minute digit. E.g. 20:50, 23:09.
+ secondDigit.addChild(minuteFirstDigit);
+
+ // When the first digit is 2, the second digit may be 4-5.
+ secondDigit = new Node(k4, k5);
+ firstDigit.addChild(secondDigit);
+ // We must now be followd by the last minute digit. E.g. 2:40, 2:53.
+ secondDigit.addChild(minuteSecondDigit);
+
+ // The first digit may be 3-9.
+ firstDigit = new Node(k3, k4, k5, k6, k7, k8, k9);
+ mLegalTimesTree.addChild(firstDigit);
+ // We must now be followed by the first minute digit. E.g. 3:57, 8:12.
+ firstDigit.addChild(minuteFirstDigit);
+ } else {
+ // We'll need to use the AM/PM node a lot.
+ // Set up AM and PM to respond to "a" and "p".
+ Node ampm = new Node(getAmOrPmKeyCode(AM), getAmOrPmKeyCode(PM));
+
+ // The first hour digit may be 1.
+ Node firstDigit = new Node(k1);
+ mLegalTimesTree.addChild(firstDigit);
+ // We'll allow quick input of on-the-hour times. E.g. 1pm.
+ firstDigit.addChild(ampm);
+
+ // When the first digit is 1, the second digit may be 0-2.
+ Node secondDigit = new Node(k0, k1, k2);
+ firstDigit.addChild(secondDigit);
+ // Also for quick input of on-the-hour times. E.g. 10pm, 12am.
+ secondDigit.addChild(ampm);
+
+ // When the first digit is 1, and the second digit is 0-2, the third digit may be 0-5.
+ Node thirdDigit = new Node(k0, k1, k2, k3, k4, k5);
+ secondDigit.addChild(thirdDigit);
+ // The time may be finished now. E.g. 1:02pm, 1:25am.
+ thirdDigit.addChild(ampm);
+
+ // When the first digit is 1, the second digit is 0-2, and the third digit is 0-5,
+ // the fourth digit may be 0-9.
+ Node fourthDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ thirdDigit.addChild(fourthDigit);
+ // The time must be finished now. E.g. 10:49am, 12:40pm.
+ fourthDigit.addChild(ampm);
+
+ // When the first digit is 1, and the second digit is 0-2, the third digit may be 6-9.
+ thirdDigit = new Node(k6, k7, k8, k9);
+ secondDigit.addChild(thirdDigit);
+ // The time must be finished now. E.g. 1:08am, 1:26pm.
+ thirdDigit.addChild(ampm);
+
+ // When the first digit is 1, the second digit may be 3-5.
+ secondDigit = new Node(k3, k4, k5);
+ firstDigit.addChild(secondDigit);
+
+ // When the first digit is 1, and the second digit is 3-5, the third digit may be 0-9.
+ thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ secondDigit.addChild(thirdDigit);
+ // The time must be finished now. E.g. 1:39am, 1:50pm.
+ thirdDigit.addChild(ampm);
+
+ // The hour digit may be 2-9.
+ firstDigit = new Node(k2, k3, k4, k5, k6, k7, k8, k9);
+ mLegalTimesTree.addChild(firstDigit);
+ // We'll allow quick input of on-the-hour-times. E.g. 2am, 5pm.
+ firstDigit.addChild(ampm);
+
+ // When the first digit is 2-9, the second digit may be 0-5.
+ secondDigit = new Node(k0, k1, k2, k3, k4, k5);
+ firstDigit.addChild(secondDigit);
+
+ // When the first digit is 2-9, and the second digit is 0-5, the third digit may be 0-9.
+ thirdDigit = new Node(k0, k1, k2, k3, k4, k5, k6, k7, k8, k9);
+ secondDigit.addChild(thirdDigit);
+ // The time must be finished now. E.g. 2:57am, 9:30pm.
+ thirdDigit.addChild(ampm);
+ }
+ }
+
+ /**
+ * Simple node class to be used for traversal to check for legal times.
+ * mLegalKeys represents the keys that can be typed to get to the node.
+ * mChildren are the children that can be reached from this node.
+ */
+ private class Node {
+ private int[] mLegalKeys;
+ private ArrayList<Node> mChildren;
+
+ public Node(int... legalKeys) {
+ mLegalKeys = legalKeys;
+ mChildren = new ArrayList<Node>();
+ }
+
+ public void addChild(Node child) {
+ mChildren.add(child);
+ }
+
+ public boolean containsKey(int key) {
+ for (int i = 0; i < mLegalKeys.length; i++) {
+ if (mLegalKeys[i] == key) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Node canReach(int key) {
+ if (mChildren == null) {
+ return null;
+ }
+ for (Node child : mChildren) {
+ if (child.containsKey(key)) {
+ return child;
+ }
+ }
+ return null;
+ }
+ }
+
+ private class KeyboardListener implements View.OnKeyListener {
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ return processKeyUp(keyCode);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Render an animator to pulsate a view in place.
+ *
+ * @param labelToAnimate the view to pulsate.
+ * @return The animator object. Use .start() to begin.
+ */
+ private static ObjectAnimator getPulseAnimator(View labelToAnimate, float decreaseRatio,
+ float increaseRatio) {
+ final Keyframe k0 = Keyframe.ofFloat(0f, 1f);
+ final Keyframe k1 = Keyframe.ofFloat(0.275f, decreaseRatio);
+ final Keyframe k2 = Keyframe.ofFloat(0.69f, increaseRatio);
+ final Keyframe k3 = Keyframe.ofFloat(1f, 1f);
+
+ PropertyValuesHolder scaleX = PropertyValuesHolder.ofKeyframe("scaleX", k0, k1, k2, k3);
+ PropertyValuesHolder scaleY = PropertyValuesHolder.ofKeyframe("scaleY", k0, k1, k2, k3);
+ ObjectAnimator pulseAnimator =
+ ObjectAnimator.ofPropertyValuesHolder(labelToAnimate, scaleX, scaleY);
+ pulseAnimator.setDuration(PULSE_ANIMATOR_DURATION);
+
+ return pulseAnimator;
+ }
+}
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index e38dfa7..bf5e49b 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.IntDef;
import android.app.INotificationManager;
import android.app.ITransientNotification;
import android.content.Context;
@@ -29,11 +30,13 @@ import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A toast is a view containing a quick little message for the user. The toast class
* helps you create and show those.
@@ -61,6 +64,11 @@ public class Toast {
static final String TAG = "Toast";
static final boolean localLOGV = false;
+ /** @hide */
+ @IntDef({LENGTH_SHORT, LENGTH_LONG})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Duration {}
+
/**
* Show the view or text notification for a short period of time. This time
* could be user-definable. This is the default.
@@ -152,7 +160,7 @@ public class Toast {
* @see #LENGTH_SHORT
* @see #LENGTH_LONG
*/
- public void setDuration(int duration) {
+ public void setDuration(@Duration int duration) {
mDuration = duration;
}
@@ -160,6 +168,7 @@ public class Toast {
* Return the duration.
* @see #setDuration
*/
+ @Duration
public int getDuration() {
return mDuration;
}
@@ -237,7 +246,7 @@ public class Toast {
* {@link #LENGTH_LONG}
*
*/
- public static Toast makeText(Context context, CharSequence text, int duration) {
+ public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
Toast result = new Toast(context);
LayoutInflater inflate = (LayoutInflater)
@@ -263,7 +272,7 @@ public class Toast {
*
* @throws Resources.NotFoundException if the resource can't be found.
*/
- public static Toast makeText(Context context, int resId, int duration)
+ public static Toast makeText(Context context, int resId, @Duration int duration)
throws Resources.NotFoundException {
return makeText(context, context.getResources().getText(resId), duration);
}
diff --git a/core/java/android/widget/ToggleButton.java b/core/java/android/widget/ToggleButton.java
index cedc777..28519d1 100644
--- a/core/java/android/widget/ToggleButton.java
+++ b/core/java/android/widget/ToggleButton.java
@@ -16,7 +16,6 @@
package android.widget;
-
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
@@ -25,8 +24,6 @@ import android.util.AttributeSet;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
-import com.android.internal.R;
-
/**
* Displays checked/unchecked states as a button
* with a "light" indicator and by default accompanied with the text "ON" or "OFF".
@@ -46,13 +43,12 @@ public class ToggleButton extends CompoundButton {
private static final int NO_ALPHA = 0xFF;
private float mDisabledAlpha;
-
- public ToggleButton(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- TypedArray a =
- context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.ToggleButton, defStyle, 0);
+
+ public ToggleButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.ToggleButton, defStyleAttr, defStyleRes);
mTextOn = a.getText(com.android.internal.R.styleable.ToggleButton_textOn);
mTextOff = a.getText(com.android.internal.R.styleable.ToggleButton_textOff);
mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.ToggleButton_disabledAlpha, 0.5f);
@@ -60,6 +56,10 @@ public class ToggleButton extends CompoundButton {
a.recycle();
}
+ public ToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
public ToggleButton(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.buttonStyleToggle);
}
diff --git a/core/java/android/widget/TwoLineListItem.java b/core/java/android/widget/TwoLineListItem.java
index f7e5266..5606c60 100644
--- a/core/java/android/widget/TwoLineListItem.java
+++ b/core/java/android/widget/TwoLineListItem.java
@@ -56,11 +56,15 @@ public class TwoLineListItem extends RelativeLayout {
this(context, attrs, 0);
}
- public TwoLineListItem(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public TwoLineListItem(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public TwoLineListItem(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.TwoLineListItem, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.TwoLineListItem, defStyleAttr, defStyleRes);
a.recycle();
}
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index d57b739..f23c64f 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -19,7 +19,6 @@ package android.widget;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.media.AudioManager;
@@ -127,8 +126,12 @@ public class VideoView extends SurfaceView
initVideoView();
}
- public VideoView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public VideoView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public VideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
initVideoView();
}
@@ -297,11 +300,8 @@ public class VideoView extends SurfaceView
// not ready for playback just yet, will try again later
return;
}
- // Tell the music playback service to pause
- // TODO: these constants need to be published somewhere in the framework.
- Intent i = new Intent("com.android.music.musicservicecommand");
- i.putExtra("command", "pause");
- mContext.sendBroadcast(i);
+ AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
// we shouldn't clear the target state, because somebody might have
// called start() previously
diff --git a/core/java/android/widget/ZoomButton.java b/core/java/android/widget/ZoomButton.java
index af17c94..715e868 100644
--- a/core/java/android/widget/ZoomButton.java
+++ b/core/java/android/widget/ZoomButton.java
@@ -49,8 +49,12 @@ public class ZoomButton extends ImageButton implements OnLongClickListener {
this(context, attrs, 0);
}
- public ZoomButton(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public ZoomButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ZoomButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
mHandler = new Handler();
setOnLongClickListener(this);
}
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
index 50c803b..f7e9648 100644
--- a/core/java/android/widget/ZoomButtonsController.java
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -32,7 +32,6 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.ViewParent;
import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.View.OnClickListener;