summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-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/ChooseAccountActivity.java2
-rw-r--r--core/java/android/accounts/ChooseAccountTypeActivity.java2
-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.java657
-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.java36
-rw-r--r--core/java/android/annotation/Nullable.java43
-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.java286
-rw-r--r--core/java/android/app/ActivityManager.java48
-rw-r--r--core/java/android/app/ActivityManagerNative.java164
-rw-r--r--core/java/android/app/ActivityOptions.java365
-rw-r--r--core/java/android/app/ActivityThread.java54
-rw-r--r--core/java/android/app/AlertDialog.java28
-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.java4
-rw-r--r--core/java/android/app/ApplicationThreadNative.java23
-rw-r--r--core/java/android/app/ContextImpl.java8
-rw-r--r--core/java/android/app/Dialog.java22
-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/IActivityManager.java29
-rw-r--r--core/java/android/app/IApplicationThread.java9
-rw-r--r--core/java/android/app/MediaRouteButton.java11
-rw-r--r--core/java/android/app/Notification.java206
-rw-r--r--core/java/android/app/OnActivityPausedListener.java2
-rw-r--r--core/java/android/app/PendingIntent.java65
-rw-r--r--core/java/android/app/ResultInfo.java4
-rw-r--r--core/java/android/app/SearchDialog.java5
-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/StatusBarManager.java3
-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.java88
-rw-r--r--core/java/android/app/admin/DeviceAdminReceiver.java83
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java176
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl8
-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/app/maintenance/IIdleCallback.aidl53
-rw-r--r--core/java/android/app/maintenance/IIdleService.aidl34
-rw-r--r--core/java/android/app/maintenance/IdleService.java228
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java2
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java1
-rw-r--r--core/java/android/appwidget/AppWidgetProviderInfo.java2
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java3
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java9
-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.java11
-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.java175
-rw-r--r--core/java/android/content/Context.java239
-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/SyncActivityTooManyDeletes.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/ApplicationInfo.java16
-rw-r--r--core/java/android/content/pm/FeatureInfo.java1
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl4
-rw-r--r--core/java/android/content/pm/PackageInfo.java21
-rw-r--r--core/java/android/content/pm/PackageManager.java8
-rw-r--r--core/java/android/content/pm/PackageParser.java8
-rw-r--r--core/java/android/content/pm/UserInfo.java27
-rw-r--r--core/java/android/content/pm/XmlSerializerAndParser.java1
-rw-r--r--core/java/android/content/res/AssetManager.java4
-rw-r--r--core/java/android/content/res/ColorStateList.java71
-rw-r--r--core/java/android/content/res/Resources.java197
-rw-r--r--core/java/android/content/res/TypedArray.java37
-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/GeomagneticField.java2
-rw-r--r--core/java/android/hardware/ICameraService.aidl8
-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/CameraCharacteristics.java1088
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java27
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java16
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java1194
-rw-r--r--core/java/android/hardware/camera2/CaptureFailure.java2
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java1216
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java2155
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDevice.java7
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java59
-rw-r--r--core/java/android/hardware/camera2/package.html2
-rw-r--r--core/java/android/hardware/camera2/utils/CameraBinderDecorator.java90
-rw-r--r--core/java/android/hardware/display/WifiDisplayStatus.java2
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl6
-rw-r--r--core/java/android/hardware/input/InputManager.java39
-rw-r--r--core/java/android/hardware/input/TouchCalibration.aidl19
-rw-r--r--core/java/android/hardware/input/TouchCalibration.java126
-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/UsbConfiguration.java178
-rw-r--r--core/java/android/hardware/usb/UsbDevice.java94
-rw-r--r--core/java/android/hardware/usb/UsbDeviceConnection.java21
-rw-r--r--core/java/android/hardware/usb/UsbEndpoint.java1
-rw-r--r--core/java/android/hardware/usb/UsbInterface.java67
-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.java62
-rw-r--r--core/java/android/net/ConnectivityManager.java122
-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.java10
-rw-r--r--core/java/android/net/IConnectivityManager.aidl2
-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.java13
-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/NetworkStats.java83
-rw-r--r--core/java/android/net/Proxy.java44
-rw-r--r--core/java/android/net/ProxyProperties.java11
-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/VpnService.java7
-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/http/X509TrustManagerExtensions.java14
-rw-r--r--core/java/android/net/nsd/NsdManager.java2
-rw-r--r--core/java/android/nfc/INfcAdapter.aidl3
-rw-r--r--core/java/android/nfc/INfcUnlockSettings.aidl70
-rw-r--r--core/java/android/nfc/NdefRecord.java43
-rw-r--r--core/java/android/nfc/NfcActivityManager.java11
-rw-r--r--core/java/android/nfc/NfcAdapter.java63
-rw-r--r--core/java/android/nfc/NfcUnlock.java255
-rw-r--r--core/java/android/nfc/Tag.java8
-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/AsyncTask.java2
-rw-r--r--core/java/android/os/BatteryProperties.java25
-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.java1934
-rw-r--r--core/java/android/os/Broadcaster.java8
-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/Debug.java2
-rw-r--r--core/java/android/os/DropBoxManager.java3
-rw-r--r--core/java/android/os/Environment.java180
-rw-r--r--core/java/android/os/FileObserver.java3
-rw-r--r--core/java/android/os/FileUtils.java37
-rw-r--r--core/java/android/os/Handler.java5
-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/INetworkActivityListener.aidl24
-rw-r--r--core/java/android/os/INetworkManagementService.aidl22
-rw-r--r--core/java/android/os/IPowerManager.aidl6
-rw-r--r--core/java/android/os/IUserManager.aidl2
-rw-r--r--core/java/android/os/Looper.java3
-rw-r--r--core/java/android/os/Message.java61
-rw-r--r--core/java/android/os/MessageQueue.java47
-rw-r--r--core/java/android/os/NullVibrator.java2
-rw-r--r--core/java/android/os/Parcel.java25
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java4
-rw-r--r--core/java/android/os/PowerManager.java43
-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/StrictMode.java11
-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.java64
-rw-r--r--core/java/android/os/storage/IMountService.java40
-rw-r--r--core/java/android/os/storage/StorageManager.java18
-rw-r--r--core/java/android/preference/CheckBoxPreference.java15
-rw-r--r--core/java/android/preference/DialogPreference.java26
-rw-r--r--core/java/android/preference/EditTextPreference.java10
-rw-r--r--core/java/android/preference/GenericInflater.java2
-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.java67
-rw-r--r--core/java/android/preference/PreferenceActivity.java12
-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/PreferenceManager.java4
-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/CallLog.java45
-rw-r--r--core/java/android/provider/Contacts.java1
-rw-r--r--core/java/android/provider/ContactsContract.java88
-rw-r--r--core/java/android/provider/MediaStore.java20
-rw-r--r--core/java/android/provider/Settings.java198
-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.java144
-rw-r--r--core/java/android/speech/tts/TextToSpeech.java22
-rw-r--r--core/java/android/speech/tts/TextToSpeechClient.java1047
-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/SpannableStringBuilder.java24
-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/BackgroundColorSpan.java20
-rw-r--r--core/java/android/text/style/CharacterStyle.java2
-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/ForegroundColorSpan.java20
-rw-r--r--core/java/android/text/style/IconMarginSpan.java1
-rw-r--r--core/java/android/text/style/ImageSpan.java2
-rw-r--r--core/java/android/text/style/LineHeightSpan.java2
-rw-r--r--core/java/android/text/style/MaskFilterSpan.java22
-rw-r--r--core/java/android/text/style/MetricAffectingSpan.java3
-rw-r--r--core/java/android/text/style/RasterizerSpan.java22
-rw-r--r--core/java/android/text/style/RelativeSizeSpan.java30
-rw-r--r--core/java/android/text/style/ScaleXSpan.java30
-rw-r--r--core/java/android/text/style/StrikethroughSpan.java8
-rw-r--r--core/java/android/text/style/StyleSpan.java38
-rw-r--r--core/java/android/text/style/SuggestionSpan.java12
-rw-r--r--core/java/android/text/style/UnderlineSpan.java8
-rw-r--r--core/java/android/transition/Recolor.java8
-rw-r--r--core/java/android/transition/Scene.java11
-rw-r--r--core/java/android/transition/Transition.java82
-rw-r--r--core/java/android/transition/TransitionInflater.java21
-rw-r--r--core/java/android/transition/TransitionManager.java56
-rw-r--r--core/java/android/transition/TransitionSet.java12
-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.java166
-rw-r--r--core/java/android/util/LongSparseLongArray.java2
-rw-r--r--core/java/android/util/LruCache.java3
-rw-r--r--core/java/android/util/Slog.java5
-rw-r--r--core/java/android/util/SparseBooleanArray.java12
-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.java10
-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.java533
-rw-r--r--core/java/android/view/GLES20Canvas.java178
-rw-r--r--core/java/android/view/GLES20DisplayList.java511
-rw-r--r--core/java/android/view/GLES20Layer.java100
-rw-r--r--core/java/android/view/GLES20RecordingCanvas.java23
-rw-r--r--core/java/android/view/GLES20RenderLayer.java130
-rw-r--r--core/java/android/view/GLES20TextureLayer.java108
-rw-r--r--core/java/android/view/GLRenderer.java1554
-rw-r--r--core/java/android/view/HapticFeedbackConstants.java5
-rw-r--r--core/java/android/view/HardwareCanvas.java38
-rw-r--r--core/java/android/view/HardwareLayer.java347
-rw-r--r--core/java/android/view/HardwareRenderer.java1756
-rw-r--r--core/java/android/view/InputQueue.java1
-rw-r--r--core/java/android/view/KeyEvent.java167
-rw-r--r--core/java/android/view/LayoutInflater.java135
-rw-r--r--core/java/android/view/PointerIcon.java15
-rw-r--r--core/java/android/view/Surface.java10
-rw-r--r--core/java/android/view/SurfaceControl.java64
-rw-r--r--core/java/android/view/SurfaceView.java19
-rw-r--r--core/java/android/view/TextureView.java68
-rw-r--r--core/java/android/view/ThreadedRenderer.java274
-rw-r--r--core/java/android/view/VideoPlaneView.java53
-rw-r--r--core/java/android/view/View.java1463
-rw-r--r--core/java/android/view/ViewConfiguration.java39
-rw-r--r--core/java/android/view/ViewGroup.java201
-rw-r--r--core/java/android/view/ViewOverlay.java5
-rw-r--r--core/java/android/view/ViewPropertyAnimator.java54
-rw-r--r--core/java/android/view/ViewRootImpl.java365
-rw-r--r--core/java/android/view/ViewStub.java13
-rw-r--r--core/java/android/view/Window.java116
-rw-r--r--core/java/android/view/WindowInsets.java278
-rw-r--r--core/java/android/view/WindowManager.java223
-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.java331
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfoCache.java39
-rw-r--r--core/java/android/view/accessibility/AccessibilityRecord.aidl19
-rw-r--r--core/java/android/view/accessibility/CaptioningManager.java39
-rw-r--r--core/java/android/view/animation/AnimationUtils.java2
-rw-r--r--core/java/android/view/animation/BounceInterpolator.java1
-rw-r--r--core/java/android/view/animation/PathInterpolator.java203
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java10
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java139
-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/InputMethodInfo.java57
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java37
-rw-r--r--core/java/android/view/inputmethod/InputMethodSubtype.java6
-rw-r--r--core/java/android/view/inputmethod/InputMethodSubtypeArray.java278
-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/DebugFlags.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.java77
-rw-r--r--core/java/android/webkit/WebViewFactory.java2
-rw-r--r--core/java/android/widget/AbsListView.java1550
-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/ActionMenuPresenter.java (renamed from core/java/com/android/internal/view/menu/ActionMenuPresenter.java)60
-rw-r--r--core/java/android/widget/ActionMenuView.java (renamed from core/java/com/android/internal/view/menu/ActionMenuView.java)82
-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.java23
-rw-r--r--core/java/android/widget/AppSecurityPermissions.java2
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java16
-rw-r--r--core/java/android/widget/Button.java8
-rw-r--r--core/java/android/widget/CalendarView.java2692
-rw-r--r--core/java/android/widget/CheckBox.java8
-rw-r--r--core/java/android/widget/CheckedTextView.java14
-rw-r--r--core/java/android/widget/Chronometer.java15
-rw-r--r--core/java/android/widget/CompoundButton.java15
-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/EdgeEffect.java4
-rw-r--r--core/java/android/widget/EditText.java27
-rw-r--r--core/java/android/widget/Editor.java66
-rw-r--r--core/java/android/widget/ExpandableListView.java14
-rw-r--r--core/java/android/widget/FastScroller.java314
-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.java128
-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.java56
-rw-r--r--core/java/android/widget/LegacyTimePickerDelegate.java638
-rw-r--r--core/java/android/widget/LinearLayout.java59
-rw-r--r--core/java/android/widget/ListPopupWindow.java1
-rw-r--r--core/java/android/widget/ListView.java355
-rw-r--r--core/java/android/widget/MediaController.java14
-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.java83
-rw-r--r--core/java/android/widget/QuickContactBadge.java11
-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.java52
-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.java27
-rw-r--r--core/java/android/widget/SeekBar.java8
-rw-r--r--core/java/android/widget/ShareActionProvider.java2
-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/SpellChecker.java6
-rw-r--r--core/java/android/widget/Spinner.java64
-rw-r--r--core/java/android/widget/StackView.java13
-rw-r--r--core/java/android/widget/SuggestionsAdapter.java4
-rw-r--r--core/java/android/widget/Switch.java233
-rw-r--r--core/java/android/widget/TabHost.java13
-rw-r--r--core/java/android/widget/TabWidget.java25
-rw-r--r--core/java/android/widget/TextClock.java18
-rw-r--r--core/java/android/widget/TextView.java71
-rw-r--r--core/java/android/widget/TimePicker.java698
-rw-r--r--core/java/android/widget/TimePickerDelegate.java1401
-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
-rw-r--r--core/java/com/android/internal/app/ActionBarImpl.java9
-rw-r--r--core/java/com/android/internal/app/AlertController.java188
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java6
-rw-r--r--core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java1
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl32
-rw-r--r--core/java/com/android/internal/app/LocalePicker.java2
-rw-r--r--core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java1
-rw-r--r--core/java/com/android/internal/app/MediaRouteControllerDialog.java4
-rw-r--r--core/java/com/android/internal/app/PlatLogoActivity.java5
-rw-r--r--core/java/com/android/internal/app/ProcessStats.java846
-rw-r--r--core/java/com/android/internal/backup/LocalTransport.java36
-rw-r--r--core/java/com/android/internal/content/PackageMonitor.java1
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java299
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodUtils.java28
-rw-r--r--core/java/com/android/internal/net/NetworkStatsFactory.java33
-rw-r--r--core/java/com/android/internal/net/VpnConfig.java2
-rw-r--r--core/java/com/android/internal/os/BatterySipper.java100
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHelper.java807
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java2803
-rw-r--r--core/java/com/android/internal/os/WrapperInit.java1
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java2
-rw-r--r--core/java/com/android/internal/preference/YesNoPreference.java12
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl8
-rw-r--r--core/java/com/android/internal/view/ActionBarPolicy.java2
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl6
-rw-r--r--core/java/com/android/internal/view/RotationPolicy.java73
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItem.java2
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItemView.java76
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuItemView.java17
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuItemView.java18
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuPresenter.java1
-rw-r--r--core/java/com/android/internal/view/menu/MenuBuilder.java12
-rw-r--r--core/java/com/android/internal/view/menu/MenuItemImpl.java2
-rw-r--r--core/java/com/android/internal/view/menu/MenuPopupHelper.java1
-rw-r--r--core/java/com/android/internal/widget/AbsActionBarView.java20
-rw-r--r--core/java/com/android/internal/widget/ActionBarContainer.java77
-rw-r--r--core/java/com/android/internal/widget/ActionBarContextView.java19
-rw-r--r--core/java/com/android/internal/widget/ActionBarOverlayLayout.java20
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java11
-rw-r--r--core/java/com/android/internal/widget/DialogTitle.java11
-rw-r--r--core/java/com/android/internal/widget/FaceUnlockView.java2
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java30
-rw-r--r--core/java/com/android/internal/widget/LockPatternView.java10
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboard.java5
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java2
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboardView.java11
-rw-r--r--core/java/com/android/internal/widget/PointerLocationView.java11
-rw-r--r--core/java/com/android/internal/widget/RotarySelector.java1
-rw-r--r--core/java/com/android/internal/widget/ScrollingTabContainerView.java1
-rw-r--r--core/java/com/android/internal/widget/SizeAdaptiveLayout.java21
-rw-r--r--core/java/com/android/internal/widget/SubtitleView.java41
-rw-r--r--core/java/com/android/internal/widget/TextProgressBar.java9
-rw-r--r--core/java/com/android/internal/widget/WaveView.java1
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/GlowPadView.java2
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java1
504 files changed, 35177 insertions, 13466 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/ChooseAccountActivity.java b/core/java/android/accounts/ChooseAccountActivity.java
index bfbae24..242b3ea 100644
--- a/core/java/android/accounts/ChooseAccountActivity.java
+++ b/core/java/android/accounts/ChooseAccountActivity.java
@@ -100,7 +100,7 @@ public class ChooseAccountActivity extends Activity {
try {
AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
Context authContext = createPackageContext(desc.packageName, 0);
- icon = authContext.getResources().getDrawable(desc.iconId);
+ icon = authContext.getDrawable(desc.iconId);
} catch (PackageManager.NameNotFoundException e) {
// Nothing we can do much here, just log
if (Log.isLoggable(TAG, Log.WARN)) {
diff --git a/core/java/android/accounts/ChooseAccountTypeActivity.java b/core/java/android/accounts/ChooseAccountTypeActivity.java
index acc8549..a3222d8 100644
--- a/core/java/android/accounts/ChooseAccountTypeActivity.java
+++ b/core/java/android/accounts/ChooseAccountTypeActivity.java
@@ -129,7 +129,7 @@ public class ChooseAccountTypeActivity extends Activity {
Drawable icon = null;
try {
Context authContext = createPackageContext(desc.packageName, 0);
- icon = authContext.getResources().getDrawable(desc.iconId);
+ icon = authContext.getDrawable(desc.iconId);
final CharSequence sequence = authContext.getResources().getText(desc.labelId);
if (sequence != null) {
name = sequence.toString();
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 21f6eda..8fce80a 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 long mJniSetter;
+ private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
+ new HashMap<Class, HashMap<String, Long>>();
+
+ 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, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ if (propertyMap != null) {
+ Long jniSetterLong = propertyMap.get(mPropertyName);
+ if (jniSetterLong != null) {
+ mJniSetter = jniSetterLong;
+ }
+ }
+ 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, Long>();
+ sJNISetterPropertyMap.put(targetClass, propertyMap);
+ }
+ propertyMap.put(mPropertyName, mJniSetter);
+ }
+ }
+ } finally {
+ mPropertyMapLock.writeLock().unlock();
+ }
+ }
+ }
+
+ static class MultiIntValuesHolder extends PropertyValuesHolder {
+ private long mJniSetter;
+ private static final HashMap<Class, HashMap<String, Long>> sJNISetterPropertyMap =
+ new HashMap<Class, HashMap<String, Long>>();
+
+ 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, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);
+ if (propertyMap != null) {
+ Long jniSetterLong = propertyMap.get(mPropertyName);
+ if (jniSetterLong != null) {
+ mJniSetter = jniSetterLong;
+ }
+ }
+ 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, Long>();
+ 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 long nGetIntMethod(Class targetClass, String methodName);
native static private long nGetFloatMethod(Class targetClass, String methodName);
+ native static private long nGetMultipleIntMethod(Class targetClass, String methodName,
+ int numParams);
+ native static private long nGetMultipleFloatMethod(Class targetClass, String methodName,
+ int numParams);
native static private void nCallIntMethod(Object target, long methodID, int arg);
native static private void nCallFloatMethod(Object target, long methodID, float arg);
+ native static private void nCallTwoIntMethod(Object target, long methodID, int arg1, int arg2);
+ native static private void nCallFourIntMethod(Object target, long methodID, int arg1, int arg2,
+ int arg3, int arg4);
+ native static private void nCallMultipleIntMethod(Object target, long methodID, int[] args);
+ native static private void nCallTwoFloatMethod(Object target, long methodID, float arg1,
+ float arg2);
+ native static private void nCallFourFloatMethod(Object target, long methodID, float arg1,
+ float arg2, float arg3, float arg4);
+ native static private void nCallMultipleFloatMethod(Object target, long 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..3ca9eea
--- /dev/null
+++ b/core/java/android/annotation/NonNull.java
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ *
+ * @hide
+ */
+@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..43f42fa
--- /dev/null
+++ b/core/java/android/annotation/Nullable.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ *
+ * @hide
+ */
+@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 63c9fec..606d803 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -16,11 +16,18 @@
package android.app;
+import android.annotation.NonNull;
+import android.transition.Scene;
+import android.transition.Transition;
+import android.transition.TransitionManager;
import android.util.ArrayMap;
+import android.util.Pair;
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 +91,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;
@@ -763,6 +772,7 @@ public class Activity extends ContextThemeWrapper
private Thread mUiThread;
final Handler mHandler = new Handler();
+ private ActivityOptions mTransitionActivityOptions;
/** Return the intent that started this activity. */
public Intent getIntent() {
@@ -852,6 +862,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 +893,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 +1021,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());
@@ -1021,7 +1032,7 @@ public class Activity extends ContextThemeWrapper
/**
* Called after {@link #onCreate} &mdash; or after {@link #onRestart} when
* the activity had been stopped, but is now again being displayed to the
- * user. It will be followed by {@link #onResume}.
+ * user. It will be followed by {@link #onResume}.
*
* <p><em>Derived classes must call through to the super class's
* implementation of this method. If they do not, an exception will be
@@ -1347,6 +1358,7 @@ public class Activity extends ContextThemeWrapper
* @see #onSaveInstanceState
* @see #onPause
*/
+ @Nullable
public CharSequence onCreateDescription() {
return null;
}
@@ -1551,6 +1563,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 +1643,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 +1656,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 +1904,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;
@@ -1979,13 +1995,58 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Retrieve the {@link TransitionManager} responsible for default transitions in this window.
+ * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * <p>This method will return non-null after content has been initialized (e.g. by using
+ * {@link #setContentView}) if {@link Window#FEATURE_CONTENT_TRANSITIONS} has been granted.</p>
+ *
+ * @return This window's content TransitionManager or null if none is set.
+ */
+ public TransitionManager getContentTransitionManager() {
+ return getWindow().getTransitionManager();
+ }
+
+ /**
+ * Set the {@link TransitionManager} to use for default transitions in this window.
+ * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * @param tm The TransitionManager to use for scene changes.
+ */
+ public void setContentTransitionManager(TransitionManager tm) {
+ getWindow().setTransitionManager(tm);
+ }
+
+ /**
+ * Retrieve the {@link Scene} representing this window's current content.
+ * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * <p>This method will return null if the current content is not represented by a Scene.</p>
+ *
+ * @return Current Scene being shown or null
+ */
+ public Scene getContentScene() {
+ return getWindow().getContentScene();
+ }
+
+ /**
* Sets whether this activity is finished when touched outside its window's
* bounds.
*/
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 +2116,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
@@ -2528,6 +2589,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;
}
@@ -3025,6 +3087,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);
@@ -3112,6 +3175,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) {
@@ -3233,13 +3297,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,
@@ -3257,11 +3321,11 @@ 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);
+ appSearchData, globalSearch);
}
/**
@@ -3274,7 +3338,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);
}
@@ -3341,6 +3405,7 @@ public class Activity extends ContextThemeWrapper
* Convenience for calling
* {@link android.view.Window#getLayoutInflater}.
*/
+ @NonNull
public LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
@@ -3348,6 +3413,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) {
@@ -3386,16 +3452,21 @@ public class Activity extends ContextThemeWrapper
*
* @throws android.content.ActivityNotFoundException
*
- * @see #startActivity
+ * @see #startActivity
*/
public void startActivityForResult(Intent intent, int requestCode) {
- startActivityForResult(intent, requestCode, null);
+ Bundle options = null;
+ if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
+ final Pair<View, String>[] noSharedElements = null;
+ options = ActivityOptions.makeSceneTransitionAnimation(noSharedElements).toBundle();
+ }
+ startActivityForResult(intent, requestCode, options);
}
/**
* Launch an activity for which you would like a result when it finished.
* When this activity exits, your
- * onActivityResult() method will be called with the given requestCode.
+ * onActivityResult() method will be called with the given requestCode.
* Using a negative requestCode is the same as calling
* {@link #startActivity} (the activity is not launched as a sub-activity).
*
@@ -3408,9 +3479,9 @@ public class Activity extends ContextThemeWrapper
*
* <p>As a special case, if you call startActivityForResult() with a requestCode
* >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
- * activity, then your window will not be displayed until a result is
- * returned back from the started activity. This is to avoid visible
- * flickering when redirecting to another activity.
+ * activity, then your window will not be displayed until a result is
+ * returned back from the started activity. This is to avoid visible
+ * flickering when redirecting to another activity.
*
* <p>This method throws {@link android.content.ActivityNotFoundException}
* if there was no Activity found to run the given Intent.
@@ -3424,9 +3495,20 @@ public class Activity extends ContextThemeWrapper
*
* @throws android.content.ActivityNotFoundException
*
- * @see #startActivity
+ * @see #startActivity
*/
- public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
+ public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
+ if (options != null) {
+ ActivityOptions activityOptions = new ActivityOptions(options);
+ if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+ if (mActionBar != null) {
+ ArrayMap<String, View> sharedElementMap = new ArrayMap<String, View>();
+ mActionBar.captureSharedElements(sharedElementMap);
+ activityOptions.addSharedElements(sharedElementMap);
+ }
+ options = mWindow.startExitTransition(activityOptions);
+ }
+ }
if (mParent == null) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
@@ -3505,7 +3587,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);
@@ -3537,7 +3619,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,
@@ -3599,7 +3681,7 @@ public class Activity extends ContextThemeWrapper
*/
@Override
public void startActivity(Intent intent) {
- startActivity(intent, null);
+ this.startActivity(intent, null);
}
/**
@@ -3625,7 +3707,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 {
@@ -3674,7 +3756,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);
}
@@ -3693,7 +3775,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);
@@ -3720,7 +3802,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,
@@ -3748,7 +3830,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);
}
@@ -3782,7 +3864,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 {
@@ -3831,7 +3914,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);
}
@@ -3854,7 +3937,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();
@@ -3884,7 +3967,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);
}
@@ -3908,8 +3991,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,
@@ -3934,7 +4017,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);
}
@@ -3959,8 +4042,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,
@@ -3992,7 +4075,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);
@@ -4091,6 +4174,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);
@@ -4113,6 +4197,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);
@@ -4305,7 +4390,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);
@@ -4366,8 +4451,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();
@@ -4394,7 +4479,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(
@@ -4416,6 +4501,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 {
@@ -4487,6 +4573,7 @@ public class Activity extends ContextThemeWrapper
*
* @return The local class name.
*/
+ @NonNull
public String getLocalClassName() {
final String pkg = getPackageName();
final String cls = mComponent.getClassName();
@@ -4532,9 +4619,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()");
@@ -4574,6 +4661,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);
@@ -4639,7 +4737,8 @@ public class Activity extends ContextThemeWrapper
*/
public final void setProgressBarIndeterminate(boolean indeterminate) {
getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
- indeterminate ? Window.PROGRESS_INDETERMINATE_ON : Window.PROGRESS_INDETERMINATE_OFF);
+ indeterminate ? Window.PROGRESS_INDETERMINATE_ON
+ : Window.PROGRESS_INDETERMINATE_OFF);
}
/**
@@ -4696,7 +4795,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.
@@ -4732,6 +4831,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;
}
@@ -4990,6 +5090,7 @@ public class Activity extends ContextThemeWrapper
*
* @see ActionMode
*/
+ @Nullable
public ActionMode startActionMode(ActionMode.Callback callback) {
return mWindow.getDecorView().startActionMode(callback);
}
@@ -5005,6 +5106,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();
@@ -5148,6 +5250,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)) {
@@ -5190,6 +5293,16 @@ public class Activity extends ContextThemeWrapper
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config) {
+ attach(context, aThread, instr, token, ident, application, intent, info, title, parent, id,
+ lastNonConfigurationInstances, config, null);
+ }
+
+ final void attach(Context context, ActivityThread aThread,
+ Instrumentation instr, IBinder token, int ident,
+ Application application, Intent intent, ActivityInfo info,
+ CharSequence title, Activity parent, String id,
+ NonConfigurationInstances lastNonConfigurationInstances,
+ Configuration config, Bundle options) {
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
@@ -5204,7 +5317,7 @@ public class Activity extends ContextThemeWrapper
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
-
+
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
@@ -5227,6 +5340,43 @@ public class Activity extends ContextThemeWrapper
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
+ mTransitionActivityOptions = null;
+ Window.SceneTransitionListener sceneTransitionListener = null;
+ if (options != null) {
+ ActivityOptions activityOptions = new ActivityOptions(options);
+ if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+ mTransitionActivityOptions = activityOptions;
+ sceneTransitionListener = new Window.SceneTransitionListener() {
+ @Override
+ public void nullPendingTransition() {
+ overridePendingTransition(0, 0);
+ }
+
+ @Override
+ public void convertFromTranslucent() {
+ Activity.this.convertFromTranslucent();
+ }
+
+ @Override
+ public void convertToTranslucent() {
+ Activity.this.convertToTranslucent(null);
+ }
+
+ @Override
+ public void sharedElementStart(Transition transition) {
+ Activity.this.onCaptureSharedElementStart(transition);
+ }
+
+ @Override
+ public void sharedElementEnd() {
+ Activity.this.onCaptureSharedElementEnd();
+ }
+ };
+
+ }
+ }
+
+ mWindow.setTransitionOptions(mTransitionActivityOptions, sceneTransitionListener);
}
/** @hide */
@@ -5240,7 +5390,7 @@ public class Activity extends ContextThemeWrapper
com.android.internal.R.styleable.Window_windowNoDisplay, false);
mFragments.dispatchActivityCreated();
}
-
+
final void performStart() {
mFragments.noteStateNotSaved();
mCalled = false;
@@ -5397,7 +5547,7 @@ public class Activity extends ContextThemeWrapper
}
}
}
-
+
mStopped = true;
}
mResumed = false;
@@ -5412,7 +5562,27 @@ public class Activity extends ContextThemeWrapper
mLoaderManager.doDestroy();
}
}
-
+
+ /**
+ * Called when setting up Activity Scene transitions when the start state for shared
+ * elements has been captured. Override this method to modify the start position of shared
+ * elements for the entry Transition.
+ *
+ * @param transition The <code>Transition</code> being used to change
+ * bounds of shared elements in the source Activity to
+ * the bounds defined by the entering Scene.
+ */
+ public void onCaptureSharedElementStart(Transition transition) {
+ }
+
+ /**
+ * Called when setting up Activity Scene transitions when the final state for
+ * shared elements state has been captured. Override this method to modify the destination
+ * position of shared elements for the entry Transition.
+ */
+ public void onCaptureSharedElementEnd() {
+ }
+
/**
* @hide
*/
@@ -5420,7 +5590,7 @@ public class Activity extends ContextThemeWrapper
return mResumed;
}
- void dispatchActivityResult(String who, int requestCode,
+ void dispatchActivityResult(String who, int requestCode,
int resultCode, Intent data) {
if (false) Log.v(
TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
@@ -5436,6 +5606,22 @@ public class Activity extends ContextThemeWrapper
}
}
+ /** @hide */
+ public void startLockTask() {
+ try {
+ ActivityManagerNative.getDefault().startLockTaskMode(mToken);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /** @hide */
+ public void stopLockTask() {
+ try {
+ ActivityManagerNative.getDefault().stopLockTaskMode();
+ } catch (RemoteException e) {
+ }
+ }
+
/**
* Interface for informing a translucent {@link Activity} once all visible activities below it
* have completed drawing. This is necessary only after an {@link Activity} has been made
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index c877cd3..a2183e6 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -155,6 +155,13 @@ public class ActivityManager {
public static final int START_SWITCHES_CANCELED = 4;
/**
+ * Result for IActivityManaqer.startActivity: a new activity was attempted to be started
+ * while in Lock Task Mode.
+ * @hide
+ */
+ public static final int START_RETURN_LOCK_TASK_MODE_VIOLATION = 5;
+
+ /**
* Flag for IActivityManaqer.startActivity: do special start mode where
* a new activity is launched only if it is needed.
* @hide
@@ -933,6 +940,16 @@ public class ActivityManager {
}
}
+ /** @hide */
+ public boolean isInHomeStack(int taskId) {
+ try {
+ return ActivityManagerNative.getDefault().isInHomeStack(taskId);
+ } catch (RemoteException e) {
+ // System dead, we will be dead too soon!
+ return false;
+ }
+ }
+
/**
* Flag for {@link #moveTaskToFront(int, int)}: also move the "home"
* activity along with the task, so it is positioned immediately behind
@@ -2222,4 +2239,35 @@ public class ActivityManager {
e.printStackTrace(pw);
}
}
+
+ /**
+ * @hide
+ */
+ public void startLockTaskMode(int taskId) {
+ try {
+ ActivityManagerNative.getDefault().startLockTaskMode(taskId);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void stopLockTaskMode() {
+ try {
+ ActivityManagerNative.getDefault().stopLockTaskMode();
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isInLockTaskMode() {
+ try {
+ return ActivityManagerNative.getDefault().isInLockTaskMode();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 7695ecc..373a8a3 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -101,9 +101,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
}
}
- static public void noteWakeupAlarm(PendingIntent ps) {
+ static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg) {
try {
- getDefault().noteWakeupAlarm(ps.getTarget());
+ getDefault().noteWakeupAlarm(ps.getTarget(), sourceUid, sourcePkg);
} catch (RemoteException ex) {
}
}
@@ -654,6 +654,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case IS_IN_HOME_STACK_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int taskId = data.readInt();
+ boolean isInHomeStack = isInHomeStack(taskId);
+ reply.writeNoException();
+ reply.writeInt(isInHomeStack ? 1 : 0);
+ return true;
+ }
+
case SET_FOCUSED_STACK_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int stackId = data.readInt();
@@ -1258,7 +1267,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
IIntentSender is = IIntentSender.Stub.asInterface(
data.readStrongBinder());
- noteWakeupAlarm(is);
+ int sourceUid = data.readInt();
+ String sourcePkg = data.readString();
+ noteWakeupAlarm(is, sourceUid, sourcePkg);
reply.writeNoException();
return true;
}
@@ -1711,6 +1722,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case START_USER_IN_BACKGROUND_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int userid = data.readInt();
+ boolean result = startUserInBackground(userid);
+ reply.writeNoException();
+ reply.writeInt(result ? 1 : 0);
+ return true;
+ }
+
case STOP_USER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int userid = data.readInt();
@@ -1841,6 +1861,17 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case GET_TAG_FOR_INTENT_SENDER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IIntentSender r = IIntentSender.Stub.asInterface(
+ data.readStrongBinder());
+ String prefix = data.readString();
+ String tag = getTagForIntentSender(r, prefix);
+ reply.writeNoException();
+ reply.writeString(tag);
+ return true;
+ }
+
case UPDATE_PERSISTENT_CONFIGURATION_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
Configuration config = Configuration.CREATOR.createFromParcel(data);
@@ -2066,6 +2097,37 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeStrongBinder(homeActivityToken);
return true;
}
+
+ case START_LOCK_TASK_BY_TASK_ID_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final int taskId = data.readInt();
+ startLockTaskMode(taskId);
+ reply.writeNoException();
+ return true;
+ }
+
+ case START_LOCK_TASK_BY_TOKEN_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ startLockTaskMode(token);
+ reply.writeNoException();
+ return true;
+ }
+
+ case STOP_LOCK_TASK_MODE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ stopLockTaskMode();
+ reply.writeNoException();
+ return true;
+ }
+
+ case IS_IN_LOCK_TASK_MODE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final boolean isInLockTaskMode = isInLockTaskMode();
+ reply.writeNoException();
+ reply.writeInt(isInLockTaskMode ? 1 : 0);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -2811,6 +2873,19 @@ class ActivityManagerProxy implements IActivityManager
return info;
}
@Override
+ public boolean isInHomeStack(int taskId) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(taskId);
+ mRemote.transact(IS_IN_HOME_STACK_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean isInHomeStack = reply.readInt() > 0;
+ data.recycle();
+ reply.recycle();
+ return isInHomeStack;
+ }
+ @Override
public void setFocusedStack(int stackId) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -3660,10 +3735,13 @@ class ActivityManagerProxy implements IActivityManager
mRemote.transact(ENTER_SAFE_MODE_TRANSACTION, data, null, 0);
data.recycle();
}
- public void noteWakeupAlarm(IIntentSender sender) throws RemoteException {
+ public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg)
+ throws RemoteException {
Parcel data = Parcel.obtain();
- data.writeStrongBinder(sender.asBinder());
data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(sender.asBinder());
+ data.writeInt(sourceUid);
+ data.writeString(sourcePkg);
mRemote.transact(NOTE_WAKEUP_ALARM_TRANSACTION, data, null, 0);
data.recycle();
}
@@ -4288,6 +4366,19 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
+ public boolean startUserInBackground(int userid) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(userid);
+ mRemote.transact(START_USER_IN_BACKGROUND_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean result = reply.readInt() != 0;
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
+
public int stopUser(int userid, IStopUserCallback callback) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -4430,6 +4521,21 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
+ public String getTagForIntentSender(IIntentSender sender, String prefix)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(sender.asBinder());
+ data.writeString(prefix);
+ mRemote.transact(GET_TAG_FOR_INTENT_SENDER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ String res = reply.readString();
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+
public void updatePersistentConfiguration(Configuration values) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -4745,5 +4851,53 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
+ @Override
+ public void startLockTaskMode(int taskId) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(taskId);
+ mRemote.transact(START_LOCK_TASK_BY_TASK_ID_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
+ public void startLockTaskMode(IBinder token) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ mRemote.transact(START_LOCK_TASK_BY_TOKEN_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
+ public void stopLockTaskMode() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(STOP_LOCK_TASK_MODE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
+ public boolean isInLockTaskMode() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(IS_IN_LOCK_TASK_MODE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean isInLockTaskMode = reply.readInt() == 1;
+ data.recycle();
+ reply.recycle();
+ return isInLockTaskMode;
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 87b1e24..07247ff 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -22,14 +22,22 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.RemoteException;
+import android.transition.Transition;
+import android.util.Log;
+import android.util.Pair;
import android.view.View;
+import java.util.ArrayList;
+import java.util.Map;
+
/**
* Helper class for building an options Bundle that can be used with
* {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
* Context.startActivity(Intent, Bundle)} and related methods.
*/
public class ActivityOptions {
+ private static final String TAG = "ActivityOptions";
+
/**
* The package name that created the options.
* @hide
@@ -90,6 +98,31 @@ public class ActivityOptions {
*/
public static final String KEY_ANIM_START_LISTENER = "android:animStartListener";
+ /**
+ * For Activity transitions, the calling Activity's TransitionListener used to
+ * notify the called Activity when the shared element and the exit transitions
+ * complete.
+ */
+ private static final String KEY_TRANSITION_COMPLETE_LISTENER
+ = "android:transitionCompleteListener";
+
+ /**
+ * For Activity transitions, the called Activity's listener to receive calls
+ * when transitions complete.
+ */
+ private static final String KEY_TRANSITION_TARGET_LISTENER = "android:transitionTargetListener";
+
+ /**
+ * The names of shared elements that are transitioned to the started Activity.
+ * This is also the name of shared elements that the started Activity accepted.
+ */
+ private static final String KEY_SHARED_ELEMENT_NAMES = "android:shared_element_names";
+
+ /**
+ * The shared elements names of the views in the calling Activity.
+ */
+ private static final String KEY_LOCAL_ELEMENT_NAMES = "android:local_element_names";
+
/** @hide */
public static final int ANIM_NONE = 0;
/** @hide */
@@ -100,6 +133,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;
@@ -111,6 +146,9 @@ public class ActivityOptions {
private int mStartWidth;
private int mStartHeight;
private IRemoteCallback mAnimationStartedListener;
+ private IRemoteCallback mTransitionCompleteListener;
+ private ArrayList<String> mSharedElementNames;
+ private ArrayList<String> mLocalElementNames;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -156,11 +194,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;
@@ -185,6 +224,12 @@ public class ActivityOptions {
void onAnimationStarted();
}
+ /** @hide */
+ public interface ActivityTransitionTarget {
+ void sharedElementTransitionComplete(Bundle transitionArgs);
+ void exitTransitionComplete();
+ }
+
/**
* Create an ActivityOptions specifying an animation where the new
* activity is scaled from a small originating area of the screen to
@@ -298,7 +343,56 @@ 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 to transition between Activities using cross-Activity scene
+ * animations. This method carries the position of one shared element to the started Activity.
+ *
+ * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be
+ * enabled on the calling Activity to cause an exit transition. The same must be in
+ * the called Activity to get an entering transition.</p>
+ * @param sharedElement The View to transition to the started Activity. sharedElement must
+ * have a non-null sharedElementName.
+ * @param sharedElementName The shared element name as used in the target Activity. This may
+ * be null if it has the same name as sharedElement.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ */
+ public static ActivityOptions makeSceneTransitionAnimation(View sharedElement,
+ String sharedElementName) {
+ return makeSceneTransitionAnimation(
+ new Pair<View, String>(sharedElement, sharedElementName));
+ }
+
+ /**
+ * Create an ActivityOptions to transition between Activities using cross-Activity scene
+ * animations. This method carries the position of multiple shared elements to the started
+ * Activity.
+ *
+ * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be
+ * enabled on the calling Activity to cause an exit transition. The same must be in
+ * the called Activity to get an entering transition.</p>
+ * @param sharedElements The View to transition to the started Activity along with the
+ * shared element name as used in the started Activity. The view
+ * must have a non-null sharedElementName.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ */
+ public static ActivityOptions makeSceneTransitionAnimation(
+ Pair<View, String>... sharedElements) {
+ ActivityOptions opts = new ActivityOptions();
+ opts.mAnimationType = ANIM_SCENE_TRANSITION;
+ opts.mSharedElementNames = new ArrayList<String>();
+ opts.mLocalElementNames = new ArrayList<String>();
+
+ if (sharedElements != null) {
+ for (Pair<View, String> sharedElement : sharedElements) {
+ opts.addSharedElement(sharedElement.first, sharedElement.second);
+ }
+ }
return opts;
}
@@ -309,23 +403,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:
+ mTransitionCompleteListener = IRemoteCallback.Stub.asInterface(
+ opts.getBinder(KEY_TRANSITION_COMPLETE_LISTENER));
+ mSharedElementNames = opts.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
+ mLocalElementNames = opts.getStringArrayList(KEY_LOCAL_ELEMENT_NAMES);
+ break;
}
}
@@ -380,6 +487,54 @@ public class ActivityOptions {
}
/** @hide */
+ public ArrayList<String> getSharedElementNames() { return mSharedElementNames; }
+
+ /** @hide */
+ public ArrayList<String> getLocalElementNames() { return mLocalElementNames; }
+
+ /** @hide */
+ public void dispatchSceneTransitionStarted(final ActivityTransitionTarget target,
+ ArrayList<String> sharedElementNames) {
+ boolean listenerSent = false;
+ if (mTransitionCompleteListener != null) {
+ IRemoteCallback callback = new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ if (data == null) {
+ target.exitTransitionComplete();
+ } else {
+ target.sharedElementTransitionComplete(data);
+ }
+ }
+ };
+ Bundle bundle = new Bundle();
+ bundle.putBinder(KEY_TRANSITION_TARGET_LISTENER, callback.asBinder());
+ bundle.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, sharedElementNames);
+ try {
+ mTransitionCompleteListener.sendResult(bundle);
+ listenerSent = true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Couldn't retrieve transition notifications", e);
+ }
+ }
+ if (!listenerSent) {
+ target.sharedElementTransitionComplete(null);
+ target.exitTransitionComplete();
+ }
+ }
+
+ /** @hide */
+ public void dispatchSharedElementsReady() {
+ if (mTransitionCompleteListener != null) {
+ try {
+ mTransitionCompleteListener.sendResult(null);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Couldn't synchronize shared elements", e);
+ }
+ }
+ }
+
+ /** @hide */
public void abort() {
if (mAnimationStartedListener != null) {
try {
@@ -405,19 +560,22 @@ public class ActivityOptions {
if (otherOptions.mPackageName != null) {
mPackageName = otherOptions.mPackageName;
}
+ mSharedElementNames = null;
+ mLocalElementNames = null;
switch (otherOptions.mAnimationType) {
case ANIM_CUSTOM:
mAnimationType = otherOptions.mAnimationType;
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;
+ mTransitionCompleteListener = null;
break;
case ANIM_SCALE_UP:
mAnimationType = otherOptions.mAnimationType;
@@ -425,13 +583,14 @@ 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;
+ mTransitionCompleteListener = null;
break;
case ANIM_THUMBNAIL_SCALE_UP:
case ANIM_THUMBNAIL_SCALE_DOWN:
@@ -439,13 +598,22 @@ 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;
+ mTransitionCompleteListener = null;
+ break;
+ case ANIM_SCENE_TRANSITION:
+ mAnimationType = otherOptions.mAnimationType;
+ mTransitionCompleteListener = otherOptions.mTransitionCompleteListener;
+ mThumbnail = null;
+ mAnimationStartedListener = null;
+ mSharedElementNames = otherOptions.mSharedElementNames;
+ mLocalElementNames = otherOptions.mLocalElementNames;
break;
}
}
@@ -468,7 +636,7 @@ public class ActivityOptions {
b.putInt(KEY_ANIM_TYPE, mAnimationType);
b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
- b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
+ b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
!= null ? mAnimationStartedListener.asBinder() : null);
break;
case ANIM_SCALE_UP:
@@ -484,10 +652,151 @@ public class ActivityOptions {
b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail);
b.putInt(KEY_ANIM_START_X, mStartX);
b.putInt(KEY_ANIM_START_Y, mStartY);
- b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
+ b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
!= null ? mAnimationStartedListener.asBinder() : null);
break;
+ case ANIM_SCENE_TRANSITION:
+ b.putInt(KEY_ANIM_TYPE, mAnimationType);
+ if (mTransitionCompleteListener != null) {
+ b.putBinder(KEY_TRANSITION_COMPLETE_LISTENER,
+ mTransitionCompleteListener.asBinder());
+ }
+ b.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, mSharedElementNames);
+ b.putStringArrayList(KEY_LOCAL_ELEMENT_NAMES, mLocalElementNames);
+ break;
}
return b;
}
+
+ /**
+ * Return the filtered options only meant to be seen by the target activity itself
+ * @hide
+ */
+ public ActivityOptions forTargetActivity() {
+ if (mAnimationType == ANIM_SCENE_TRANSITION) {
+ final ActivityOptions result = new ActivityOptions();
+ result.update(this);
+ return result;
+ }
+
+ return null;
+ }
+
+ /** @hide */
+ public void addSharedElements(Map<String, View> sharedElements) {
+ for (Map.Entry<String, View> entry : sharedElements.entrySet()) {
+ addSharedElement(entry.getValue(), entry.getKey());
+ }
+ }
+
+ /** @hide */
+ public void updateSceneTransitionAnimation(Transition exitTransition,
+ Transition sharedElementTransition, SharedElementSource sharedElementSource) {
+ mTransitionCompleteListener = new ExitTransitionListener(exitTransition,
+ sharedElementTransition, sharedElementSource);
+ }
+
+ private void addSharedElement(View view, String name) {
+ String sharedElementName = view.getSharedElementName();
+ if (name == null) {
+ name = sharedElementName;
+ }
+ mSharedElementNames.add(name);
+ mLocalElementNames.add(sharedElementName);
+ }
+
+ /** @hide */
+ public interface SharedElementSource {
+ Bundle getSharedElementExitState();
+ void acceptedSharedElements(ArrayList<String> sharedElementNames);
+ void hideSharedElements();
+ }
+
+ private static class ExitTransitionListener extends IRemoteCallback.Stub
+ implements Transition.TransitionListener {
+ private boolean mSharedElementNotified;
+ private Transition mExitTransition;
+ private Transition mSharedElementTransition;
+ private IRemoteCallback mTransitionCompleteCallback;
+ private boolean mExitComplete;
+ private boolean mSharedElementComplete;
+ private SharedElementSource mSharedElementSource;
+
+ public ExitTransitionListener(Transition exitTransition, Transition sharedElementTransition,
+ SharedElementSource sharedElementSource) {
+ mSharedElementSource = sharedElementSource;
+ mExitTransition = exitTransition;
+ mExitTransition.addListener(this);
+ mSharedElementTransition = sharedElementTransition;
+ mSharedElementTransition.addListener(this);
+ }
+
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ if (data != null) {
+ mTransitionCompleteCallback = IRemoteCallback.Stub.asInterface(
+ data.getBinder(KEY_TRANSITION_TARGET_LISTENER));
+ ArrayList<String> sharedElementNames
+ = data.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
+ mSharedElementSource.acceptedSharedElements(sharedElementNames);
+ notifySharedElement();
+ notifyExit();
+ } else {
+ mSharedElementSource.hideSharedElements();
+ }
+ }
+
+ @Override
+ public void onTransitionStart(Transition transition) {
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ if (transition == mExitTransition) {
+ mExitComplete = true;
+ notifyExit();
+ mExitTransition.removeListener(this);
+ } else {
+ mSharedElementComplete = true;
+ notifySharedElement();
+ mSharedElementTransition.removeListener(this);
+ }
+ }
+
+ @Override
+ public void onTransitionCancel(Transition transition) {
+ onTransitionEnd(transition);
+ }
+
+ @Override
+ public void onTransitionPause(Transition transition) {
+ }
+
+ @Override
+ public void onTransitionResume(Transition transition) {
+ }
+
+ private void notifySharedElement() {
+ if (!mSharedElementNotified && mSharedElementComplete
+ && mTransitionCompleteCallback != null) {
+ mSharedElementNotified = true;
+ try {
+ Bundle sharedElementState = mSharedElementSource.getSharedElementExitState();
+ mTransitionCompleteCallback.sendResult(sharedElementState);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Couldn't notify that the transition ended", e);
+ }
+ }
+ }
+
+ private void notifyExit() {
+ if (mExitComplete && mTransitionCompleteCallback != null) {
+ try {
+ mTransitionCompleteCallback.sendResult(null);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Couldn't notify that the transition ended", e);
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7f8dbba..69ada6a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -56,6 +56,7 @@ import android.os.DropBoxManager;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
@@ -68,6 +69,8 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.transition.Scene;
+import android.transition.TransitionManager;
import android.provider.Settings;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
@@ -75,6 +78,7 @@ import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.LogPrinter;
+import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SuperNotCalledException;
@@ -289,6 +293,7 @@ public final class ActivityThread {
boolean isForward;
int pendingConfigChanges;
boolean onlyLocalRequest;
+ Bundle activityOptions;
View mPendingRemoveWindow;
WindowManager mPendingRemoveWindowManager;
@@ -581,9 +586,10 @@ public final class ActivityThread {
}
public final void scheduleResumeActivity(IBinder token, int processState,
- boolean isForward) {
+ boolean isForward, Bundle resumeArgs) {
updateProcessState(processState, false);
- sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
+ sendMessage(H.RESUME_ACTIVITY, new Pair<IBinder, Bundle>(token, resumeArgs),
+ isForward ? 1 : 0);
}
public final void scheduleSendResult(IBinder token, List<ResultInfo> results) {
@@ -599,7 +605,8 @@ public final class ActivityThread {
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
int procState, Bundle state, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
- String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) {
+ String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
+ Bundle resumeArgs) {
updateProcessState(procState, false);
@@ -621,6 +628,7 @@ public final class ActivityThread {
r.profileFile = profileName;
r.profileFd = profileFd;
r.autoStopProfiler = autoStopProfiler;
+ r.activityOptions = resumeArgs;
updatePendingConfiguration(curConfig);
@@ -1244,7 +1252,7 @@ public final class ActivityThread {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
- ActivityClientRecord r = (ActivityClientRecord)msg.obj;
+ final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
@@ -1290,7 +1298,8 @@ public final class ActivityThread {
break;
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
- handleResumeActivity((IBinder)msg.obj, true,
+ final Pair<IBinder, Bundle> resumeArgs = (Pair<IBinder, Bundle>) msg.obj;
+ handleResumeActivity(resumeArgs.first, resumeArgs.second, true,
msg.arg1 != 0, true);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
@@ -2076,7 +2085,7 @@ public final class ActivityThread {
+ ", comp=" + name
+ ", token=" + token);
}
- return performLaunchActivity(r, null);
+ return performLaunchActivity(r, null, null);
}
public final Activity getActivity(IBinder token) {
@@ -2129,7 +2138,8 @@ public final class ActivityThread {
sendMessage(H.CLEAN_UP_CONTEXT, cci);
}
- private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
+ private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent,
+ Bundle options) {
// System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
ActivityInfo aInfo = r.activityInfo;
@@ -2187,7 +2197,7 @@ public final class ActivityThread {
+ r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
- r.embeddedID, r.lastNonConfigurationInstances, config);
+ r.embeddedID, r.lastNonConfigurationInstances, config, options);
if (customIntent != null) {
activity.mIntent = customIntent;
@@ -2297,12 +2307,13 @@ public final class ActivityThread {
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
- Activity a = performLaunchActivity(r, customIntent);
+
+ Activity a = performLaunchActivity(r, customIntent, r.activityOptions);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
- handleResumeActivity(r.token, false, r.isForward,
+ handleResumeActivity(r.token, r.activityOptions, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
if (!r.activity.mFinished && r.startsNotResumed) {
@@ -2861,12 +2872,13 @@ public final class ActivityThread {
r.mPendingRemoveWindowManager = null;
}
- final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
- boolean reallyResume) {
+ final void handleResumeActivity(IBinder token, Bundle resumeArgs,
+ boolean clearHide, boolean isForward, boolean reallyResume) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
+ // TODO Push resumeArgs into the activity for consideration
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
@@ -2991,11 +3003,17 @@ public final class ActivityThread {
int h;
if (w < 0) {
Resources res = r.activity.getResources();
- mThumbnailHeight = h =
- res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
-
- mThumbnailWidth = w =
- res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
+ if (SystemProperties.getBoolean("persist.recents.use_alternate", false)) {
+ int wId = com.android.internal.R.dimen.recents_thumbnail_width;
+ int hId = com.android.internal.R.dimen.recents_thumbnail_height;
+ mThumbnailWidth = w = res.getDimensionPixelSize(wId);
+ mThumbnailHeight = h = res.getDimensionPixelSize(hId);
+ } else {
+ mThumbnailHeight = h =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
+ mThumbnailWidth = w =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
+ }
} else {
h = mThumbnailHeight;
}
@@ -3787,6 +3805,7 @@ public final class ActivityThread {
}
}
r.startsNotResumed = tmp.startsNotResumed;
+ r.activityOptions = null;
handleLaunchActivity(r, currentIntent);
}
@@ -3964,6 +3983,7 @@ public final class ActivityThread {
ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config);
// Cleanup hardware accelerated stuff
+ // TODO: Do we actually want to do this in response to all config changes?
WindowManagerGlobal.getInstance().trimLocalMemory();
freeTextLayoutCachesIfNeeded(configDiff);
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index 10d5e25..ab148a9 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;
@@ -147,10 +146,10 @@ public class AlertDialog extends Dialog implements DialogInterface {
}
/**
- * Gets one of the buttons used in the dialog.
- * <p>
- * If a button does not exist in the dialog, null will be returned.
- *
+ * Gets one of the buttons used in the dialog. Returns null if the specified
+ * button does not exist or the dialog has not yet been fully created (for
+ * example, via {@link #show()} or {@link #create()}).
+ *
* @param whichButton The identifier of the button that should be returned.
* For example, this can be
* {@link DialogInterface#BUTTON_POSITIVE}.
@@ -159,7 +158,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
public Button getButton(int whichButton) {
return mAlert.getButton(whichButton);
}
-
+
/**
* Gets the list view used in the dialog.
*
@@ -853,6 +852,21 @@ public class AlertDialog extends Dialog implements DialogInterface {
}
/**
+ * Set a custom view resource to be the contents of the Dialog. The
+ * resource will be inflated, adding all top-level views to the screen.
+ *
+ * @param layoutResId Resource ID to be inflated.
+ * @return This Builder object to allow for chaining of calls to set
+ * methods
+ */
+ public Builder setView(int layoutResId) {
+ P.mView = null;
+ P.mViewLayoutResId = layoutResId;
+ P.mViewSpacingSpecified = false;
+ return this;
+ }
+
+ /**
* Set a custom view to be the contents of the Dialog. If the supplied view is an instance
* of a {@link ListView} the light background will be used.
*
@@ -862,6 +876,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
*/
public Builder setView(View view) {
P.mView = view;
+ P.mViewLayoutResId = 0;
P.mViewSpacingSpecified = false;
return this;
}
@@ -891,6 +906,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
public Builder setView(View view, int viewSpacingLeft, int viewSpacingTop,
int viewSpacingRight, int viewSpacingBottom) {
P.mView = view;
+ P.mViewLayoutResId = 0;
P.mViewSpacingSpecified = true;
P.mViewSpacingLeft = viewSpacingLeft;
P.mViewSpacingTop = viewSpacingTop;
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 4ddabd9..8165fa1 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*/
@@ -1094,7 +1092,7 @@ final class ApplicationPackageManager extends PackageManager {
}
@Override
- public void installPackageWithVerificationAndEncryption(Uri packageURI,
+ public void installPackageWithVerificationAndEncryption(Uri packageURI,
IPackageInstallObserver observer, int flags, String installerPackageName,
VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
try {
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index cb453e2..f1c632e 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -113,7 +113,8 @@ public abstract class ApplicationThreadNative extends Binder
IBinder b = data.readStrongBinder();
int procState = data.readInt();
boolean isForward = data.readInt() != 0;
- scheduleResumeActivity(b, procState, isForward);
+ Bundle resumeArgs = data.readBundle();
+ scheduleResumeActivity(b, procState, isForward, resumeArgs);
return true;
}
@@ -145,8 +146,10 @@ public abstract class ApplicationThreadNative extends Binder
ParcelFileDescriptor profileFd = data.readInt() != 0
? ParcelFileDescriptor.CREATOR.createFromParcel(data) : null;
boolean autoStopProfiler = data.readInt() != 0;
+ Bundle resumeArgs = data.readBundle();
scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, procState, state,
- ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler);
+ ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler,
+ resumeArgs);
return true;
}
@@ -705,20 +708,22 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
- public final void scheduleResumeActivity(IBinder token, int procState, boolean isForward)
+ public final void scheduleResumeActivity(IBinder token, int procState, boolean isForward,
+ Bundle resumeArgs)
throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
data.writeInt(procState);
data.writeInt(isForward ? 1 : 0);
+ data.writeBundle(resumeArgs);
mRemote.transact(SCHEDULE_RESUME_ACTIVITY_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
}
public final void scheduleSendResult(IBinder token, List<ResultInfo> results)
- throws RemoteException {
+ throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
@@ -731,9 +736,10 @@ class ApplicationThreadProxy implements IApplicationThread {
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
int procState, Bundle state, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
- String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler)
- throws RemoteException {
+ List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+ String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
+ Bundle resumeArgs)
+ throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
intent.writeToParcel(data, 0);
@@ -756,6 +762,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeInt(0);
}
data.writeInt(autoStopProfiler ? 1 : 0);
+ data.writeBundle(resumeArgs);
mRemote.transact(SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
@@ -886,7 +893,7 @@ class ApplicationThreadProxy implements IApplicationThread {
}
public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
- int flags, Intent args) throws RemoteException {
+ int flags, Intent args) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index df50989..0351292 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -17,6 +17,7 @@
package android.app;
import android.os.Build;
+
import com.android.internal.policy.PolicyManager;
import com.android.internal.util.Preconditions;
@@ -62,6 +63,7 @@ import android.location.ILocationManager;
import android.location.LocationManager;
import android.media.AudioManager;
import android.media.MediaRouter;
+import android.media.session.MediaSessionManager;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.INetworkPolicyManager;
@@ -595,6 +597,12 @@ class ContextImpl extends Context {
public Object createService(ContextImpl ctx) {
return new ConsumerIrManager(ctx);
}});
+
+ registerService(MEDIA_SESSION_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ return new MediaSessionManager(ctx);
+ }
+ });
}
static ContextImpl getImpl(Context context) {
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index a8277b5..fb96d8d 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;
@@ -240,6 +239,18 @@ public class Dialog implements DialogInterface, Window.Callback,
}
/**
+ * Forces immediate creation of the dialog.
+ * <p>
+ * Note that you should not override this method to perform dialog creation.
+ * Rather, override {@link #onCreate(Bundle)}.
+ */
+ public void create() {
+ if (!mCreated) {
+ dispatchOnCreate(null);
+ }
+ }
+
+ /**
* Start the dialog and display it on screen. The window is placed in the
* application layer and opaque. Note that you should not override this
* method to do initialization when the dialog is shown, instead implement
@@ -457,11 +468,12 @@ public class Dialog implements DialogInterface, Window.Callback,
}
/**
- * Finds a view that was identified by the id attribute from the XML that
- * was processed in {@link #onStart}.
+ * Finds a child view with the given identifier. Returns null if the
+ * specified child view does not exist or the dialog has not yet been fully
+ * created (for example, via {@link #show()} or {@link #create()}).
*
* @param id the identifier of the view to find
- * @return The view if found or null otherwise.
+ * @return The view with the given id or null.
*/
public View findViewById(int id) {
return mWindow.findViewById(id);
@@ -480,7 +492,7 @@ public class Dialog implements DialogInterface, Window.Callback,
/**
* Set the screen content to an explicit view. This view is placed
* directly into the screen's view hierarchy. It can itself be a complex
- * view hierarhcy.
+ * view hierarchy.
*
* @param view The desired content to display.
*/
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 af8f177..6c0d379 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/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 02a6343..cb06a42 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -122,6 +122,7 @@ public interface IActivityManager extends IInterface {
public void resizeStack(int stackId, Rect bounds) throws RemoteException;
public List<StackInfo> getAllStackInfos() throws RemoteException;
public StackInfo getStackInfo(int stackId) throws RemoteException;
+ public boolean isInHomeStack(int taskId) throws RemoteException;
public void setFocusedStack(int stackId) throws RemoteException;
public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException;
/* oneway */
@@ -243,7 +244,8 @@ public interface IActivityManager extends IInterface {
public void enterSafeMode() throws RemoteException;
- public void noteWakeupAlarm(IIntentSender sender) throws RemoteException;
+ public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg)
+ throws RemoteException;
public boolean killPids(int[] pids, String reason, boolean secure) throws RemoteException;
public boolean killProcessesBelowForeground(String reason) throws RemoteException;
@@ -348,6 +350,7 @@ public interface IActivityManager extends IInterface {
// Multi-user APIs
public boolean switchUser(int userid) throws RemoteException;
+ public boolean startUserInBackground(int userid) throws RemoteException;
public int stopUser(int userid, IStopUserCallback callback) throws RemoteException;
public UserInfo getCurrentUser() throws RemoteException;
public boolean isUserRunning(int userid, boolean orStopping) throws RemoteException;
@@ -366,6 +369,8 @@ public interface IActivityManager extends IInterface {
public Intent getIntentForIntentSender(IIntentSender sender) throws RemoteException;
+ public String getTagForIntentSender(IIntentSender sender, String prefix) throws RemoteException;
+
public void updatePersistentConfiguration(Configuration values) throws RemoteException;
public long[] getProcessPss(int[] pids) throws RemoteException;
@@ -419,6 +424,18 @@ public interface IActivityManager extends IInterface {
public IBinder getHomeActivityToken() throws RemoteException;
+ /** @hide */
+ public void startLockTaskMode(int taskId) throws RemoteException;
+
+ /** @hide */
+ public void startLockTaskMode(IBinder token) throws RemoteException;
+
+ /** @hide */
+ public void stopLockTaskMode() throws RemoteException;
+
+ /** @hide */
+ public boolean isInLockTaskMode() throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -708,4 +725,14 @@ public interface IActivityManager extends IInterface {
int GET_HOME_ACTIVITY_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+183;
int GET_ACTIVITY_CONTAINER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+184;
int DELETE_ACTIVITY_CONTAINER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+185;
+
+
+ // Start of L transactions
+ int GET_TAG_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+210;
+ int START_USER_IN_BACKGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+211;
+ int IS_IN_HOME_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+212;
+ int START_LOCK_TASK_BY_TASK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+213;
+ int START_LOCK_TASK_BY_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+214;
+ int STOP_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+215;
+ int IS_IN_LOCK_TASK_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+216;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 3aceff9..ac8ac8f 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -50,15 +50,16 @@ public interface IApplicationThread extends IInterface {
int configChanges) throws RemoteException;
void scheduleWindowVisibility(IBinder token, boolean showWindow) throws RemoteException;
void scheduleSleeping(IBinder token, boolean sleeping) throws RemoteException;
- void scheduleResumeActivity(IBinder token, int procState, boolean isForward)
+ void scheduleResumeActivity(IBinder token, int procState, boolean isForward, Bundle resumeArgs)
throws RemoteException;
void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException;
void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
int procState, Bundle state, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
- String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler)
- throws RemoteException;
+ List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+ String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler,
+ Bundle resumeArgs)
+ throws RemoteException;
void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, int configChanges,
boolean notResumed, Configuration config) throws RemoteException;
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 6be2b7b..12a8ff6 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;
@@ -201,6 +204,15 @@ public class Notification implements Parcelable
*/
public RemoteViews bigContentView;
+
+ /**
+ * @hide
+ * A medium-format version of {@link #contentView}, giving the Notification an
+ * opportunity to add action buttons to contentView. The system UI may
+ * choose to show this as a popup notification at its discretion.
+ */
+ public RemoteViews headsUpContentView;
+
/**
* The bitmap that may escape the bounds of the panel and bar.
*/
@@ -357,6 +369,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.
@@ -398,8 +415,34 @@ public class Notification implements Parcelable
* system will make a determination about how to interpret this priority when presenting
* the notification.
*/
+ @Priority
public int priority;
+
+ /**
+ * Sphere of visibility of this notification, which affects how and when the SystemUI reveals
+ * the notification's presence and contents in untrusted situations (namely, on the secure
+ * lockscreen).
+ *
+ * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always
+ * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are
+ * shown in all situations, but the contents are only available if the device is unlocked for
+ * the appropriate user.
+ *
+ * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification
+ * can be read even in an "insecure" context (that is, above a secure lockscreen).
+ * To modify the public version of this notification—for example, to redact some portions—see
+ * {@link Builder#setPublicVersion(Notification)}.
+ *
+ * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon
+ * and ticker until the user has bypassed the lockscreen.
+ */
+ public int visibility;
+
+ public static final int VISIBILITY_PUBLIC = 1;
+ public static final int VISIBILITY_PRIVATE = 0;
+ public static final int VISIBILITY_SECRET = -1;
+
/**
* @hide
* Notification type: incoming call (voice or video) or similar synchronous communication request.
@@ -547,6 +590,7 @@ public class Notification implements Parcelable
* notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
*/
public static final String EXTRA_TEXT_LINES = "android.textLines";
+ public static final String EXTRA_TEMPLATE = "android.template";
/**
* {@link #extras} key: An array of people that this notification relates to, specified
@@ -568,6 +612,13 @@ public class Notification implements Parcelable
public static final String EXTRA_AS_HEADS_UP = "headsup";
/**
+ * Extra added from {@link Notification.Builder} to indicate that the remote views were inflated
+ * from the builder, as opposed to being created directly from the application.
+ * @hide
+ */
+ public static final String EXTRA_BUILDER_REMOTE_VIEWS = "android.builderRemoteViews";
+
+ /**
* Value for {@link #EXTRA_AS_HEADS_UP}.
* @hide
*/
@@ -668,6 +719,13 @@ public class Notification implements Parcelable
public Action[] actions;
/**
+ * Replacement version of this notification whose content will be shown
+ * in an insecure context such as atop a secure keyguard. See {@link #visibility}
+ * and {@link #VISIBILITY_PUBLIC}.
+ */
+ public Notification publicVersion;
+
+ /**
* Constructs a Notification object with default values.
* You might want to consider using {@link Builder} instead.
*/
@@ -766,6 +824,16 @@ public class Notification implements Parcelable
if (parcel.readInt() != 0) {
bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
}
+
+ if (parcel.readInt() != 0) {
+ headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel);
+ }
+
+ visibility = parcel.readInt();
+
+ if (parcel.readInt() != 0) {
+ publicVersion = Notification.CREATOR.createFromParcel(parcel);
+ }
}
@Override
@@ -851,6 +919,17 @@ public class Notification implements Parcelable
that.bigContentView = this.bigContentView.clone();
}
+ if (heavy && this.headsUpContentView != null) {
+ that.headsUpContentView = this.headsUpContentView.clone();
+ }
+
+ that.visibility = this.visibility;
+
+ if (this.publicVersion != null) {
+ that.publicVersion = new Notification();
+ this.publicVersion.cloneInto(that.publicVersion, heavy);
+ }
+
if (!heavy) {
that.lightenPayload(); // will clean out extras
}
@@ -865,6 +944,7 @@ public class Notification implements Parcelable
tickerView = null;
contentView = null;
bigContentView = null;
+ headsUpContentView = null;
largeIcon = null;
if (extras != null) {
extras.remove(Notification.EXTRA_LARGE_ICON);
@@ -976,6 +1056,22 @@ public class Notification implements Parcelable
} else {
parcel.writeInt(0);
}
+
+ if (headsUpContentView != null) {
+ parcel.writeInt(1);
+ headsUpContentView.writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+
+ parcel.writeInt(visibility);
+
+ if (publicVersion != null) {
+ parcel.writeInt(1);
+ publicVersion.writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
}
/**
@@ -1118,6 +1214,9 @@ public class Notification implements Parcelable
if (bigContentView != null) {
bigContentView.setUser(user);
}
+ if (headsUpContentView != null) {
+ headsUpContentView.setUser(user);
+ }
}
/**
@@ -1179,6 +1278,9 @@ public class Notification implements Parcelable
private boolean mUseChronometer;
private Style mStyle;
private boolean mShowWhen = true;
+ private int mVisibility = VISIBILITY_PRIVATE;
+ private Notification mPublicVersion = null;
+ private boolean mQuantumTheme;
/**
* Constructs a new Builder with the defaults:
@@ -1206,6 +1308,9 @@ public class Notification implements Parcelable
mWhen = System.currentTimeMillis();
mAudioStreamType = STREAM_DEFAULT;
mPriority = PRIORITY_DEFAULT;
+
+ // TODO: Decide on targetSdk from calling app whether to use quantum theme.
+ mQuantumTheme = true;
}
/**
@@ -1568,7 +1673,7 @@ public class Notification implements Parcelable
*
* @see Notification#priority
*/
- public Builder setPriority(int pri) {
+ public Builder setPriority(@Priority int pri) {
mPriority = pri;
return this;
}
@@ -1672,6 +1777,30 @@ public class Notification implements Parcelable
return this;
}
+ /**
+ * Specify the value of {@link #visibility}.
+
+ * @param visibility One of {@link #VISIBILITY_PRIVATE} (the default),
+ * {@link #VISIBILITY_SECRET}, or {@link #VISIBILITY_PUBLIC}.
+ *
+ * @return The same Builder.
+ */
+ public Builder setVisibility(int visibility) {
+ mVisibility = visibility;
+ return this;
+ }
+
+ /**
+ * Supply a replacement Notification whose contents should be shown in insecure contexts
+ * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}.
+ * @param n A replacement notification, presumably with some or all info redacted.
+ * @return The same Builder.
+ */
+ public Builder setPublicVersion(Notification n) {
+ mPublicVersion = n;
+ return this;
+ }
+
private void setFlag(int mask, boolean value) {
if (value) {
mFlags |= mask;
@@ -1689,7 +1818,7 @@ public class Notification implements Parcelable
contentView.setImageViewBitmap(R.id.icon, mLargeIcon);
smallIconImageViewId = R.id.right_icon;
}
- if (mPriority < PRIORITY_LOW) {
+ if (!mQuantumTheme && mPriority < PRIORITY_LOW) {
contentView.setInt(R.id.icon,
"setBackgroundResource", R.drawable.notification_template_icon_low_bg);
contentView.setInt(R.id.status_bar_latest_event_content,
@@ -1803,7 +1932,7 @@ public class Notification implements Parcelable
if (mContentView != null) {
return mContentView;
} else {
- return applyStandardTemplate(R.layout.notification_template_base, true); // no more special large_icon flavor
+ return applyStandardTemplate(getBaseLayoutResource(), true); // no more special large_icon flavor
}
}
@@ -1824,14 +1953,21 @@ public class Notification implements Parcelable
private RemoteViews makeBigContentView() {
if (mActions.size() == 0) return null;
- return applyStandardTemplateWithActions(R.layout.notification_template_big_base);
+ return applyStandardTemplateWithActions(getBigBaseLayoutResource());
+ }
+
+ private RemoteViews makeHeadsUpContentView() {
+ if (mActions.size() == 0) return null;
+
+ return applyStandardTemplateWithActions(getBigBaseLayoutResource());
}
+
private RemoteViews generateActionButton(Action action) {
final boolean tombstone = (action.actionIntent == null);
RemoteViews button = new RemoteViews(mContext.getPackageName(),
- tombstone ? R.layout.notification_action_tombstone
- : R.layout.notification_action);
+ tombstone ? getActionTombstoneLayoutResource()
+ : getActionLayoutResource());
button.setTextViewCompoundDrawablesRelative(R.id.action0, action.icon, 0, 0, 0);
button.setTextViewText(R.id.action0, action.title);
if (!tombstone) {
@@ -1867,6 +2003,7 @@ public class Notification implements Parcelable
n.defaults = mDefaults;
n.flags = mFlags;
n.bigContentView = makeBigContentView();
+ n.headsUpContentView = makeHeadsUpContentView();
if (mLedOnMs != 0 || mLedOffMs != 0) {
n.flags |= FLAG_SHOW_LIGHTS;
}
@@ -1884,6 +2021,12 @@ public class Notification implements Parcelable
n.actions = new Action[mActions.size()];
mActions.toArray(n.actions);
}
+ n.visibility = mVisibility;
+
+ if (mPublicVersion != null) {
+ n.publicVersion = new Notification();
+ mPublicVersion.cloneInto(n.publicVersion, true);
+ }
return n;
}
@@ -1905,6 +2048,7 @@ public class Notification implements Parcelable
extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mProgressIndeterminate);
extras.putBoolean(EXTRA_SHOW_CHRONOMETER, mUseChronometer);
extras.putBoolean(EXTRA_SHOW_WHEN, mShowWhen);
+ extras.putBoolean(EXTRA_BUILDER_REMOTE_VIEWS, mContentView == null);
if (mLargeIcon != null) {
extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
}
@@ -1948,6 +2092,49 @@ public class Notification implements Parcelable
build().cloneInto(n, true);
return n;
}
+
+
+ private int getBaseLayoutResource() {
+ return mQuantumTheme
+ ? R.layout.notification_template_quantum_base
+ : R.layout.notification_template_base;
+ }
+
+ private int getBigBaseLayoutResource() {
+ return mQuantumTheme
+ ? R.layout.notification_template_quantum_big_base
+ : R.layout.notification_template_big_base;
+ }
+
+ private int getBigPictureLayoutResource() {
+ return mQuantumTheme
+ ? R.layout.notification_template_quantum_big_picture
+ : R.layout.notification_template_big_picture;
+ }
+
+ private int getBigTextLayoutResource() {
+ return mQuantumTheme
+ ? R.layout.notification_template_quantum_big_text
+ : R.layout.notification_template_big_text;
+ }
+
+ private int getInboxLayoutResource() {
+ return mQuantumTheme
+ ? R.layout.notification_template_quantum_inbox
+ : R.layout.notification_template_inbox;
+ }
+
+ private int getActionLayoutResource() {
+ return mQuantumTheme
+ ? R.layout.notification_quantum_action
+ : R.layout.notification_action;
+ }
+
+ private int getActionTombstoneLayoutResource() {
+ return mQuantumTheme
+ ? R.layout.notification_quantum_action_tombstone
+ : R.layout.notification_action_tombstone;
+ }
}
/**
@@ -2033,6 +2220,7 @@ public class Notification implements Parcelable
if (mBigContentTitle != null) {
extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
}
+ extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
}
/**
@@ -2116,7 +2304,7 @@ public class Notification implements Parcelable
}
private RemoteViews makeBigContentView() {
- RemoteViews contentView = getStandardView(R.layout.notification_template_big_picture);
+ RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
contentView.setImageViewBitmap(R.id.big_picture, mPicture);
@@ -2215,7 +2403,7 @@ public class Notification implements Parcelable
final boolean hadThreeLines = (mBuilder.mContentText != null && mBuilder.mSubText != null);
mBuilder.mContentText = null;
- RemoteViews contentView = getStandardView(R.layout.notification_template_big_text);
+ RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource());
if (hadThreeLines) {
// vertical centering
@@ -2309,7 +2497,7 @@ public class Notification implements Parcelable
private RemoteViews makeBigContentView() {
// Remove the content text so line3 disappears unless you have a summary
mBuilder.mContentText = null;
- RemoteViews contentView = getStandardView(R.layout.notification_template_inbox);
+ RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource());
contentView.setViewVisibility(R.id.text2, View.GONE);
diff --git a/core/java/android/app/OnActivityPausedListener.java b/core/java/android/app/OnActivityPausedListener.java
index 379f133..5003973 100644
--- a/core/java/android/app/OnActivityPausedListener.java
+++ b/core/java/android/app/OnActivityPausedListener.java
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 45467b8..8efc14f 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 indicating that this PendingIntent can be used only once.
* For use with {@link #getActivity}, {@link #getBroadcast}, and
@@ -222,7 +248,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);
}
@@ -255,7 +281,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;
@@ -280,7 +306,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;
@@ -345,7 +371,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);
}
@@ -395,7 +421,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++) {
@@ -421,7 +447,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++) {
@@ -465,7 +491,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()));
}
@@ -519,7 +545,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;
@@ -749,6 +775,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()
@@ -807,6 +834,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()
@@ -861,6 +889,20 @@ public final class PendingIntent implements Parcelable {
}
/**
+ * @hide
+ * Return descriptive tag for this PendingIntent.
+ */
+ public String getTag(String prefix) {
+ try {
+ return ActivityManagerNative.getDefault()
+ .getTagForIntentSender(mTarget, prefix);
+ } catch (RemoteException e) {
+ // Should never happen.
+ return null;
+ }
+ }
+
+ /**
* Comparison operator on two PendingIntent objects, such that true
* is returned then they both represent the same operation from the
* same package. This allows you to use {@link #getActivity},
@@ -922,8 +964,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);
}
@@ -938,7 +980,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/SearchDialog.java b/core/java/android/app/SearchDialog.java
index d04e9db..af1810b 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -188,8 +188,7 @@ public class SearchDialog extends Dialog {
mSearchView.findViewById(com.android.internal.R.id.search_src_text);
mAppIcon = (ImageView) findViewById(com.android.internal.R.id.search_app_icon);
mSearchPlate = mSearchView.findViewById(com.android.internal.R.id.search_plate);
- mWorkingSpinner = getContext().getResources().
- getDrawable(com.android.internal.R.drawable.search_spinner);
+ mWorkingSpinner = getContext().getDrawable(com.android.internal.R.drawable.search_spinner);
// TODO: Restore the spinner for slow suggestion lookups
// mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds(
// null, null, mWorkingSpinner, null);
@@ -458,7 +457,7 @@ public class SearchDialog extends Dialog {
// optionally show one or the other.
if (mSearchable.useBadgeIcon()) {
- icon = mActivityContext.getResources().getDrawable(mSearchable.getIconId());
+ icon = mActivityContext.getDrawable(mSearchable.getIconId());
visibility = View.VISIBLE;
if (DBG) Log.d(LOG_TAG, "Using badge icon: " + mSearchable.getIconId());
} else if (mSearchable.useBadgeLabel()) {
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 a292ecb..1b838fb 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/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 2045ed8..a6a04d1 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -38,8 +38,11 @@ public class StatusBarManager {
public static final int DISABLE_NOTIFICATION_ICONS = View.STATUS_BAR_DISABLE_NOTIFICATION_ICONS;
public static final int DISABLE_NOTIFICATION_ALERTS
= View.STATUS_BAR_DISABLE_NOTIFICATION_ALERTS;
+ @Deprecated
public static final int DISABLE_NOTIFICATION_TICKER
= View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER;
+ public static final int DISABLE_PRIVATE_NOTIFICATIONS
+ = View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER;
public static final int DISABLE_SYSTEM_INFO = View.STATUS_BAR_DISABLE_SYSTEM_INFO;
public static final int DISABLE_HOME = View.STATUS_BAR_DISABLE_HOME;
public static final int DISABLE_RECENT = View.STATUS_BAR_DISABLE_RECENT;
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..e6e0f35 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -41,13 +41,10 @@ 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;
-import android.util.DisplayMetrics;
import android.util.Log;
-import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import java.io.BufferedInputStream;
@@ -221,24 +218,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 +229,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 +265,6 @@ public class WallpaperManager {
synchronized (this) {
mWallpaper = null;
mDefaultWallpaper = null;
- mHandler.removeMessages(MSG_CLEAR_WALLPAPER);
}
}
@@ -294,9 +278,8 @@ public class WallpaperManager {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
- Bitmap bm = BitmapFactory.decodeFileDescriptor(
+ return BitmapFactory.decodeFileDescriptor(
fd.getFileDescriptor(), null, options);
- return generateBitmap(context, bm, width, height);
} catch (OutOfMemoryError e) {
Log.w(TAG, "Can't decode file", e);
} finally {
@@ -323,8 +306,7 @@ public class WallpaperManager {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
- Bitmap bm = BitmapFactory.decodeStream(is, null, options);
- return generateBitmap(context, bm, width, height);
+ return BitmapFactory.decodeStream(is, null, options);
} catch (OutOfMemoryError e) {
Log.w(TAG, "Can't decode stream", e);
} finally {
@@ -1029,62 +1011,4 @@ public class WallpaperManager {
public void clear() throws IOException {
setResource(com.android.internal.R.drawable.default_wallpaper);
}
-
- static Bitmap generateBitmap(Context context, Bitmap bm, int width, int height) {
- if (bm == null) {
- return null;
- }
-
- WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
- DisplayMetrics metrics = new DisplayMetrics();
- wm.getDefaultDisplay().getMetrics(metrics);
- bm.setDensity(metrics.noncompatDensityDpi);
-
- if (width <= 0 || height <= 0
- || (bm.getWidth() == width && bm.getHeight() == height)) {
- return bm;
- }
-
- // This is the final bitmap we want to return.
- try {
- Bitmap newbm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- newbm.setDensity(metrics.noncompatDensityDpi);
-
- Canvas c = new Canvas(newbm);
- Rect targetRect = new Rect();
- targetRect.right = bm.getWidth();
- targetRect.bottom = bm.getHeight();
-
- int deltaw = width - targetRect.right;
- int deltah = height - targetRect.bottom;
-
- if (deltaw > 0 || deltah > 0) {
- // We need to scale up so it covers the entire area.
- float scale;
- if (deltaw > deltah) {
- scale = width / (float)targetRect.right;
- } else {
- scale = height / (float)targetRect.bottom;
- }
- targetRect.right = (int)(targetRect.right*scale);
- targetRect.bottom = (int)(targetRect.bottom*scale);
- deltaw = width - targetRect.right;
- deltah = height - targetRect.bottom;
- }
-
- targetRect.offset(deltaw/2, deltah/2);
-
- Paint paint = new Paint();
- paint.setFilterBitmap(true);
- paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
- c.drawBitmap(bm, null, targetRect, paint);
-
- bm.recycle();
- c.setBitmap(null);
- return newbm;
- } catch (OutOfMemoryError e) {
- Log.w(TAG, "Can't generate default bitmap", e);
- return bm;
- }
- }
}
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index 30b65de..f9d9059 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -29,25 +29,25 @@ import android.os.Bundle;
* Base class for implementing a device administration component. This
* class provides a convenience for interpreting the raw intent actions
* that are sent by the system.
- *
+ *
* <p>The callback methods, like the base
* {@link BroadcastReceiver#onReceive(Context, Intent) BroadcastReceiver.onReceive()}
* method, happen on the main thread of the process. Thus long running
* operations must be done on another thread. Note that because a receiver
* is done once returning from its receive function, such long-running operations
* should probably be done in a {@link Service}.
- *
+ *
* <p>When publishing your DeviceAdmin subclass as a receiver, it must
* handle {@link #ACTION_DEVICE_ADMIN_ENABLED} and require the
* {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission. A typical
* manifest entry would look like:</p>
- *
+ *
* {@sample development/samples/ApiDemos/AndroidManifest.xml device_admin_declaration}
- *
+ *
* <p>The meta-data referenced here provides addition information specific
* to the device administrator, as parsed by the {@link DeviceAdminInfo} class.
* A typical file would be:</p>
- *
+ *
* {@sample development/samples/ApiDemos/res/xml/device_admin_sample.xml meta_data}
*
* <div class="special reference">
@@ -86,7 +86,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED
= "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
-
+
/**
* A CharSequence that can be shown to the user informing them of the
* impact of disabling your admin.
@@ -94,7 +94,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
* @see #ACTION_DEVICE_ADMIN_DISABLE_REQUESTED
*/
public static final String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING";
-
+
/**
* Action sent to a device administrator when the user has disabled
* it. Upon return, the application no longer has access to the
@@ -107,7 +107,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_DEVICE_ADMIN_DISABLED
= "android.app.action.DEVICE_ADMIN_DISABLED";
-
+
/**
* Action sent to a device administrator when the user has changed the
* password of their device. You can at this point check the characteristics
@@ -115,7 +115,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
* DevicePolicyManager.isActivePasswordSufficient()}.
* You will generally
* handle this in {@link DeviceAdminReceiver#onPasswordChanged}.
- *
+ *
* <p>The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to receive
* this broadcast.
@@ -123,7 +123,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PASSWORD_CHANGED
= "android.app.action.ACTION_PASSWORD_CHANGED";
-
+
/**
* Action sent to a device administrator when the user has failed at
* attempted to enter the password. You can at this point check the
@@ -131,7 +131,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
* {@link DevicePolicyManager#getCurrentFailedPasswordAttempts
* DevicePolicyManager.getCurrentFailedPasswordAttempts()}. You will generally
* handle this in {@link DeviceAdminReceiver#onPasswordFailed}.
- *
+ *
* <p>The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive
* this broadcast.
@@ -139,11 +139,11 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PASSWORD_FAILED
= "android.app.action.ACTION_PASSWORD_FAILED";
-
+
/**
* Action sent to a device administrator when the user has successfully
* entered their password, after failing one or more times.
- *
+ *
* <p>The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive
* this broadcast.
@@ -165,15 +165,31 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
= "android.app.action.ACTION_PASSWORD_EXPIRING";
/**
+ * Broadcast Action: This broadcast is sent to the newly created profile when
+ * the provisioning of a managed profile has completed successfully.
+ *
+ * <p>The broadcast is limited to the package which started the provisioning as specified in
+ * the extra {@link DevicePolicyManager#EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} of the
+ * {@link DevicePolicyManager#ACTION_PROVISION_MANAGED_PROFILE} intent that started the
+ * provisioning. It is also limited to the managed profile.
+ *
+ * <p>Input: Nothing.</p>
+ * <p>Output: Nothing</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PROFILE_PROVISIONING_COMPLETE =
+ "android.app.action.ACTION_PROFILE_PROVISIONING_COMPLETE";
+
+ /**
* Name under which a DevicePolicy component publishes information
* about itself. This meta-data must reference an XML resource containing
* a device-admin tag. XXX TO DO: describe syntax.
*/
public static final String DEVICE_ADMIN_META_DATA = "android.app.device_admin";
-
+
private DevicePolicyManager mManager;
private ComponentName mWho;
-
+
/**
* Retrieve the DevicePolicyManager interface for this administrator to work
* with the system.
@@ -186,7 +202,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
Context.DEVICE_POLICY_SERVICE);
return mManager;
}
-
+
/**
* Retrieve the ComponentName describing who this device administrator is, for
* use in {@link DevicePolicyManager} APIs that require the administrator to
@@ -199,7 +215,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
mWho = new ComponentName(context, getClass());
return mWho;
}
-
+
/**
* Called after the administrator is first enabled, as a result of
* receiving {@link #ACTION_DEVICE_ADMIN_ENABLED}. At this point you
@@ -209,7 +225,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
*/
public void onEnabled(Context context, Intent intent) {
}
-
+
/**
* Called when the user has asked to disable the administrator, as a result of
* receiving {@link #ACTION_DEVICE_ADMIN_DISABLE_REQUESTED}, giving you
@@ -224,7 +240,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
public CharSequence onDisableRequested(Context context, Intent intent) {
return null;
}
-
+
/**
* Called prior to the administrator being disabled, as a result of
* receiving {@link #ACTION_DEVICE_ADMIN_DISABLED}. Upon return, you
@@ -235,7 +251,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
*/
public void onDisabled(Context context, Intent intent) {
}
-
+
/**
* Called after the user has changed their password, as a result of
* receiving {@link #ACTION_PASSWORD_CHANGED}. At this point you
@@ -247,7 +263,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
*/
public void onPasswordChanged(Context context, Intent intent) {
}
-
+
/**
* Called after the user has failed at entering their current password, as a result of
* receiving {@link #ACTION_PASSWORD_FAILED}. At this point you
@@ -258,7 +274,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
*/
public void onPasswordFailed(Context context, Intent intent) {
}
-
+
/**
* Called after the user has succeeded at entering their current password,
* as a result of receiving {@link #ACTION_PASSWORD_SUCCEEDED}. This will
@@ -292,6 +308,26 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
}
/**
+ * Called on the new profile when managed profile provisioning has completed.
+ * Managed profile provisioning is the process of setting up the device so that it has a
+ * separate profile which is managed by the mobile device management(mdm) application that
+ * triggered the provisioning.
+ *
+ * <p>As part of provisioning a new profile is created, the mdm is moved to the new profile and
+ * set as the owner of the profile so that it has full control over it.
+ * This intent is only received by the mdm package that is set as profile owner during
+ * provisioning.
+ *
+ * <p>Provisioning can be triggered via an intent with the action
+ * android.managedprovisioning.ACTION_PROVISION_MANAGED_PROFILE.
+ *
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ */
+ public void onProfileProvisioningComplete(Context context, Intent intent) {
+ }
+
+ /**
* Intercept standard device administrator broadcasts. Implementations
* should not override this method; it is better to implement the
* convenience callbacks for each action.
@@ -299,6 +335,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
+
if (ACTION_PASSWORD_CHANGED.equals(action)) {
onPasswordChanged(context, intent);
} else if (ACTION_PASSWORD_FAILED.equals(action)) {
@@ -317,6 +354,8 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
onDisabled(context, intent);
} else if (ACTION_PASSWORD_EXPIRING.equals(action)) {
onPasswordExpiring(context, intent);
+ } else if (ACTION_PROFILE_PROVISIONING_COMPLETE.equals(action)) {
+ onProfileProvisioningComplete(context, intent);
}
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ab82531..e06cf38 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -22,10 +22,12 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ComponentName;
import android.content.Context;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Handler;
+import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -75,6 +77,43 @@ public class DevicePolicyManager {
}
/**
+ * Activity action: Starts the provisioning flow which sets up a managed profile.
+ * This intent will typically be sent by a mobile device management application(mdm).
+ * Managed profile provisioning creates a profile, moves the mdm to the profile,
+ * sets the mdm as the profile owner and removes all non required applications from the profile.
+ * As a profile owner the mdm than has full control over the managed profile.
+ *
+ * <p>The intent must contain the extras {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} and
+ * {@link #EXTRA_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME}.
+ *
+ * <p> When managed provisioning has completed, an intent of the type
+ * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcasted to the
+ * mdm app on the managed profile.
+ *
+ * <p>Input: Nothing.</p>
+ * <p>Output: Nothing</p>
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PROVISION_MANAGED_PROFILE
+ = "android.managedprovisioning.ACTION_PROVISION_MANAGED_PROFILE";
+
+ /**
+ * A String extra holding the name of the package of the mobile device management application
+ * that starts the managed provisioning flow. This package will be set as the profile owner.
+ * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}.
+ */
+ public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME
+ = "deviceAdminPackageName";
+
+ /**
+ * A String extra holding the default name of the profile that is created during managed profile
+ * provisioning.
+ * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}
+ */
+ public static final String EXTRA_PROVISIONING_DEFAULT_MANAGED_PROFILE_NAME
+ = "defaultManagedProfileName";
+
+ /**
* Activity action: ask the user to add a new device administrator to the system.
* The desired policy is the ComponentName of the policy in the
* {@link #EXTRA_DEVICE_ADMIN} extra field. This will invoke a UI to
@@ -1153,7 +1192,9 @@ public class DevicePolicyManager {
}
exclSpec = listBuilder.toString();
}
- android.net.Proxy.validate(hostName, Integer.toString(port), exclSpec);
+ if (android.net.Proxy.validate(hostName, Integer.toString(port), exclSpec)
+ != android.net.Proxy.PROXY_VALID)
+ throw new IllegalArgumentException();
}
return mService.setGlobalProxy(admin, hostSpec, exclSpec, UserHandle.myUserId());
} catch (RemoteException e) {
@@ -1681,4 +1722,137 @@ public class DevicePolicyManager {
}
return null;
}
+
+ /**
+ * @hide
+ * Sets the given package as the profile owner of the given user profile. The package must
+ * already be installed and there shouldn't be an existing profile owner registered for this
+ * user. Also, this method must be called before the user has been used for the first time.
+ * @param packageName the package name of the application to be registered as profile owner.
+ * @param ownerName the human readable name of the organisation associated with this DPM.
+ * @param userHandle the userId to set the profile owner for.
+ * @return whether the package was successfully registered as the profile owner.
+ * @throws IllegalArgumentException if packageName is null, the package isn't installed, or
+ * the user has already been set up.
+ */
+ public boolean setProfileOwner(String packageName, String ownerName, int userHandle)
+ throws IllegalArgumentException {
+ if (mService != null) {
+ try {
+ return mService.setProfileOwner(packageName, ownerName, userHandle);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to set profile owner", re);
+ throw new IllegalArgumentException("Couldn't set profile owner.", re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Used to determine if a particular package is registered as the Profile Owner for the
+ * current user. A profile owner is a special device admin that has additional priviledges
+ * within the managed profile.
+ *
+ * @param packageName The package name of the app to compare with the registered profile owner.
+ * @return Whether or not the package is registered as the profile owner.
+ */
+ public boolean isProfileOwnerApp(String packageName) {
+ if (mService != null) {
+ try {
+ String profileOwnerPackage = mService.getProfileOwner(
+ Process.myUserHandle().getIdentifier());
+ return profileOwnerPackage != null && profileOwnerPackage.equals(packageName);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to check profile owner");
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @hide
+ * @return the packageName of the owner of the given user profile or null if no profile
+ * owner has been set for that user.
+ * @throws IllegalArgumentException if the userId is invalid.
+ */
+ public String getProfileOwner() throws IllegalArgumentException {
+ if (mService != null) {
+ try {
+ return mService.getProfileOwner(Process.myUserHandle().getIdentifier());
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to get profile owner");
+ throw new IllegalArgumentException(
+ "Requested profile owner for invalid userId", re);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ * @return the human readable name of the organisation associated with this DPM or null if
+ * one is not set.
+ * @throws IllegalArgumentException if the userId is invalid.
+ */
+ public String getProfileOwnerName() throws IllegalArgumentException {
+ if (mService != null) {
+ try {
+ return mService.getProfileOwnerName(Process.myUserHandle().getIdentifier());
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to get profile owner");
+ throw new IllegalArgumentException(
+ "Requested profile owner for invalid userId", re);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Called by a profile owner or device owner to add a default intent handler activity for
+ * intents that match a certain intent filter. This activity will remain the default intent
+ * handler even if the set of potential event handlers for the intent filter changes and if
+ * the intent preferences are reset.
+ *
+ * <p>The default disambiguation mechanism takes over if the activity is not installed
+ * (anymore). When the activity is (re)installed, it is automatically reset as default
+ * intent handler for the filter.
+ *
+ * <p>The calling device admin must be a profile owner or device owner. If it is not, a
+ * security exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param filter The IntentFilter for which a default handler is added.
+ * @param activity The Activity that is added as default intent handler.
+ */
+ public void addPersistentPreferredActivity(ComponentName admin, IntentFilter filter,
+ ComponentName activity) {
+ if (mService != null) {
+ try {
+ mService.addPersistentPreferredActivity(admin, filter, activity);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Called by a profile owner or device owner to remove all persistent intent handler preferences
+ * associated with the given package that were set by {@link #addPersistentPreferredActivity}.
+ *
+ * <p>The calling device admin must be a profile owner. If it is not, a security
+ * exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param packageName The name of the package for which preferences are removed.
+ */
+ public void clearPackagePersistentPreferredActivities(ComponentName admin,
+ String packageName) {
+ if (mService != null) {
+ try {
+ mService.clearPackagePersistentPreferredActivities(admin, packageName);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 172c47c..8119585 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -18,6 +18,7 @@
package android.app.admin;
import android.content.ComponentName;
+import android.content.IntentFilter;
import android.os.RemoteCallback;
/**
@@ -103,6 +104,13 @@ interface IDevicePolicyManager {
String getDeviceOwner();
String getDeviceOwnerName();
+ boolean setProfileOwner(String packageName, String ownerName, int userHandle);
+ String getProfileOwner(int userHandle);
+ String getProfileOwnerName(int userHandle);
+
boolean installCaCert(in byte[] certBuffer);
void uninstallCaCert(in byte[] certBuffer);
+
+ void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity);
+ void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName);
}
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/app/maintenance/IIdleCallback.aidl b/core/java/android/app/maintenance/IIdleCallback.aidl
new file mode 100644
index 0000000..582dede
--- /dev/null
+++ b/core/java/android/app/maintenance/IIdleCallback.aidl
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2014, 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.app.maintenance;
+
+import android.app.maintenance.IIdleService;
+
+/**
+ * The server side of the idle maintenance IPC protocols. The app-side implementation
+ * invokes on this interface to indicate completion of the (asynchronous) instructions
+ * issued by the server.
+ *
+ * In all cases, the 'who' parameter is the caller's service binder, used to track
+ * which idle service instance is reporting.
+ *
+ * {@hide}
+ */
+interface IIdleCallback {
+ /**
+ * Acknowledge receipt and processing of the asynchronous "start idle work" incall.
+ * 'result' is true if the app wants some time to perform ongoing background
+ * idle-time work; or false if the app declares that it does not need any time
+ * for such work.
+ */
+ void acknowledgeStart(int token, boolean result);
+
+ /**
+ * Acknowledge receipt and processing of the asynchronous "stop idle work" incall.
+ */
+ void acknowledgeStop(int token);
+
+ /*
+ * Tell the idle service manager that we're done with our idle maintenance, so that
+ * it can go on to the next one and stop attributing wakelock time to us etc.
+ *
+ * @param opToken The identifier passed in the startIdleMaintenance() call that
+ * indicated the beginning of this service's idle timeslice.
+ */
+ void idleFinished(int token);
+}
diff --git a/core/java/android/app/maintenance/IIdleService.aidl b/core/java/android/app/maintenance/IIdleService.aidl
new file mode 100644
index 0000000..54abccd
--- /dev/null
+++ b/core/java/android/app/maintenance/IIdleService.aidl
@@ -0,0 +1,34 @@
+/**
+ * Copyright 2014, 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.app.maintenance;
+
+import android.app.maintenance.IIdleCallback;
+
+/**
+ * Interface that the framework uses to communicate with application code
+ * that implements an idle-time "maintenance" service. End user code does
+ * not implement this interface directly; instead, the app's idle service
+ * implementation will extend android.app.maintenance.IdleService.
+ * {@hide}
+ */
+oneway interface IIdleService {
+ /**
+ * Begin your idle-time work.
+ */
+ void startIdleMaintenance(IIdleCallback callbackBinder, int token);
+ void stopIdleMaintenance(IIdleCallback callbackBinder, int token);
+}
diff --git a/core/java/android/app/maintenance/IdleService.java b/core/java/android/app/maintenance/IdleService.java
new file mode 100644
index 0000000..2331b81
--- /dev/null
+++ b/core/java/android/app/maintenance/IdleService.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2014 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.app.maintenance;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * Idle maintenance API. Full docs TBW (to be written).
+ */
+public abstract class IdleService extends Service {
+ private static final String TAG = "IdleService";
+
+ static final int MSG_START = 1;
+ static final int MSG_STOP = 2;
+ static final int MSG_FINISH = 3;
+
+ IdleHandler mHandler;
+ IIdleCallback mCallbackBinder;
+ int mToken;
+ final Object mHandlerLock = new Object();
+
+ void ensureHandler() {
+ synchronized (mHandlerLock) {
+ if (mHandler == null) {
+ mHandler = new IdleHandler(getMainLooper());
+ }
+ }
+ }
+
+ /**
+ * TBW: the idle service should supply an intent-filter handling this intent
+ * <p>
+ * <p class="note">The application must also protect the idle service with the
+ * {@code "android.permission.BIND_IDLE_SERVICE"} permission to ensure that other
+ * applications cannot maliciously bind to it. If an idle service's manifest
+ * declaration does not require that permission, it will never be invoked.
+ * </p>
+ */
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.idle.IdleService";
+
+ /**
+ * Idle services must be protected with this permission:
+ *
+ * <pre class="prettyprint">
+ * <service android:name="MyIdleService"
+ * android:permission="android.permission.BIND_IDLE_SERVICE" >
+ * ...
+ * </service>
+ * </pre>
+ *
+ * <p>If an idle service is declared in the manifest but not protected with this
+ * permission, that service will be ignored by the OS.
+ */
+ public static final String PERMISSION_BIND =
+ "android.permission.BIND_IDLE_SERVICE";
+
+ // Trampoline: the callbacks are always run on the main thread
+ IIdleService mBinder = new IIdleService.Stub() {
+ @Override
+ public void startIdleMaintenance(IIdleCallback callbackBinder, int token)
+ throws RemoteException {
+ ensureHandler();
+ Message msg = mHandler.obtainMessage(MSG_START, token, 0, callbackBinder);
+ mHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void stopIdleMaintenance(IIdleCallback callbackBinder, int token)
+ throws RemoteException {
+ ensureHandler();
+ Message msg = mHandler.obtainMessage(MSG_STOP, token, 0, callbackBinder);
+ mHandler.sendMessage(msg);
+ }
+ };
+
+ /**
+ * Your application may begin doing "idle" maintenance work in the background.
+ * <p>
+ * Your application may continue to run in the background until it receives a call
+ * to {@link #onIdleStop()}, at which point you <i>must</i> cease doing work. The
+ * OS will hold a wakelock on your application's behalf from the time this method is
+ * called until after the following call to {@link #onIdleStop()} returns.
+ * </p>
+ * <p>
+ * Returning {@code false} from this method indicates that you have no ongoing work
+ * to do at present. The OS will respond by immediately calling {@link #onIdleStop()}
+ * and returning your application to its normal stopped state. Returning {@code true}
+ * indicates that the application is indeed performing ongoing work, so the OS will
+ * let your application run in this state until it's no longer appropriate.
+ * </p>
+ * <p>
+ * You will always receive a matching call to {@link #onIdleStop()} even if your
+ * application returns {@code false} from this method.
+ *
+ * @return {@code true} to indicate that the application wishes to perform some ongoing
+ * background work; {@code false} to indicate that it does not need to perform such
+ * work at present.
+ */
+ public abstract boolean onIdleStart();
+
+ /**
+ * Your app's maintenance opportunity is over. Once the application returns from
+ * this method, the wakelock held by the OS on its behalf will be released.
+ */
+ public abstract void onIdleStop();
+
+ /**
+ * Tell the OS that you have finished your idle work. Calling this more than once,
+ * or calling it when you have not received an {@link #onIdleStart()} callback, is
+ * an error.
+ *
+ * <p>It is safe to call {@link #finishIdle()} from any thread.
+ */
+ public final void finishIdle() {
+ ensureHandler();
+ mHandler.sendEmptyMessage(MSG_FINISH);
+ }
+
+ class IdleHandler extends Handler {
+ IdleHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START: {
+ // Call the concrete onIdleStart(), reporting its return value back to
+ // the OS. If onIdleStart() throws, report it as a 'false' return but
+ // rethrow the exception at the offending app.
+ boolean result = false;
+ IIdleCallback callbackBinder = (IIdleCallback) msg.obj;
+ mCallbackBinder = callbackBinder;
+ final int token = mToken = msg.arg1;
+ try {
+ result = IdleService.this.onIdleStart();
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to start idle workload", e);
+ throw new RuntimeException(e);
+ } finally {
+ // don't bother if the service already called finishIdle()
+ if (mCallbackBinder != null) {
+ try {
+ callbackBinder.acknowledgeStart(token, result);
+ } catch (RemoteException re) {
+ Log.e(TAG, "System unreachable to start idle workload");
+ }
+ }
+ }
+ break;
+ }
+
+ case MSG_STOP: {
+ // Structured just like MSG_START for the stop-idle bookend call.
+ IIdleCallback callbackBinder = (IIdleCallback) msg.obj;
+ final int token = msg.arg1;
+ try {
+ IdleService.this.onIdleStop();
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to stop idle workload", e);
+ throw new RuntimeException(e);
+ } finally {
+ if (mCallbackBinder != null) {
+ try {
+ callbackBinder.acknowledgeStop(token);
+ } catch (RemoteException re) {
+ Log.e(TAG, "System unreachable to stop idle workload");
+ }
+ }
+ }
+ break;
+ }
+
+ case MSG_FINISH: {
+ if (mCallbackBinder != null) {
+ try {
+ mCallbackBinder.idleFinished(mToken);
+ } catch (RemoteException e) {
+ Log.e(TAG, "System unreachable to finish idling");
+ } finally {
+ mCallbackBinder = null;
+ }
+ } else {
+ Log.e(TAG, "finishIdle() called but the idle service is not started");
+ }
+ break;
+ }
+
+ default: {
+ Slog.w(TAG, "Unknown message " + msg.what);
+ }
+ }
+ }
+ }
+
+ /** @hide */
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return mBinder.asBinder();
+ }
+
+}
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/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 7b8b286..4b33799 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -172,7 +172,7 @@ public class AppWidgetProviderInfo implements Parcelable {
* <p>This field corresponds to the <code>android:previewImage</code> attribute in
* the <code>&lt;receiver&gt;</code> element in the AndroidManifest.xml file.
*/
- public int previewImage;
+ public int previewImage;
/**
* The rules by which a widget can be resized. See {@link #RESIZE_NONE},
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index d1f1f2a..38a71aa 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;
@@ -37,7 +35,6 @@ import java.util.HashMap;
import java.util.HashSet;
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..a396a05 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;
@@ -947,8 +945,13 @@ public final class BluetoothDevice implements Parcelable {
* was started.
*/
public boolean fetchUuidsWithSdp() {
+ IBluetooth service = sService;
+ if (service == null) {
+ Log.e(TAG, "BT not enabled. Cannot fetchUuidsWithSdp");
+ return false;
+ }
try {
- return sService.fetchRemoteUuids(this);
+ return service.fetchRemoteUuids(this);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index cd093c5..e3820a2 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 153215c..0c00c06 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 c48b15d..333f825 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 1e75fc2..f532f7c 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;
@@ -462,8 +457,10 @@ public final class BluetoothSocket implements Closeable {
mSocket.close();
mSocket = null;
}
- if(mPfd != null)
- mPfd.detachFd();
+ if (mPfd != null) {
+ mPfd.close();
+ mPfd = null;
+ }
}
}
}
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 2bf4d7d..f3c803d 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
@@ -192,6 +192,14 @@ public abstract class ContentResolver {
*/
public static final String CURSOR_DIR_BASE_TYPE = "vnd.android.cursor.dir";
+ /**
+ * This is the Android platform's generic MIME type to match any MIME
+ * type of the form "{@link #CURSOR_ITEM_BASE_TYPE}/{@code SUB_TYPE}".
+ * {@code SUB_TYPE} is the sub-type of the application-dependent
+ * content, e.g., "audio", "video", "playlist".
+ */
+ public static final String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
+
/** @hide */
public static final int SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS = 1;
/** @hide */
@@ -368,9 +376,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 +411,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 +1756,7 @@ public abstract class ContentResolver {
new SyncRequest.Builder()
.setSyncAdapter(account, authority)
.setExtras(extras)
- .syncOnce()
+ .syncOnce() // Immediate sync.
.build();
requestSync(request);
}
@@ -1759,9 +1764,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,8 +1831,21 @@ 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) {
+
}
}
@@ -1897,12 +1912,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 +1948,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 +1987,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 +2022,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 +2074,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 +2139,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 +2148,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);
+ 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(null, null, cname);
} catch (RemoteException e) {
throw new RuntimeException("the ContentService should always be reachable", e);
}
@@ -2098,7 +2225,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 +2241,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..81a886a 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,
@@ -356,6 +376,19 @@ public abstract class Context {
return getResources().getString(resId, formatArgs);
}
+ /**
+ * Return a drawable object associated with a particular resource ID and
+ * styled for the current theme.
+ *
+ * @param id The desired resource identifier, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource
+ * entry. The value 0 is an invalid identifier.
+ * @return Drawable An object that can be used to draw this resource.
+ */
+ public final Drawable getDrawable(int id) {
+ return getResources().getDrawable(id, getTheme());
+ }
+
/**
* Set the base theme for this context. Note that this should be called
* before any views are instantiated in the Context (for example before
@@ -495,7 +528,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 +707,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
@@ -707,7 +741,7 @@ public abstract class Context {
* Returned paths may be {@code null} if a storage device is unavailable.
*
* @see #getExternalFilesDir(String)
- * @see Environment#getStorageState(File)
+ * @see Environment#getExternalStorageState(File)
*/
public abstract File[] getExternalFilesDirs(String type);
@@ -771,7 +805,7 @@ public abstract class Context {
* Returned paths may be {@code null} if a storage device is unavailable.
*
* @see #getObbDir()
- * @see Environment#getStorageState(File)
+ * @see Environment#getExternalStorageState(File)
*/
public abstract File[] getObbDirs();
@@ -840,6 +874,7 @@ public abstract class Context {
*
* @see #getCacheDir
*/
+ @Nullable
public abstract File getExternalCacheDir();
/**
@@ -873,7 +908,7 @@ public abstract class Context {
* Returned paths may be {@code null} if a storage device is unavailable.
*
* @see #getExternalCacheDir()
- * @see Environment#getStorageState(File)
+ * @see Environment#getExternalStorageState(File)
*/
public abstract File[] getExternalCacheDirs();
@@ -960,7 +995,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 +1142,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 +1158,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 +1277,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 +1327,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 +1358,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 +1402,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 +1445,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 +1478,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 +1545,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 +1606,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 +1674,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 +1709,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 +1738,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 +1800,7 @@ public abstract class Context {
* @see #stopService
* @see #bindService
*/
+ @Nullable
public abstract ComponentName startService(Intent service);
/**
@@ -1798,7 +1840,7 @@ public abstract class Context {
* @hide like {@link #stopService(Intent)} but for a specific user.
*/
public abstract boolean stopServiceAsUser(Intent service, UserHandle user);
-
+
/**
* Connect to an application service, creating it if needed. This defines
* a dependency between your application and the service. The given
@@ -1846,8 +1888,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 +1910,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 +1935,65 @@ 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,
+ MEDIA_SESSION_SERVICE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ServiceName {}
/**
* Return the handle to a system-level service by name. The class of the
@@ -1995,7 +2094,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
@@ -2253,6 +2352,15 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.media.session.MediaSessionManager} for managing media Sessions.
+ *
+ * @see #getSystemService
+ * @see android.media.session.MediaSessionManager
+ */
+ public static final String MEDIA_SESSION_SERVICE = "media_session";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.telephony.TelephonyManager} for handling management the
* telephony features of the device.
*
@@ -2431,7 +2539,6 @@ public abstract class Context {
*
* @see #getSystemService
* @see android.hardware.camera2.CameraManager
- * @hide
*/
public static final String CAMERA_SERVICE = "camera";
@@ -2470,7 +2577,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 +2601,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 +2620,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 +2636,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 +2657,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 +2673,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 +2709,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 +2728,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 +2751,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 +2775,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 +2794,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 +2807,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 +2819,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 +2843,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 +2865,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 +2884,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 +2896,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 +2908,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 +2974,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 +3010,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 +3031,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 65e2268..96479e2 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;
@@ -3350,6 +3353,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
@@ -6394,6 +6403,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.
@@ -6487,10 +6511,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 a045b3a..e9d82af 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/SyncActivityTooManyDeletes.java b/core/java/android/content/SyncActivityTooManyDeletes.java
index 350f35e..093fb08 100644
--- a/core/java/android/content/SyncActivityTooManyDeletes.java
+++ b/core/java/android/content/SyncActivityTooManyDeletes.java
@@ -95,7 +95,7 @@ public class SyncActivityTooManyDeletes extends Activity
// try {
// final Context authContext = createPackageContext(desc.packageName, 0);
// ImageView imageView = new ImageView(this);
-// imageView.setImageDrawable(authContext.getResources().getDrawable(desc.iconId));
+// imageView.setImageDrawable(authContext.getDrawable(desc.iconId));
// ll.addView(imageView, lp);
// } catch (PackageManager.NameNotFoundException e) {
// }
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/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 9c46d96..8434c5d 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -338,7 +338,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* the normal application lifecycle.
*
* <p>Comes from the
- * {@link android.R.styleable#AndroidManifestApplication_cantSaveState android:cantSaveState}
+ * android.R.styleable#AndroidManifestApplication_cantSaveState
* attribute of the &lt;application&gt; tag.
*
* {@hide}
@@ -456,7 +456,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* behavior was introduced.
*/
public int targetSdkVersion;
-
+
+ /**
+ * The app's declared version code.
+ * @hide
+ */
+ public int versionCode;
+
/**
* When false, indicates that all components within this application are
* considered disabled, regardless of their individually set enabled status.
@@ -508,7 +514,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
if (sharedLibraryFiles != null) {
pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles);
}
- pw.println(prefix + "enabled=" + enabled + " targetSdkVersion=" + targetSdkVersion);
+ pw.println(prefix + "enabled=" + enabled + " targetSdkVersion=" + targetSdkVersion
+ + " versionCode=" + versionCode);
if (manageSpaceActivityName != null) {
pw.println(prefix + "manageSpaceActivityName="+manageSpaceActivityName);
}
@@ -576,6 +583,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dataDir = orig.dataDir;
uid = orig.uid;
targetSdkVersion = orig.targetSdkVersion;
+ versionCode = orig.versionCode;
enabled = orig.enabled;
enabledSetting = orig.enabledSetting;
installLocation = orig.installLocation;
@@ -616,6 +624,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeString(dataDir);
dest.writeInt(uid);
dest.writeInt(targetSdkVersion);
+ dest.writeInt(versionCode);
dest.writeInt(enabled ? 1 : 0);
dest.writeInt(enabledSetting);
dest.writeInt(installLocation);
@@ -655,6 +664,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dataDir = source.readString();
uid = source.readInt();
targetSdkVersion = source.readInt();
+ versionCode = source.readInt();
enabled = source.readInt() != 0;
enabledSetting = source.readInt();
installLocation = source.readInt();
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/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 20002ad..c9fb530 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -237,6 +237,10 @@ interface IPackageManager {
int getPreferredActivities(out List<IntentFilter> outFilters,
out List<ComponentName> outActivities, String packageName);
+ void addPersistentPreferredActivity(in IntentFilter filter, in ComponentName activity, int userId);
+
+ void clearPackagePersistentPreferredActivities(String packageName, int userId);
+
/**
* Report the set of 'Home' activity candidates, plus (if any) which of them
* is the current "always use this one" setting.
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 785f2b4..ef0c4d5 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -209,6 +209,19 @@ public class PackageInfo implements Parcelable {
*/
public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2;
/**
+ * Flag for {@link #requiredForProfile}
+ * The application will always be installed for a restricted profile.
+ * @hide
+ */
+ public static final int RESTRICTED_PROFILE = 1;
+ /**
+ * Flag for {@link #requiredForProfile}
+ * The application will always be installed for a managed profile.
+ * @hide
+ */
+ public static final int MANAGED_PROFILE = 2;
+
+ /**
* The install location requested by the activity. From the
* {@link android.R.attr#installLocation} attribute, one of
* {@link #INSTALL_LOCATION_AUTO},
@@ -218,6 +231,12 @@ public class PackageInfo implements Parcelable {
*/
public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY;
+ /**
+ * Defines which profiles this app is required for.
+ * @hide
+ */
+ public int requiredForProfile;
+
/** @hide */
public boolean requiredForAllUsers;
@@ -276,6 +295,7 @@ public class PackageInfo implements Parcelable {
dest.writeTypedArray(reqFeatures, parcelableFlags);
dest.writeInt(installLocation);
dest.writeInt(requiredForAllUsers ? 1 : 0);
+ dest.writeInt(requiredForProfile);
dest.writeString(restrictedAccountType);
dest.writeString(requiredAccountType);
dest.writeString(overlayTarget);
@@ -318,6 +338,7 @@ public class PackageInfo implements Parcelable {
reqFeatures = source.createTypedArray(FeatureInfo.CREATOR);
installLocation = source.readInt();
requiredForAllUsers = source.readInt() != 0;
+ requiredForProfile = source.readInt();
restrictedAccountType = source.readString();
requiredAccountType = source.readString();
overlayTarget = source.readString();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6440f0b..2facef6 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/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index a07ec1a..f76aada 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -306,6 +306,7 @@ public class PackageParser {
if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0
|| (pi.applicationInfo.flags&ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
pi.requiredForAllUsers = p.mRequiredForAllUsers;
+ pi.requiredForProfile = p.mRequiredForProfile;
}
pi.restrictedAccountType = p.mRestrictedAccountType;
pi.requiredAccountType = p.mRequiredAccountType;
@@ -988,7 +989,7 @@ public class PackageParser {
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifest);
- pkg.mVersionCode = sa.getInteger(
+ pkg.mVersionCode = pkg.applicationInfo.versionCode = sa.getInteger(
com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
pkg.mVersionName = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifest_versionName, 0);
@@ -1986,6 +1987,8 @@ public class PackageParser {
false)) {
owner.mRequiredForAllUsers = true;
}
+ owner.mRequiredForProfile = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestApplication_requiredForProfile, 0);
String restrictedAccountType = sa.getString(com.android.internal.R.styleable
.AndroidManifestApplication_restrictedAccountType);
@@ -3586,6 +3589,9 @@ public class PackageParser {
/* An app that's required for all users and cannot be uninstalled for a user */
public boolean mRequiredForAllUsers;
+ /* For which types of profile this app is required */
+ public int mRequiredForProfile;
+
/* The restricted account authenticator type that is used by this application */
public String mRestrictedAccountType;
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 4c87830..6f1d4f8 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -18,6 +18,7 @@ package android.content.pm;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemProperties;
import android.os.UserHandle;
/**
@@ -63,6 +64,15 @@ public class UserInfo implements Parcelable {
*/
public static final int FLAG_INITIALIZED = 0x00000010;
+ /**
+ * Indicates that this user is a profile of another user, for example holding a users
+ * corporate data.
+ */
+ public static final int FLAG_MANAGED_PROFILE = 0x00000020;
+
+
+ public static final int NO_RELATED_GROUP_ID = -1;
+
public int id;
public int serialNumber;
public String name;
@@ -70,6 +80,7 @@ public class UserInfo implements Parcelable {
public int flags;
public long creationTime;
public long lastLoggedInTime;
+ public int relatedGroupId;
/** User is only partially created. */
public boolean partial;
@@ -83,6 +94,7 @@ public class UserInfo implements Parcelable {
this.name = name;
this.flags = flags;
this.iconPath = iconPath;
+ this.relatedGroupId = NO_RELATED_GROUP_ID;
}
public boolean isPrimary() {
@@ -101,6 +113,18 @@ public class UserInfo implements Parcelable {
return (flags & FLAG_RESTRICTED) == FLAG_RESTRICTED;
}
+ public boolean isManagedProfile() {
+ return (flags & FLAG_MANAGED_PROFILE) == FLAG_MANAGED_PROFILE;
+ }
+
+ /**
+ * @return true if this user can be switched to.
+ **/
+ public boolean supportsSwitchTo() {
+ // TODO remove fw.show_hidden_users when we have finished developing managed profiles.
+ return !isManagedProfile() || SystemProperties.getBoolean("fw.show_hidden_users", false);
+ }
+
public UserInfo() {
}
@@ -113,6 +137,7 @@ public class UserInfo implements Parcelable {
creationTime = orig.creationTime;
lastLoggedInTime = orig.lastLoggedInTime;
partial = orig.partial;
+ relatedGroupId = orig.relatedGroupId;
}
public UserHandle getUserHandle() {
@@ -137,6 +162,7 @@ public class UserInfo implements Parcelable {
dest.writeLong(creationTime);
dest.writeLong(lastLoggedInTime);
dest.writeInt(partial ? 1 : 0);
+ dest.writeInt(relatedGroupId);
}
public static final Parcelable.Creator<UserInfo> CREATOR
@@ -158,5 +184,6 @@ public class UserInfo implements Parcelable {
creationTime = source.readLong();
lastLoggedInTime = source.readLong();
partial = source.readInt() != 0;
+ relatedGroupId = source.readInt();
}
}
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 9ce17e4..a41b4f9 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;
@@ -536,6 +535,9 @@ public final class AssetManager {
}
public final class AssetInputStream extends InputStream {
+ /**
+ * @hide
+ */
public final int getAssetInt() {
throw new UnsupportedOperationException();
}
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index bd23db4..419abf2 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -16,12 +16,15 @@
package android.content.res;
+import android.graphics.Color;
+
import com.android.internal.util.ArrayUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.util.AttributeSet;
+import android.util.MathUtils;
import android.util.SparseArray;
import android.util.StateSet;
import android.util.Xml;
@@ -171,7 +174,7 @@ public class ColorStateList implements Parcelable {
* Fill in this object based on the contents of an XML "selector" element.
*/
private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
- throws XmlPullParserException, IOException {
+ throws XmlPullParserException, IOException {
int type;
@@ -194,6 +197,8 @@ public class ColorStateList implements Parcelable {
continue;
}
+ int alphaRes = 0;
+ float alpha = 1.0f;
int colorRes = 0;
int color = 0xffff0000;
boolean haveColor = false;
@@ -205,17 +210,20 @@ public class ColorStateList implements Parcelable {
for (i = 0; i < numAttrs; i++) {
final int stateResId = attrs.getAttributeNameResource(i);
if (stateResId == 0) break;
- if (stateResId == com.android.internal.R.attr.color) {
+ if (stateResId == com.android.internal.R.attr.alpha) {
+ alphaRes = attrs.getAttributeResourceValue(i, 0);
+ if (alphaRes == 0) {
+ alpha = attrs.getAttributeFloatValue(i, 1.0f);
+ }
+ } else if (stateResId == com.android.internal.R.attr.color) {
colorRes = attrs.getAttributeResourceValue(i, 0);
-
if (colorRes == 0) {
color = attrs.getAttributeIntValue(i, color);
haveColor = true;
}
} else {
stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
- ? stateResId
- : -stateResId;
+ ? stateResId : -stateResId;
}
}
stateSpec = StateSet.trimStateSet(stateSpec, j);
@@ -228,10 +236,18 @@ public class ColorStateList implements Parcelable {
+ ": <item> tag requires a 'android:color' attribute.");
}
+ if (alphaRes != 0) {
+ alpha = r.getFraction(alphaRes, 1, 1);
+ }
+
+ // Apply alpha modulation.
+ final int alphaMod = MathUtils.constrain((int) (Color.alpha(color) * alpha), 0, 255);
+ color = (color & 0xFFFFFF) | (alphaMod << 24);
+
if (listSize == 0 || stateSpec.length == 0) {
mDefaultColor = color;
}
-
+
if (listSize + 1 >= listAllocated) {
listAllocated = ArrayUtils.idealIntArraySize(listSize + 1);
@@ -259,7 +275,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.
*
@@ -289,6 +315,25 @@ public class ColorStateList implements Parcelable {
return mDefaultColor;
}
+ /**
+ * Return the states in this {@link ColorStateList}.
+ * @return the states in this {@link ColorStateList}
+ * @hide
+ */
+ public int[][] getStates() {
+ return mStateSpecs;
+ }
+
+ /**
+ * Return the colors in this {@link ColorStateList}.
+ * @return the colors in this {@link ColorStateList}
+ * @hide
+ */
+ public int[] getColors() {
+ return mColors;
+ }
+
+ @Override
public String toString() {
return "ColorStateList{" +
"mStateSpecs=" + Arrays.deepToString(mStateSpecs) +
@@ -296,14 +341,16 @@ public class ColorStateList implements Parcelable {
"mDefaultColor=" + mDefaultColor + '}';
}
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel dest, int flags) {
final int N = mStateSpecs.length;
dest.writeInt(N);
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
dest.writeIntArray(mStateSpecs[i]);
}
dest.writeIntArray(mColors);
@@ -311,17 +358,19 @@ public class ColorStateList implements Parcelable {
public static final Parcelable.Creator<ColorStateList> CREATOR =
new Parcelable.Creator<ColorStateList>() {
+ @Override
public ColorStateList[] newArray(int size) {
return new ColorStateList[size];
}
+ @Override
public ColorStateList createFromParcel(Parcel source) {
final int N = source.readInt();
- int[][] stateSpecs = new int[N][];
- for (int i=0; i<N; i++) {
+ final int[][] stateSpecs = new int[N][];
+ for (int i = 0; i < N; i++) {
stateSpecs[i] = source.createIntArray();
}
- int[] colors = source.createIntArray();
+ final int[] colors = source.createIntArray();
return new ColorStateList(stateSpecs, colors);
}
};
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 3d9daca..5c27072 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;
@@ -72,16 +71,19 @@ import libcore.icu.NativePluralRules;
*/
public class Resources {
static final String TAG = "Resources";
+
private static final boolean DEBUG_LOAD = false;
private static final boolean DEBUG_CONFIG = false;
private static final boolean DEBUG_ATTRIBUTES_CACHE = false;
private static final boolean TRACE_FOR_PRELOAD = false;
private static final boolean TRACE_FOR_MISS_PRELOAD = false;
+ private static final int LAYOUT_DIR_CONFIG = ActivityInfo.activityInfoConfigToNative(
+ ActivityInfo.CONFIG_LAYOUT_DIRECTION);
+
private static final int ID_OTHER = 0x01000004;
private static final Object sSync = new Object();
- /*package*/ static Resources mSystem = null;
// Information about preloaded resources. Note that they are not
// protected by a lock, because while preloading in zygote we are all
@@ -92,32 +94,35 @@ public class Resources {
private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists
= new LongSparseArray<ColorStateList>();
+ // Used by BridgeResources in layoutlib
+ static Resources mSystem = null;
+
private static boolean sPreloaded;
private static int sPreloadedDensity;
// These are protected by mAccessLock.
+ private final Object mAccessLock = new Object();
+ private final Configuration mTmpConfig = new Configuration();
+ private final LongSparseArray<WeakReference<Drawable.ConstantState>> mDrawableCache
+ = new LongSparseArray<WeakReference<Drawable.ConstantState>>(0);
+ private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache
+ = new LongSparseArray<WeakReference<ColorStateList>>(0);
+ private final LongSparseArray<WeakReference<Drawable.ConstantState>> mColorDrawableCache
+ = new LongSparseArray<WeakReference<Drawable.ConstantState>>(0);
- /*package*/ final Object mAccessLock = new Object();
- /*package*/ final Configuration mTmpConfig = new Configuration();
- /*package*/ TypedValue mTmpValue = new TypedValue();
- /*package*/ final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
- = new LongSparseArray<WeakReference<Drawable.ConstantState> >(0);
- /*package*/ final LongSparseArray<WeakReference<ColorStateList> > mColorStateListCache
- = new LongSparseArray<WeakReference<ColorStateList> >(0);
- /*package*/ final LongSparseArray<WeakReference<Drawable.ConstantState> > mColorDrawableCache
- = new LongSparseArray<WeakReference<Drawable.ConstantState> >(0);
- /*package*/ boolean mPreloading;
+ private TypedValue mTmpValue = new TypedValue();
+ private boolean mPreloading;
- /*package*/ TypedArray mCachedStyledAttributes = null;
- RuntimeException mLastRetrievedAttrs = null;
+ private TypedArray mCachedStyledAttributes = null;
+ private RuntimeException mLastRetrievedAttrs = null;
private int mLastCachedXmlBlockIndex = -1;
private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 };
private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4];
- /*package*/ final AssetManager mAssets;
+ private final AssetManager mAssets;
private final Configuration mConfiguration = new Configuration();
- /*package*/ final DisplayMetrics mMetrics = new DisplayMetrics();
+ private final DisplayMetrics mMetrics = new DisplayMetrics();
private NativePluralRules mPluralRule;
private CompatibilityInfo mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
@@ -681,12 +686,27 @@ public class Resources {
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
- *
- * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
- *
* @return Drawable An object that can be used to draw this resource.
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
*/
public Drawable getDrawable(int id) throws NotFoundException {
+ return getDrawable(id, null);
+ }
+
+ /**
+ * Return a drawable object associated with a particular resource ID and
+ * styled for the specified theme.
+ *
+ * @param id The desired resource identifier, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource
+ * entry. The value 0 is an invalid identifier.
+ * @param theme The theme used to style the drawable attributes.
+ * @return Drawable An object that can be used to draw this resource.
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ */
+ public Drawable getDrawable(int id, Theme theme) throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -697,7 +717,7 @@ public class Resources {
}
getValue(id, value, true);
}
- Drawable res = loadDrawable(value, id);
+ final Drawable res = loadDrawable(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
@@ -715,17 +735,36 @@ public class Resources {
* depending on the underlying resource -- for example, a solid color, PNG
* image, scalable image, etc. The Drawable API hides these implementation
* details.
- *
+ *
* @param id The desired resource identifier, as generated by the aapt tool.
* This integer encodes the package, type, and resource entry.
* The value 0 is an invalid identifier.
* @param density the desired screen density indicated by the resource as
* found in {@link DisplayMetrics}.
+ * @return Drawable An object that can be used to draw this resource.
* @throws NotFoundException Throws NotFoundException if the given ID does
* not exist.
- * @return Drawable An object that can be used to draw this resource.
+ * @see #getDrawableForDensity(int, int, Theme)
*/
public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
+ return getDrawableForDensity(id, density, null);
+ }
+
+ /**
+ * Return a drawable object associated with a particular resource ID for the
+ * given screen density in DPI and styled for the specified theme.
+ *
+ * @param id The desired resource identifier, as generated by the aapt tool.
+ * This integer encodes the package, type, and resource entry.
+ * The value 0 is an invalid identifier.
+ * @param density The desired screen density indicated by the resource as
+ * found in {@link DisplayMetrics}.
+ * @param theme The theme used to style the drawable attributes.
+ * @return Drawable An object that can be used to draw this resource.
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ */
+ public Drawable getDrawableForDensity(int id, int density, Theme theme) {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -752,7 +791,7 @@ public class Resources {
}
}
- Drawable res = loadDrawable(value, id);
+ final Drawable res = loadDrawable(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
@@ -1246,8 +1285,9 @@ public class Resources {
* @see #obtainStyledAttributes(AttributeSet, int[], int, int)
*/
public TypedArray obtainStyledAttributes(int[] attrs) {
- int len = attrs.length;
- TypedArray array = getCachedStyledAttributes(len);
+ final int len = attrs.length;
+ final TypedArray array = getCachedStyledAttributes(len);
+ array.mTheme = this;
array.mRsrcs = attrs;
AssetManager.applyStyle(mTheme, 0, 0, 0, attrs,
array.mData, array.mIndices);
@@ -1274,10 +1314,10 @@ public class Resources {
* @see #obtainStyledAttributes(int[])
* @see #obtainStyledAttributes(AttributeSet, int[], int, int)
*/
- public TypedArray obtainStyledAttributes(int resid, int[] attrs)
- throws NotFoundException {
- int len = attrs.length;
- TypedArray array = getCachedStyledAttributes(len);
+ public TypedArray obtainStyledAttributes(int resid, int[] attrs) throws NotFoundException {
+ final int len = attrs.length;
+ final TypedArray array = getCachedStyledAttributes(len);
+ array.mTheme = this;
array.mRsrcs = attrs;
AssetManager.applyStyle(mTheme, 0, resid, 0, attrs,
@@ -1361,19 +1401,18 @@ public class Resources {
*/
public TypedArray obtainStyledAttributes(AttributeSet set,
int[] attrs, int defStyleAttr, int defStyleRes) {
- int len = attrs.length;
- TypedArray array = getCachedStyledAttributes(len);
+ final int len = attrs.length;
+ final TypedArray array = getCachedStyledAttributes(len);
// XXX note that for now we only work with compiled XML files.
// To support generic XML files we will need to manually parse
// out the attributes from the XML file (applying type information
// contained in the resources and such).
- XmlBlock.Parser parser = (XmlBlock.Parser)set;
- AssetManager.applyStyle(
- mTheme, defStyleAttr, defStyleRes,
- parser != null ? parser.mParseState : 0, attrs,
- array.mData, array.mIndices);
+ final XmlBlock.Parser parser = (XmlBlock.Parser)set;
+ AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
+ parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices);
+ array.mTheme = this;
array.mRsrcs = attrs;
array.mXml = parser;
@@ -1439,6 +1478,21 @@ public class Resources {
}
/**
+ * Return a drawable object associated with a particular resource ID
+ * and styled for the Theme.
+ *
+ * @param id The desired resource identifier, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource
+ * entry. The value 0 is an invalid identifier.
+ * @return Drawable An object that can be used to draw this resource.
+ * @throws NotFoundException Throws NotFoundException if the given ID
+ * does not exist.
+ */
+ public Drawable getDrawable(int id) throws NotFoundException {
+ return Resources.this.getDrawable(id, this);
+ }
+
+ /**
* Print contents of this theme out to the log. For debugging only.
*
* @param priority The log priority to use.
@@ -1448,7 +1502,8 @@ public class Resources {
public void dump(int priority, String tag, String prefix) {
AssetManager.dumpTheme(mTheme, priority, tag, prefix);
}
-
+
+ @Override
protected void finalize() throws Throwable {
super.finalize();
mAssets.releaseTheme(mTheme);
@@ -1459,6 +1514,7 @@ public class Resources {
mTheme = mAssets.createTheme();
}
+ @SuppressWarnings("hiding")
private final AssetManager mAssets;
private final long mTheme;
}
@@ -1569,7 +1625,7 @@ public class Resources {
String locale = null;
if (mConfiguration.locale != null) {
- locale = mConfiguration.locale.toLanguageTag();
+ locale = adjustLanguageTag(localeToLanguageTag(mConfiguration.locale));
}
int width, height;
if (mMetrics.widthPixels >= mMetrics.heightPixels) {
@@ -1650,6 +1706,47 @@ public class Resources {
}
}
+ // Locale.toLanguageTag() is not available in Java6. LayoutLib overrides
+ // this method to enable users to use Java6.
+ private String localeToLanguageTag(Locale locale) {
+ return locale.toLanguageTag();
+ }
+
+ /**
+ * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated)
+ * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively.
+ *
+ * All released versions of android prior to "L" used the deprecated language
+ * tags, so we will need to support them for backwards compatibility.
+ *
+ * Note that this conversion needs to take place *after* the call to
+ * {@code toLanguageTag} because that will convert all the deprecated codes to
+ * the new ones, even if they're set manually.
+ */
+ private static String adjustLanguageTag(String languageTag) {
+ final int separator = languageTag.indexOf('-');
+ final String language;
+ final String remainder;
+
+ if (separator == -1) {
+ language = languageTag;
+ remainder = "";
+ } else {
+ language = languageTag.substring(0, separator);
+ remainder = languageTag.substring(separator);
+ }
+
+ if ("id".equals(language)) {
+ return "in" + remainder;
+ } else if ("yi".equals(language)) {
+ return "ji" + remainder;
+ } else if ("he".equals(language)) {
+ return "iw" + remainder;
+ } else {
+ return languageTag;
+ }
+ }
+
/**
* Update the system resources configuration if they have previously
* been initialized.
@@ -2020,17 +2117,14 @@ public class Resources {
return true;
}
- static private final int LAYOUT_DIR_CONFIG = ActivityInfo.activityInfoConfigToNative(
- ActivityInfo.CONFIG_LAYOUT_DIRECTION);
-
- /*package*/ Drawable loadDrawable(TypedValue value, int id)
- throws NotFoundException {
-
+ /*package*/ Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
if ((id >>> 24) == 0x1) {
final String name = getResourceName(id);
- if (name != null) android.util.Log.d("PreloadDrawable", name);
+ if (name != null) {
+ Log.d("PreloadDrawable", name);
+ }
}
}
@@ -2235,12 +2329,12 @@ public class Resources {
"Resource is not a ColorStateList (color or path): " + value);
}
- String file = value.string.toString();
+ final String file = value.string.toString();
if (file.endsWith(".xml")) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
try {
- XmlResourceParser rp = loadXmlResourceParser(
+ final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "colorstatelist");
csl = ColorStateList.createFromXml(this, rp);
rp.close();
@@ -2363,6 +2457,15 @@ public class Resources {
+ Integer.toHexString(id));
}
+ /*package*/ void recycleCachedStyledAttributes(TypedArray attrs) {
+ synchronized (mAccessLock) {
+ final TypedArray cached = mCachedStyledAttributes;
+ if (cached == null || cached.mData.length < attrs.mData.length) {
+ mCachedStyledAttributes = attrs;
+ }
+ }
+ }
+
private TypedArray getCachedStyledAttributes(int len) {
synchronized (mAccessLock) {
TypedArray attrs = mCachedStyledAttributes;
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 83d48aa..4858d08 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;
@@ -38,13 +37,16 @@ import java.util.Arrays;
*/
public class TypedArray {
private final Resources mResources;
+ private final DisplayMetrics mMetrics;
+ private final AssetManager mAssets;
/*package*/ XmlBlock.Parser mXml;
/*package*/ int[] mRsrcs;
/*package*/ int[] mData;
/*package*/ int[] mIndices;
/*package*/ int mLength;
/*package*/ TypedValue mValue = new TypedValue();
-
+ /*package*/ Resources.Theme mTheme;
+
/**
* Return the number of values in this array.
*/
@@ -393,7 +395,7 @@ public class TypedArray {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(
- data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
+ data[index+AssetManager.STYLE_DATA], mMetrics);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
@@ -425,7 +427,7 @@ public class TypedArray {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelOffset(
- data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
+ data[index+AssetManager.STYLE_DATA], mMetrics);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
@@ -458,7 +460,7 @@ public class TypedArray {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(
- data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
+ data[index+AssetManager.STYLE_DATA], mMetrics);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
@@ -486,7 +488,7 @@ public class TypedArray {
return data[index+AssetManager.STYLE_DATA];
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(
- data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
+ data[index+AssetManager.STYLE_DATA], mMetrics);
}
throw new RuntimeException(getPositionDescription()
@@ -515,7 +517,7 @@ public class TypedArray {
return data[index+AssetManager.STYLE_DATA];
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(
- data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
+ data[index+AssetManager.STYLE_DATA], mMetrics);
}
return defValue;
@@ -599,7 +601,7 @@ public class TypedArray {
+ " cookie=" + value.assetCookie);
System.out.println("******************************************************************");
}
- return mResources.loadDrawable(value, value.resourceId);
+ return mResources.loadDrawable(value, value.resourceId, mTheme);
}
return null;
}
@@ -688,13 +690,11 @@ public class TypedArray {
* Give back a previously retrieved array, for later re-use.
*/
public void recycle() {
- synchronized (mResources.mAccessLock) {
- TypedArray cached = mResources.mCachedStyledAttributes;
- if (cached == null || cached.mData.length < mData.length) {
- mXml = null;
- mResources.mCachedStyledAttributes = this;
- }
- }
+ mResources.recycleCachedStyledAttributes(this);
+
+ mXml = null;
+ mRsrcs = null;
+ mTheme = null;
}
private boolean getValueAt(int index, TypedValue outValue) {
@@ -723,18 +723,19 @@ public class TypedArray {
}
return null;
}
- //System.out.println("Getting pooled from: " + v);
- return mResources.mAssets.getPooledString(
- cookie, data[index+AssetManager.STYLE_DATA]);
+ return mAssets.getPooledString(cookie, data[index+AssetManager.STYLE_DATA]);
}
/*package*/ TypedArray(Resources resources, int[] data, int[] indices, int len) {
mResources = resources;
+ mMetrics = mResources.getDisplayMetrics();
+ mAssets = mResources.getAssets();
mData = data;
mIndices = indices;
mLength = len;
}
+ @Override
public String toString() {
return Arrays.toString(mData);
}
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 111062d..6e2a099 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/GeomagneticField.java b/core/java/android/hardware/GeomagneticField.java
index 0369825..ef05732 100644
--- a/core/java/android/hardware/GeomagneticField.java
+++ b/core/java/android/hardware/GeomagneticField.java
@@ -361,7 +361,7 @@ public class GeomagneticField {
mP[0] = new float[] { 1.0f };
mPDeriv[0] = new float[] { 0.0f };
for (int n = 1; n <= maxN; n++) {
- mP[n] = new float[n + 1];
+ mP[n] = new float[n + 1];
mPDeriv[n] = new float[n + 1];
for (int m = 0; m <= n; m++) {
if (n == m) {
diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl
index 542af6a..4c50dda 100644
--- a/core/java/android/hardware/ICameraService.aidl
+++ b/core/java/android/hardware/ICameraService.aidl
@@ -61,4 +61,12 @@ interface ICameraService
int removeListener(ICameraServiceListener listener);
int getCameraCharacteristics(int cameraId, out CameraMetadataNative info);
+
+ /**
+ * The java stubs for this method are not intended to be used. Please use
+ * the native stub in frameworks/av/include/camera/ICameraService.h instead.
+ * The BinderHolder output is being used as a placeholder, and will not be
+ * well-formatted in the generated java method.
+ */
+ int getCameraVendorTagDescriptor(out BinderHolder desc);
}
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/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a38beec..d27485b 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -126,206 +126,279 @@ public final class CameraCharacteristics extends CameraMetadata {
* modify the comment blocks at the start or end.
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/
+
/**
- * <p>
- * Which set of antibanding modes are
- * supported
- * </p>
+ * <p>The set of auto-exposure antibanding modes that are
+ * supported by this camera device.</p>
+ * <p>Not all of the auto-exposure anti-banding modes may be
+ * supported by a given camera device. This field lists the
+ * valid anti-banding modes that the application may request
+ * for this camera device; they must include AUTO.</p>
*/
public static final Key<byte[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES =
new Key<byte[]>("android.control.aeAvailableAntibandingModes", byte[].class);
/**
- * <p>
- * List of frame rate ranges supported by the
- * AE algorithm/hardware
- * </p>
+ * <p>The set of auto-exposure modes that are supported by this
+ * camera device.</p>
+ * <p>Not all the auto-exposure modes may be supported by a
+ * given camera device, especially if no flash unit is
+ * available. This entry lists the valid modes for
+ * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} for this camera device.</p>
+ * <p>All camera devices support ON, and all camera devices with
+ * flash units support ON_AUTO_FLASH and
+ * ON_ALWAYS_FLASH.</p>
+ * <p>Full-capability camera devices always support OFF mode,
+ * which enables application control of camera exposure time,
+ * sensitivity, and frame duration.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_MODE
+ */
+ public static final Key<byte[]> CONTROL_AE_AVAILABLE_MODES =
+ new Key<byte[]>("android.control.aeAvailableModes", byte[].class);
+
+ /**
+ * <p>List of frame rate ranges supported by the
+ * AE algorithm/hardware</p>
*/
public static final Key<int[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES =
new Key<int[]>("android.control.aeAvailableTargetFpsRanges", int[].class);
/**
- * <p>
- * Maximum and minimum exposure compensation
+ * <p>Maximum and minimum exposure compensation
* setting, in counts of
- * android.control.aeCompensationStepSize
- * </p>
+ * {@link CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP android.control.aeCompensationStep}.</p>
+ *
+ * @see CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP
*/
public static final Key<int[]> CONTROL_AE_COMPENSATION_RANGE =
new Key<int[]>("android.control.aeCompensationRange", int[].class);
/**
- * <p>
- * Smallest step by which exposure compensation
- * can be changed
- * </p>
+ * <p>Smallest step by which exposure compensation
+ * can be changed</p>
*/
public static final Key<Rational> CONTROL_AE_COMPENSATION_STEP =
new Key<Rational>("android.control.aeCompensationStep", Rational.class);
/**
- * <p>
- * List of AF modes that can be
- * selected
- * </p>
+ * <p>List of AF modes that can be
+ * selected with {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}.</p>
+ * <p>Not all the auto-focus modes may be supported by a
+ * given camera device. This entry lists the valid modes for
+ * {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} for this camera device.</p>
+ * <p>All camera devices will support OFF mode, and all camera devices with
+ * adjustable focuser units (<code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} &gt; 0</code>)
+ * will support AUTO mode.</p>
+ *
+ * @see CaptureRequest#CONTROL_AF_MODE
+ * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE
*/
public static final Key<byte[]> CONTROL_AF_AVAILABLE_MODES =
new Key<byte[]>("android.control.afAvailableModes", byte[].class);
/**
- * <p>
- * what subset of the full color effect enum
- * list is supported
- * </p>
+ * <p>List containing the subset of color effects
+ * specified in {@link CaptureRequest#CONTROL_EFFECT_MODE android.control.effectMode} that is supported by
+ * this device.</p>
+ * <p>This list contains the color effect modes that can be applied to
+ * images produced by the camera device. Only modes that have
+ * been fully implemented for the current device may be included here.
+ * Implementations are not expected to be consistent across all devices.
+ * If no color effect modes are available for a device, this should
+ * simply be set to OFF.</p>
+ * <p>A color effect will only be applied if
+ * {@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF.</p>
+ *
+ * @see CaptureRequest#CONTROL_EFFECT_MODE
+ * @see CaptureRequest#CONTROL_MODE
*/
public static final Key<byte[]> CONTROL_AVAILABLE_EFFECTS =
new Key<byte[]>("android.control.availableEffects", byte[].class);
/**
- * <p>
- * what subset of the scene mode enum list is
- * supported.
- * </p>
+ * <p>List containing a subset of scene modes
+ * specified in {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode}.</p>
+ * <p>This list contains scene modes that can be set for the camera device.
+ * Only scene modes that have been fully implemented for the
+ * camera device may be included here. Implementations are not expected
+ * to be consistent across all devices. If no scene modes are supported
+ * by the camera device, this will be set to <code>[DISABLED]</code>.</p>
+ *
+ * @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final Key<byte[]> CONTROL_AVAILABLE_SCENE_MODES =
new Key<byte[]>("android.control.availableSceneModes", byte[].class);
/**
- * <p>
- * List of video stabilization modes that can
- * be supported
- * </p>
+ * <p>List of video stabilization modes that can
+ * be supported</p>
*/
public static final Key<byte[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES =
new Key<byte[]>("android.control.availableVideoStabilizationModes", byte[].class);
/**
+ * <p>The set of auto-white-balance modes ({@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode})
+ * that are supported by this camera device.</p>
+ * <p>Not all the auto-white-balance modes may be supported by a
+ * given camera device. This entry lists the valid modes for
+ * {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} for this camera device.</p>
+ * <p>All camera devices will support ON mode.</p>
+ * <p>Full-capability camera devices will always support OFF mode,
+ * which enables application control of white balance, by using
+ * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform} and {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains}({@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} must be set to TRANSFORM_MATRIX).</p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_GAINS
+ * @see CaptureRequest#COLOR_CORRECTION_MODE
+ * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
+ * @see CaptureRequest#CONTROL_AWB_MODE
*/
public static final Key<byte[]> CONTROL_AWB_AVAILABLE_MODES =
new Key<byte[]>("android.control.awbAvailableModes", byte[].class);
/**
- * <p>
- * For AE, AWB, and AF, how many individual
- * regions can be listed for metering?
- * </p>
+ * <p>List of the maximum number of regions that can be used for metering in
+ * auto-exposure (AE), auto-white balance (AWB), and auto-focus (AF);
+ * this corresponds to the the maximum number of elements in
+ * {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}, {@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions},
+ * and {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_REGIONS
+ * @see CaptureRequest#CONTROL_AF_REGIONS
+ * @see CaptureRequest#CONTROL_AWB_REGIONS
*/
- public static final Key<Integer> CONTROL_MAX_REGIONS =
- new Key<Integer>("android.control.maxRegions", int.class);
+ public static final Key<int[]> CONTROL_MAX_REGIONS =
+ new Key<int[]>("android.control.maxRegions", int[].class);
/**
- * <p>
- * Whether this camera has a
- * flash
- * </p>
- * <p>
- * If no flash, none of the flash controls do
- * anything. All other metadata should return 0
- * </p>
+ * <p>Whether this camera device has a
+ * flash.</p>
+ * <p>If no flash, none of the flash controls do
+ * anything. All other metadata should return 0.</p>
*/
- public static final Key<Byte> FLASH_INFO_AVAILABLE =
- new Key<Byte>("android.flash.info.available", byte.class);
+ public static final Key<Boolean> FLASH_INFO_AVAILABLE =
+ new Key<Boolean>("android.flash.info.available", boolean.class);
/**
- * <p>
- * Supported resolutions for the JPEG
- * thumbnail
- * </p>
+ * <p>Supported resolutions for the JPEG thumbnail</p>
+ * <p>Below condiditions will be satisfied for this size list:</p>
+ * <ul>
+ * <li>The sizes will be sorted by increasing pixel area (width x height).
+ * If several resolutions have the same area, they will be sorted by increasing width.</li>
+ * <li>The aspect ratio of the largest thumbnail size will be same as the
+ * aspect ratio of largest JPEG output size in {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS android.scaler.availableStreamConfigurations}.
+ * The largest size is defined as the size that has the largest pixel area
+ * in a given size list.</li>
+ * <li>Each output JPEG size in {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS android.scaler.availableStreamConfigurations} will have at least
+ * one corresponding size that has the same aspect ratio in availableThumbnailSizes,
+ * and vice versa.</li>
+ * <li>All non (0, 0) sizes will have non-zero widths and heights.</li>
+ * </ul>
+ *
+ * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS
*/
public static final Key<android.hardware.camera2.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES =
new Key<android.hardware.camera2.Size[]>("android.jpeg.availableThumbnailSizes", android.hardware.camera2.Size[].class);
/**
- * <p>
- * List of supported aperture
- * values
- * </p>
- * <p>
- * If variable aperture not available, only setting
- * should be for the fixed aperture
- * </p>
+ * <p>List of supported aperture
+ * values.</p>
+ * <p>If the camera device doesn't support variable apertures,
+ * listed value will be the fixed aperture.</p>
+ * <p>If the camera device supports variable apertures, the aperture value
+ * in this list will be sorted in ascending order.</p>
*/
public static final Key<float[]> LENS_INFO_AVAILABLE_APERTURES =
new Key<float[]>("android.lens.info.availableApertures", float[].class);
/**
- * <p>
- * List of supported ND filter
- * values
- * </p>
- * <p>
- * If not available, only setting is 0. Otherwise,
- * lists the available exposure index values for dimming
- * (2 would mean the filter is set to reduce incoming
- * light by two stops)
- * </p>
+ * <p>List of supported neutral density filter values for
+ * {@link CaptureRequest#LENS_FILTER_DENSITY android.lens.filterDensity}.</p>
+ * <p>If changing {@link CaptureRequest#LENS_FILTER_DENSITY android.lens.filterDensity} is not supported,
+ * availableFilterDensities must contain only 0. Otherwise, this
+ * list contains only the exact filter density values available on
+ * this camera device.</p>
+ *
+ * @see CaptureRequest#LENS_FILTER_DENSITY
*/
public static final Key<float[]> LENS_INFO_AVAILABLE_FILTER_DENSITIES =
new Key<float[]>("android.lens.info.availableFilterDensities", float[].class);
/**
- * <p>
- * If fitted with optical zoom, what focal
- * lengths are available. If not, the static focal
- * length
- * </p>
- * <p>
- * If optical zoom not supported, only one value
- * should be reported
- * </p>
+ * <p>The available focal lengths for this device for use with
+ * {@link CaptureRequest#LENS_FOCAL_LENGTH android.lens.focalLength}.</p>
+ * <p>If optical zoom is not supported, this will only report
+ * a single value corresponding to the static focal length of the
+ * device. Otherwise, this will report every focal length supported
+ * by the device.</p>
+ *
+ * @see CaptureRequest#LENS_FOCAL_LENGTH
*/
public static final Key<float[]> LENS_INFO_AVAILABLE_FOCAL_LENGTHS =
new Key<float[]>("android.lens.info.availableFocalLengths", float[].class);
/**
- * <p>
- * List of supported optical image
- * stabilization modes
- * </p>
+ * <p>List containing a subset of the optical image
+ * stabilization (OIS) modes specified in
+ * {@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}.</p>
+ * <p>If OIS is not implemented for a given camera device, this should
+ * contain only OFF.</p>
+ *
+ * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
*/
public static final Key<byte[]> LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION =
new Key<byte[]>("android.lens.info.availableOpticalStabilization", byte[].class);
/**
- * <p>
- * Hyperfocal distance for this lens; set to
- * 0 if fixed focus
- * </p>
- * <p>
- * The hyperfocal distance is used for the old
- * API's 'fixed' setting
- * </p>
+ * <p>Optional. Hyperfocal distance for this lens.</p>
+ * <p>If the lens is fixed focus, the camera device will report 0.</p>
+ * <p>If the lens is not fixed focus, the camera device will report this
+ * field when {@link CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION android.lens.info.focusDistanceCalibration} is APPROXIMATE or CALIBRATED.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION
*/
public static final Key<Float> LENS_INFO_HYPERFOCAL_DISTANCE =
new Key<Float>("android.lens.info.hyperfocalDistance", float.class);
/**
- * <p>
- * Shortest distance from frontmost surface
- * of the lens that can be focused correctly
- * </p>
- * <p>
- * If the lens is fixed-focus, this should be
- * 0
- * </p>
+ * <p>Shortest distance from frontmost surface
+ * of the lens that can be focused correctly.</p>
+ * <p>If the lens is fixed-focus, this should be
+ * 0.</p>
*/
public static final Key<Float> LENS_INFO_MINIMUM_FOCUS_DISTANCE =
new Key<Float>("android.lens.info.minimumFocusDistance", float.class);
/**
- * <p>
- * Dimensions of lens shading
- * map
- * </p>
+ * <p>Dimensions of lens shading map.</p>
+ * <p>The map should be on the order of 30-40 rows and columns, and
+ * must be smaller than 64x64.</p>
*/
public static final Key<android.hardware.camera2.Size> LENS_INFO_SHADING_MAP_SIZE =
new Key<android.hardware.camera2.Size>("android.lens.info.shadingMapSize", android.hardware.camera2.Size.class);
/**
- * <p>
- * Direction the camera faces relative to
- * device screen
- * </p>
+ * <p>The lens focus distance calibration quality.</p>
+ * <p>The lens focus distance calibration quality determines the reliability of
+ * focus related metadata entries, i.e. {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance},
+ * {@link CaptureResult#LENS_FOCUS_RANGE android.lens.focusRange}, {@link CameraCharacteristics#LENS_INFO_HYPERFOCAL_DISTANCE android.lens.info.hyperfocalDistance}, and
+ * {@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance}.</p>
+ *
+ * @see CaptureRequest#LENS_FOCUS_DISTANCE
+ * @see CaptureResult#LENS_FOCUS_RANGE
+ * @see CameraCharacteristics#LENS_INFO_HYPERFOCAL_DISTANCE
+ * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE
+ * @see #LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED
+ * @see #LENS_INFO_FOCUS_DISTANCE_CALIBRATION_APPROXIMATE
+ * @see #LENS_INFO_FOCUS_DISTANCE_CALIBRATION_CALIBRATED
+ */
+ public static final Key<Integer> LENS_INFO_FOCUS_DISTANCE_CALIBRATION =
+ new Key<Integer>("android.lens.info.focusDistanceCalibration", int.class);
+
+ /**
+ * <p>Direction the camera faces relative to
+ * device screen</p>
* @see #LENS_FACING_FRONT
* @see #LENS_FACING_BACK
*/
@@ -333,292 +406,773 @@ public final class CameraCharacteristics extends CameraMetadata {
new Key<Integer>("android.lens.facing", int.class);
/**
- * <p>
- * If set to 1, the HAL will always split result
+ * <p>If set to 1, the HAL will always split result
* metadata for a single capture into multiple buffers,
- * returned using multiple process_capture_result calls.
- * </p>
- * <p>
- * Does not need to be listed in static
+ * returned using multiple process_capture_result calls.</p>
+ * <p>Does not need to be listed in static
* metadata. Support for partial results will be reworked in
* future versions of camera service. This quirk will stop
* working at that point; DO NOT USE without careful
- * consideration of future support.
- * </p>
- *
- * <b>Optional</b> - This value may be null on some devices.
- *
+ * consideration of future support.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* @hide
*/
public static final Key<Byte> QUIRKS_USE_PARTIAL_RESULT =
new Key<Byte>("android.quirks.usePartialResult", byte.class);
/**
- * <p>
- * How many output streams can be allocated at
- * the same time for each type of stream
- * </p>
- * <p>
- * Video snapshot with preview callbacks requires 3
- * processed streams (preview, record, app callbacks) and
- * one JPEG stream (snapshot)
- * </p>
+ * <p>The maximum numbers of different types of output streams
+ * that can be configured and used simultaneously by a camera device.</p>
+ * <p>This is a 3 element tuple that contains the max number of output simultaneous
+ * streams for raw sensor, processed (and uncompressed), and JPEG formats respectively.
+ * For example, if max raw sensor format output stream number is 1, max YUV streams
+ * number is 3, and max JPEG stream number is 2, then this tuple should be <code>(1, 3, 2)</code>.</p>
+ * <p>This lists the upper bound of the number of output streams supported by
+ * the camera device. Using more streams simultaneously may require more hardware and
+ * CPU resources that will consume more power. The image format for a output stream can
+ * be any supported format provided by {@link CameraCharacteristics#SCALER_AVAILABLE_FORMATS android.scaler.availableFormats}. The formats
+ * defined in {@link CameraCharacteristics#SCALER_AVAILABLE_FORMATS android.scaler.availableFormats} can be catergorized into the 3 stream types
+ * as below:</p>
+ * <ul>
+ * <li>JPEG-compressed format: BLOB.</li>
+ * <li>Raw formats: RAW_SENSOR and RAW_OPAQUE.</li>
+ * <li>processed, uncompressed formats: YCbCr_420_888, YCrCb_420_SP, YV12.</li>
+ * </ul>
+ *
+ * @see CameraCharacteristics#SCALER_AVAILABLE_FORMATS
*/
public static final Key<int[]> REQUEST_MAX_NUM_OUTPUT_STREAMS =
new Key<int[]>("android.request.maxNumOutputStreams", int[].class);
/**
- * <p>
- * List of app-visible formats
- * </p>
+ * <p>The maximum numbers of any type of input streams
+ * that can be configured and used simultaneously by a camera device.</p>
+ * <p>When set to 0, it means no input stream is supported.</p>
+ * <p>The image format for a input stream can be any supported
+ * format provided by
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP android.scaler.availableInputOutputFormatsMap}. When using an
+ * input stream, there must be at least one output stream
+ * configured to to receive the reprocessed images.</p>
+ * <p>For example, for Zero Shutter Lag (ZSL) still capture use case, the input
+ * stream image format will be RAW_OPAQUE, the associated output stream image format
+ * should be JPEG.</p>
+ *
+ * @see CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP
+ */
+ public static final Key<Integer> REQUEST_MAX_NUM_INPUT_STREAMS =
+ new Key<Integer>("android.request.maxNumInputStreams", int.class);
+
+ /**
+ * <p>Specifies the number of maximum pipeline stages a frame
+ * has to go through from when it's exposed to when it's available
+ * to the framework.</p>
+ * <p>A typical minimum value for this is 2 (one stage to expose,
+ * one stage to readout) from the sensor. The ISP then usually adds
+ * its own stages to do custom HW processing. Further stages may be
+ * added by SW processing.</p>
+ * <p>Depending on what settings are used (e.g. YUV, JPEG) and what
+ * processing is enabled (e.g. face detection), the actual pipeline
+ * depth (specified by {@link CaptureResult#REQUEST_PIPELINE_DEPTH android.request.pipelineDepth}) may be less than
+ * the max pipeline depth.</p>
+ * <p>A pipeline depth of X stages is equivalent to a pipeline latency of
+ * X frame intervals.</p>
+ * <p>This value will be 8 or less.</p>
+ *
+ * @see CaptureResult#REQUEST_PIPELINE_DEPTH
+ */
+ public static final Key<Byte> REQUEST_PIPELINE_MAX_DEPTH =
+ new Key<Byte>("android.request.pipelineMaxDepth", byte.class);
+
+ /**
+ * <p>Optional. Defaults to 1. Defines how many sub-components
+ * a result will be composed of.</p>
+ * <p>In order to combat the pipeline latency, partial results
+ * may be delivered to the application layer from the camera device as
+ * soon as they are available.</p>
+ * <p>A value of 1 means that partial results are not supported.</p>
+ * <p>A typical use case for this might be: after requesting an AF lock the
+ * new AF state might be available 50% of the way through the pipeline.
+ * The camera device could then immediately dispatch this state via a
+ * partial result to the framework/application layer, and the rest of
+ * the metadata via later partial results.</p>
+ */
+ public static final Key<Integer> REQUEST_PARTIAL_RESULT_COUNT =
+ new Key<Integer>("android.request.partialResultCount", int.class);
+
+ /**
+ * <p>List of capabilities that the camera device
+ * advertises as fully supporting.</p>
+ * <p>A capability is a contract that the camera device makes in order
+ * to be able to satisfy one or more use cases.</p>
+ * <p>Listing a capability guarantees that the whole set of features
+ * required to support a common use will all be available.</p>
+ * <p>Using a subset of the functionality provided by an unsupported
+ * capability may be possible on a specific camera device implementation;
+ * to do this query each of android.request.availableRequestKeys,
+ * android.request.availableResultKeys,
+ * android.request.availableCharacteristicsKeys.</p>
+ * <p>XX: Maybe these should go into {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel}
+ * as a table instead?</p>
+ * <p>The following capabilities are guaranteed to be available on
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} <code>==</code> FULL devices:</p>
+ * <ul>
+ * <li>MANUAL_SENSOR</li>
+ * <li>ZSL</li>
+ * </ul>
+ * <p>Other capabilities may be available on either FULL or LIMITED
+ * devices, but the app. should query this field to be sure.</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_OPTIONAL
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_GCAM
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_ZSL
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_DNG
+ */
+ public static final Key<Integer> REQUEST_AVAILABLE_CAPABILITIES =
+ new Key<Integer>("android.request.availableCapabilities", int.class);
+
+ /**
+ * <p>A list of all keys that the camera device has available
+ * to use with CaptureRequest.</p>
+ * <p>Attempting to set a key into a CaptureRequest that is not
+ * listed here will result in an invalid request and will be rejected
+ * by the camera device.</p>
+ * <p>This field can be used to query the feature set of a camera device
+ * at a more granular level than capabilities. This is especially
+ * important for optional keys that are not listed under any capability
+ * in {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}.</p>
+ * <p>TODO: This should be used by #getAvailableCaptureRequestKeys.</p>
+ *
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ * @hide
+ */
+ public static final Key<int[]> REQUEST_AVAILABLE_REQUEST_KEYS =
+ new Key<int[]>("android.request.availableRequestKeys", int[].class);
+
+ /**
+ * <p>A list of all keys that the camera device has available
+ * to use with CaptureResult.</p>
+ * <p>Attempting to get a key from a CaptureResult that is not
+ * listed here will always return a <code>null</code> value. Getting a key from
+ * a CaptureResult that is listed here must never return a <code>null</code>
+ * value.</p>
+ * <p>The following keys may return <code>null</code> unless they are enabled:</p>
+ * <ul>
+ * <li>{@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} (non-null iff {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON)</li>
+ * </ul>
+ * <p>(Those sometimes-null keys should nevertheless be listed here
+ * if they are available.)</p>
+ * <p>This field can be used to query the feature set of a camera device
+ * at a more granular level than capabilities. This is especially
+ * important for optional keys that are not listed under any capability
+ * in {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}.</p>
+ * <p>TODO: This should be used by #getAvailableCaptureResultKeys.</p>
+ *
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
+ * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
+ * @hide
+ */
+ public static final Key<int[]> REQUEST_AVAILABLE_RESULT_KEYS =
+ new Key<int[]>("android.request.availableResultKeys", int[].class);
+
+ /**
+ * <p>A list of all keys that the camera device has available
+ * to use with CameraCharacteristics.</p>
+ * <p>This entry follows the same rules as
+ * android.request.availableResultKeys (except that it applies for
+ * CameraCharacteristics instead of CaptureResult). See above for more
+ * details.</p>
+ * <p>TODO: This should be used by CameraCharacteristics#getKeys.</p>
+ * @hide
+ */
+ public static final Key<int[]> REQUEST_AVAILABLE_CHARACTERISTICS_KEYS =
+ new Key<int[]>("android.request.availableCharacteristicsKeys", int[].class);
+
+ /**
+ * <p>The list of image formats that are supported by this
+ * camera device for output streams.</p>
+ * <p>All camera devices will support JPEG and YUV_420_888 formats.</p>
+ * <p>When set to YUV_420_888, application can access the YUV420 data directly.</p>
*/
public static final Key<int[]> SCALER_AVAILABLE_FORMATS =
new Key<int[]>("android.scaler.availableFormats", int[].class);
/**
- * <p>
- * The minimum frame duration that is supported
- * for each resolution in availableJpegSizes. Should
- * correspond to the frame duration when only that JPEG
- * stream is active and captured in a burst, with all
- * processing set to FAST
- * </p>
- * <p>
- * When multiple streams are configured, the minimum
- * frame duration will be >= max(individual stream min
- * durations)
- * </p>
+ * <p>The minimum frame duration that is supported
+ * for each resolution in {@link CameraCharacteristics#SCALER_AVAILABLE_JPEG_SIZES android.scaler.availableJpegSizes}.</p>
+ * <p>This corresponds to the minimum steady-state frame duration when only
+ * that JPEG stream is active and captured in a burst, with all
+ * processing (typically in android.*.mode) set to FAST.</p>
+ * <p>When multiple streams are configured, the minimum
+ * frame duration will be &gt;= max(individual stream min
+ * durations)</p>
+ *
+ * @see CameraCharacteristics#SCALER_AVAILABLE_JPEG_SIZES
*/
public static final Key<long[]> SCALER_AVAILABLE_JPEG_MIN_DURATIONS =
new Key<long[]>("android.scaler.availableJpegMinDurations", long[].class);
/**
- * <p>
- * The resolutions available for output from
- * the JPEG block. Listed as width x height
- * </p>
+ * <p>The JPEG resolutions that are supported by this camera device.</p>
+ * <p>The resolutions are listed as <code>(width, height)</code> pairs. All camera devices will support
+ * sensor maximum resolution (defined by {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}).</p>
+ *
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
public static final Key<android.hardware.camera2.Size[]> SCALER_AVAILABLE_JPEG_SIZES =
new Key<android.hardware.camera2.Size[]>("android.scaler.availableJpegSizes", android.hardware.camera2.Size[].class);
/**
- * <p>
- * The maximum ratio between active area width
+ * <p>The maximum ratio between active area width
* and crop region width, or between active area height and
* crop region height, if the crop region height is larger
- * than width
- * </p>
+ * than width</p>
*/
public static final Key<Float> SCALER_AVAILABLE_MAX_DIGITAL_ZOOM =
new Key<Float>("android.scaler.availableMaxDigitalZoom", float.class);
/**
- * <p>
- * The minimum frame duration that is supported
- * for each resolution in availableProcessedSizes. Should
- * correspond to the frame duration when only that processed
- * stream is active, with all processing set to
- * FAST
- * </p>
- * <p>
- * When multiple streams are configured, the minimum
- * frame duration will be >= max(individual stream min
- * durations)
- * </p>
+ * <p>For each available processed output size (defined in
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES android.scaler.availableProcessedSizes}), this property lists the
+ * minimum supportable frame duration for that size.</p>
+ * <p>This should correspond to the frame duration when only that processed
+ * stream is active, with all processing (typically in android.*.mode)
+ * set to FAST.</p>
+ * <p>When multiple streams are configured, the minimum frame duration will
+ * be &gt;= max(individual stream min durations).</p>
+ *
+ * @see CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_SIZES
*/
public static final Key<long[]> SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS =
new Key<long[]>("android.scaler.availableProcessedMinDurations", long[].class);
/**
- * <p>
- * The resolutions available for use with
+ * <p>The resolutions available for use with
* processed output streams, such as YV12, NV12, and
* platform opaque YUV/RGB streams to the GPU or video
- * encoders. Listed as width, height
- * </p>
- * <p>
- * The actual supported resolution list may be limited by
- * consumer end points for different use cases. For example, for
- * recording use case, the largest supported resolution may be
- * limited by max supported size from encoder, for preview use
- * case, the largest supported resolution may be limited by max
- * resolution SurfaceTexture/SurfaceView can support.
- * </p>
+ * encoders.</p>
+ * <p>The resolutions are listed as <code>(width, height)</code> pairs.</p>
+ * <p>For a given use case, the actual maximum supported resolution
+ * may be lower than what is listed here, depending on the destination
+ * Surface for the image data. For example, for recording video,
+ * the video encoder chosen may have a maximum size limit (e.g. 1080p)
+ * smaller than what the camera (e.g. maximum resolution is 3264x2448)
+ * can provide.</p>
+ * <p>Please reference the documentation for the image data destination to
+ * check if it limits the maximum size for image data.</p>
*/
public static final Key<android.hardware.camera2.Size[]> SCALER_AVAILABLE_PROCESSED_SIZES =
new Key<android.hardware.camera2.Size[]>("android.scaler.availableProcessedSizes", android.hardware.camera2.Size[].class);
/**
- * <p>
- * Area of raw data which corresponds to only
- * active pixels; smaller or equal to
- * pixelArraySize.
- * </p>
+ * <p>The mapping of image formats that are supported by this
+ * camera device for input streams, to their corresponding output formats.</p>
+ * <p>All camera devices with at least 1
+ * {@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} will have at least one
+ * available input format.</p>
+ * <p>The camera device will support the following map of formats,
+ * if its dependent capability is supported:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="left">Input Format</th>
+ * <th align="left">Output Format</th>
+ * <th align="left">Capability</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="left">RAW_OPAQUE</td>
+ * <td align="left">JPEG</td>
+ * <td align="left">ZSL</td>
+ * </tr>
+ * <tr>
+ * <td align="left">RAW_OPAQUE</td>
+ * <td align="left">YUV_420_888</td>
+ * <td align="left">ZSL</td>
+ * </tr>
+ * <tr>
+ * <td align="left">RAW_OPAQUE</td>
+ * <td align="left">RAW16</td>
+ * <td align="left">DNG</td>
+ * </tr>
+ * <tr>
+ * <td align="left">RAW16</td>
+ * <td align="left">YUV_420_888</td>
+ * <td align="left">DNG</td>
+ * </tr>
+ * <tr>
+ * <td align="left">RAW16</td>
+ * <td align="left">JPEG</td>
+ * <td align="left">DNG</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <p>For ZSL-capable camera devices, using the RAW_OPAQUE format
+ * as either input or output will never hurt maximum frame rate (i.e.
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS android.scaler.availableStallDurations} will not have RAW_OPAQUE).</p>
+ * <p>Attempting to configure an input stream with output streams not
+ * listed as available in this map is not valid.</p>
+ * <p>TODO: Add java type mapping for this property.</p>
+ *
+ * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS
+ * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS
+ */
+ public static final Key<int[]> SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP =
+ new Key<int[]>("android.scaler.availableInputOutputFormatsMap", int[].class);
+
+ /**
+ * <p>The available stream configurations that this
+ * camera device supports
+ * (i.e. format, width, height, output/input stream).</p>
+ * <p>The configurations are listed as <code>(format, width, height, input?)</code>
+ * tuples.</p>
+ * <p>All camera devices will support sensor maximum resolution (defined by
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}) for the JPEG format.</p>
+ * <p>For a given use case, the actual maximum supported resolution
+ * may be lower than what is listed here, depending on the destination
+ * Surface for the image data. For example, for recording video,
+ * the video encoder chosen may have a maximum size limit (e.g. 1080p)
+ * smaller than what the camera (e.g. maximum resolution is 3264x2448)
+ * can provide.</p>
+ * <p>Please reference the documentation for the image data destination to
+ * check if it limits the maximum size for image data.</p>
+ * <p>Not all output formats may be supported in a configuration with
+ * an input stream of a particular format. For more details, see
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP android.scaler.availableInputOutputFormatsMap}.</p>
+ * <p>The following table describes the minimum required output stream
+ * configurations based on the hardware level
+ * ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel}):</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">Format</th>
+ * <th align="center">Size</th>
+ * <th align="center">Hardware Level</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">JPEG</td>
+ * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
+ * <td align="center">Any</td>
+ * <td align="center"></td>
+ * </tr>
+ * <tr>
+ * <td align="center">JPEG</td>
+ * <td align="center">1920x1080 (1080p)</td>
+ * <td align="center">Any</td>
+ * <td align="center">if 1080p &lt;= activeArraySize</td>
+ * </tr>
+ * <tr>
+ * <td align="center">JPEG</td>
+ * <td align="center">1280x720 (720)</td>
+ * <td align="center">Any</td>
+ * <td align="center">if 720p &lt;= activeArraySize</td>
+ * </tr>
+ * <tr>
+ * <td align="center">JPEG</td>
+ * <td align="center">640x480 (480p)</td>
+ * <td align="center">Any</td>
+ * <td align="center">if 480p &lt;= activeArraySize</td>
+ * </tr>
+ * <tr>
+ * <td align="center">JPEG</td>
+ * <td align="center">320x240 (240p)</td>
+ * <td align="center">Any</td>
+ * <td align="center">if 240p &lt;= activeArraySize</td>
+ * </tr>
+ * <tr>
+ * <td align="center">YUV_420_888</td>
+ * <td align="center">all output sizes available for JPEG</td>
+ * <td align="center">FULL</td>
+ * <td align="center"></td>
+ * </tr>
+ * <tr>
+ * <td align="center">YUV_420_888</td>
+ * <td align="center">all output sizes available for JPEG, up to the maximum video size</td>
+ * <td align="center">LIMITED</td>
+ * <td align="center"></td>
+ * </tr>
+ * <tr>
+ * <td align="center">IMPLEMENTATION_DEFINED</td>
+ * <td align="center">same as YUV_420_888</td>
+ * <td align="center">Any</td>
+ * <td align="center"></td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <p>Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} for additional
+ * mandatory stream configurations on a per-capability basis.</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ * @see CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ * @see #SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT
+ * @see #SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT
+ */
+ public static final Key<int[]> SCALER_AVAILABLE_STREAM_CONFIGURATIONS =
+ new Key<int[]>("android.scaler.availableStreamConfigurations", int[].class);
+
+ /**
+ * <p>This lists the minimum frame duration for each
+ * format/size combination.</p>
+ * <p>This should correspond to the frame duration when only that
+ * stream is active, with all processing (typically in android.*.mode)
+ * set to either OFF or FAST.</p>
+ * <p>When multiple streams are used in a request, the minimum frame
+ * duration will be max(individual stream min durations).</p>
+ * <p>The minimum frame duration of a stream (of a particular format, size)
+ * is the same regardless of whether the stream is input or output.</p>
+ * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS android.scaler.availableStallDurations} for more details about
+ * calculating the max frame rate.</p>
+ *
+ * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ */
+ public static final Key<long[]> SCALER_AVAILABLE_MIN_FRAME_DURATIONS =
+ new Key<long[]>("android.scaler.availableMinFrameDurations", long[].class);
+
+ /**
+ * <p>This lists the maximum stall duration for each
+ * format/size combination.</p>
+ * <p>A stall duration is how much extra time would get added
+ * to the normal minimum frame duration for a repeating request
+ * that has streams with non-zero stall.</p>
+ * <p>For example, consider JPEG captures which have the following
+ * characteristics:</p>
+ * <ul>
+ * <li>JPEG streams act like processed YUV streams in requests for which
+ * they are not included; in requests in which they are directly
+ * referenced, they act as JPEG streams. This is because supporting a
+ * JPEG stream requires the underlying YUV data to always be ready for
+ * use by a JPEG encoder, but the encoder will only be used (and impact
+ * frame duration) on requests that actually reference a JPEG stream.</li>
+ * <li>The JPEG processor can run concurrently to the rest of the camera
+ * pipeline, but cannot process more than 1 capture at a time.</li>
+ * </ul>
+ * <p>In other words, using a repeating YUV request would result
+ * in a steady frame rate (let's say it's 30 FPS). If a single
+ * JPEG request is submitted periodically, the frame rate will stay
+ * at 30 FPS (as long as we wait for the previous JPEG to return each
+ * time). If we try to submit a repeating YUV + JPEG request, then
+ * the frame rate will drop from 30 FPS.</p>
+ * <p>In general, submitting a new request with a non-0 stall time
+ * stream will <em>not</em> cause a frame rate drop unless there are still
+ * outstanding buffers for that stream from previous requests.</p>
+ * <p>Submitting a repeating request with streams (call this <code>S</code>)
+ * is the same as setting the minimum frame duration from
+ * the normal minimum frame duration corresponding to <code>S</code>, added with
+ * the maximum stall duration for <code>S</code>.</p>
+ * <p>If interleaving requests with and without a stall duration,
+ * a request will stall by the maximum of the remaining times
+ * for each can-stall stream with outstanding buffers.</p>
+ * <p>This means that a stalling request will not have an exposure start
+ * until the stall has completed.</p>
+ * <p>This should correspond to the stall duration when only that stream is
+ * active, with all processing (typically in android.*.mode) set to FAST
+ * or OFF. Setting any of the processing modes to HIGH_QUALITY
+ * effectively results in an indeterminate stall duration for all
+ * streams in a request (the regular stall calculation rules are
+ * ignored).</p>
+ * <p>The following formats may always have a stall duration:</p>
+ * <ul>
+ * <li>JPEG</li>
+ * <li>RAW16</li>
+ * </ul>
+ * <p>The following formats will never have a stall duration:</p>
+ * <ul>
+ * <li>YUV_420_888</li>
+ * <li>IMPLEMENTATION_DEFINED</li>
+ * </ul>
+ * <p>All other formats may or may not have an allowed stall duration on
+ * a per-capability basis; refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
+ * for more details.</p>
+ * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} for more information about
+ * calculating the max frame rate (absent stalls).</p>
+ *
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ */
+ public static final Key<long[]> SCALER_AVAILABLE_STALL_DURATIONS =
+ new Key<long[]>("android.scaler.availableStallDurations", long[].class);
+
+ /**
+ * <p>Area of raw data which corresponds to only
+ * active pixels.</p>
+ * <p>It is smaller or equal to
+ * sensor full pixel array, which could include the black calibration pixels.</p>
*/
public static final Key<android.graphics.Rect> SENSOR_INFO_ACTIVE_ARRAY_SIZE =
new Key<android.graphics.Rect>("android.sensor.info.activeArraySize", android.graphics.Rect.class);
/**
- * <p>
- * Range of valid sensitivities
- * </p>
+ * <p>Range of valid sensitivities</p>
*/
public static final Key<int[]> SENSOR_INFO_SENSITIVITY_RANGE =
new Key<int[]>("android.sensor.info.sensitivityRange", int[].class);
/**
- * <p>
- * Range of valid exposure
- * times
- * </p>
+ * <p>Range of valid exposure
+ * times used by {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}.</p>
+ *
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
*/
public static final Key<long[]> SENSOR_INFO_EXPOSURE_TIME_RANGE =
new Key<long[]>("android.sensor.info.exposureTimeRange", long[].class);
/**
- * <p>
- * Maximum possible frame duration (minimum frame
- * rate)
- * </p>
- * <p>
- * Minimum duration is a function of resolution,
- * processing settings. See
- * android.scaler.availableProcessedMinDurations
- * android.scaler.availableJpegMinDurations
- * android.scaler.availableRawMinDurations
- * </p>
+ * <p>Maximum possible frame duration (minimum frame
+ * rate).</p>
+ * <p>The largest possible {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}
+ * that will be accepted by the camera device. Attempting to use
+ * frame durations beyond the maximum will result in the frame duration
+ * being clipped to the maximum. See that control
+ * for a full definition of frame durations.</p>
+ * <p>Refer to
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS android.scaler.availableProcessedMinDurations},
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_JPEG_MIN_DURATIONS android.scaler.availableJpegMinDurations}, and
+ * android.scaler.availableRawMinDurations for the minimum
+ * frame duration values.</p>
+ *
+ * @see CameraCharacteristics#SCALER_AVAILABLE_JPEG_MIN_DURATIONS
+ * @see CameraCharacteristics#SCALER_AVAILABLE_PROCESSED_MIN_DURATIONS
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
*/
public static final Key<Long> SENSOR_INFO_MAX_FRAME_DURATION =
new Key<Long>("android.sensor.info.maxFrameDuration", long.class);
/**
- * <p>
- * The physical dimensions of the full pixel
- * array
- * </p>
- * <p>
- * Needed for FOV calculation for old API
- * </p>
+ * <p>The physical dimensions of the full pixel
+ * array</p>
+ * <p>Needed for FOV calculation for old API</p>
*/
public static final Key<float[]> SENSOR_INFO_PHYSICAL_SIZE =
new Key<float[]>("android.sensor.info.physicalSize", float[].class);
/**
- * <p>
- * Gain factor from electrons to raw units when
- * ISO=100
- * </p>
+ * <p>Dimensions of full pixel array, possibly
+ * including black calibration pixels.</p>
+ * <p>Maximum output resolution for raw format must
+ * match this in
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS android.scaler.availableStreamConfigurations}.</p>
*
- * <b>Optional</b> - This value may be null on some devices.
+ * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS
+ */
+ public static final Key<android.hardware.camera2.Size> SENSOR_INFO_PIXEL_ARRAY_SIZE =
+ new Key<android.hardware.camera2.Size>("android.sensor.info.pixelArraySize", android.hardware.camera2.Size.class);
+
+ /**
+ * <p>Maximum raw value output by sensor.</p>
+ * <p>This specifies the fully-saturated encoding level for the raw
+ * sample values from the sensor. This is typically caused by the
+ * sensor becoming highly non-linear or clipping. The minimum for
+ * each channel is specified by the offset in the
+ * {@link CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN android.sensor.blackLevelPattern} tag.</p>
+ * <p>The white level is typically determined either by sensor bit depth
+ * (10-14 bits is expected), or by the point where the sensor response
+ * becomes too non-linear to be useful. The default value for this is
+ * maximum representable value for a 16-bit raw sample (2^16 - 1).</p>
*
- * <b>{@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL}</b> -
- * Present on all devices that report being FULL level hardware devices in the
- * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL HARDWARE_LEVEL} key.
+ * @see CameraCharacteristics#SENSOR_BLACK_LEVEL_PATTERN
+ */
+ public static final Key<Integer> SENSOR_INFO_WHITE_LEVEL =
+ new Key<Integer>("android.sensor.info.whiteLevel", int.class);
+
+ /**
+ * <p>Gain factor from electrons to raw units when
+ * ISO=100</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ * <p><b>Full capability</b> -
+ * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
*/
public static final Key<Rational> SENSOR_BASE_GAIN_FACTOR =
new Key<Rational>("android.sensor.baseGainFactor", Rational.class);
/**
- * <p>
- * Maximum sensitivity that is implemented
- * purely through analog gain
- * </p>
- * <p>
- * For android.sensor.sensitivity values less than or
- * equal to this, all applied gain must be analog. For
- * values above this, it can be a mix of analog and
- * digital
- * </p>
+ * <p>A fixed black level offset for each of the color filter arrangement
+ * (CFA) mosaic channels.</p>
+ * <p>This tag specifies the zero light value for each of the CFA mosaic
+ * channels in the camera sensor. The maximal value output by the
+ * sensor is represented by the value in {@link CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL android.sensor.info.whiteLevel}.</p>
+ * <p>The values are given in row-column scan order, with the first value
+ * corresponding to the element of the CFA in row=0, column=0.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
*
- * <b>Optional</b> - This value may be null on some devices.
+ * @see CameraCharacteristics#SENSOR_INFO_WHITE_LEVEL
+ */
+ public static final Key<int[]> SENSOR_BLACK_LEVEL_PATTERN =
+ new Key<int[]>("android.sensor.blackLevelPattern", int[].class);
+
+ /**
+ * <p>Maximum sensitivity that is implemented
+ * purely through analog gain.</p>
+ * <p>For {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} values less than or
+ * equal to this, all applied gain must be analog. For
+ * values above this, the gain applied can be a mix of analog and
+ * digital.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ * <p><b>Full capability</b> -
+ * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
- * <b>{@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL}</b> -
- * Present on all devices that report being FULL level hardware devices in the
- * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL HARDWARE_LEVEL} key.
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SENSOR_SENSITIVITY
*/
public static final Key<Integer> SENSOR_MAX_ANALOG_SENSITIVITY =
new Key<Integer>("android.sensor.maxAnalogSensitivity", int.class);
/**
- * <p>
- * Clockwise angle through which the output
+ * <p>Clockwise angle through which the output
* image needs to be rotated to be upright on the device
* screen in its native orientation. Also defines the
* direction of rolling shutter readout, which is from top
- * to bottom in the sensor's coordinate system
- * </p>
+ * to bottom in the sensor's coordinate system</p>
*/
public static final Key<Integer> SENSOR_ORIENTATION =
new Key<Integer>("android.sensor.orientation", int.class);
/**
- * <p>
- * Which face detection modes are available,
- * if any
- * </p>
- * <p>
- * OFF means face detection is disabled, it must
- * be included in the list.
- * </p><p>
- * SIMPLE means the device supports the
+ * <p>The number of input samples for each dimension of
+ * {@link CaptureResult#SENSOR_PROFILE_HUE_SAT_MAP android.sensor.profileHueSatMap}.</p>
+ * <p>The number of input samples for the hue, saturation, and value
+ * dimension of {@link CaptureResult#SENSOR_PROFILE_HUE_SAT_MAP android.sensor.profileHueSatMap}. The order of the
+ * dimensions given is hue, saturation, value; where hue is the 0th
+ * element.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureResult#SENSOR_PROFILE_HUE_SAT_MAP
+ */
+ public static final Key<int[]> SENSOR_PROFILE_HUE_SAT_MAP_DIMENSIONS =
+ new Key<int[]>("android.sensor.profileHueSatMapDimensions", int[].class);
+
+ /**
+ * <p>Optional. Defaults to [OFF]. Lists the supported test
+ * pattern modes for {@link CaptureRequest#SENSOR_TEST_PATTERN_MODE android.sensor.testPatternMode}.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE
+ */
+ public static final Key<Byte> SENSOR_AVAILABLE_TEST_PATTERN_MODES =
+ new Key<Byte>("android.sensor.availableTestPatternModes", byte.class);
+
+ /**
+ * <p>Which face detection modes are available,
+ * if any</p>
+ * <p>OFF means face detection is disabled, it must
+ * be included in the list.</p>
+ * <p>SIMPLE means the device supports the
* android.statistics.faceRectangles and
- * android.statistics.faceScores outputs.
- * </p><p>
- * FULL means the device additionally supports the
+ * android.statistics.faceScores outputs.</p>
+ * <p>FULL means the device additionally supports the
* android.statistics.faceIds and
- * android.statistics.faceLandmarks outputs.
- * </p>
+ * android.statistics.faceLandmarks outputs.</p>
*/
public static final Key<byte[]> STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES =
new Key<byte[]>("android.statistics.info.availableFaceDetectModes", byte[].class);
/**
- * <p>
- * Maximum number of simultaneously detectable
- * faces
- * </p>
+ * <p>Maximum number of simultaneously detectable
+ * faces</p>
*/
public static final Key<Integer> STATISTICS_INFO_MAX_FACE_COUNT =
new Key<Integer>("android.statistics.info.maxFaceCount", int.class);
/**
- * <p>
- * Maximum number of supported points in the
- * tonemap curve
- * </p>
+ * <p>Maximum number of supported points in the
+ * tonemap curve that can be used for {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, or
+ * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, or {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}.</p>
+ * <p>If the actual number of points provided by the application (in
+ * android.tonemap.curve*) is less than max, the camera device will
+ * resample the curve to its internal representation, using linear
+ * interpolation.</p>
+ * <p>The output curves in the result metadata may have a different number
+ * of points than the input curves, and will represent the actual
+ * hardware curves used as closely as possible when linearly interpolated.</p>
+ *
+ * @see CaptureRequest#TONEMAP_CURVE_BLUE
+ * @see CaptureRequest#TONEMAP_CURVE_GREEN
+ * @see CaptureRequest#TONEMAP_CURVE_RED
*/
public static final Key<Integer> TONEMAP_MAX_CURVE_POINTS =
new Key<Integer>("android.tonemap.maxCurvePoints", int.class);
/**
- * <p>
- * A list of camera LEDs that are available on this system.
- * </p>
+ * <p>A list of camera LEDs that are available on this system.</p>
* @see #LED_AVAILABLE_LEDS_TRANSMIT
- *
* @hide
*/
public static final Key<int[]> LED_AVAILABLE_LEDS =
new Key<int[]>("android.led.availableLeds", int[].class);
/**
- * <p>
- * The camera 3 HAL device can implement one of two possible
- * operational modes; limited and full. Full support is
- * expected from new higher-end devices. Limited mode has
- * hardware requirements roughly in line with those for a
- * camera HAL device v1 implementation, and is expected from
- * older or inexpensive devices. Full is a strict superset of
- * limited, and they share the same essential operational flow.
- * </p><p>
- * For full details refer to "S3. Operational Modes" in camera3.h
- * </p>
+ * <p>Generally classifies the overall set of the camera device functionality.</p>
+ * <p>Camera devices will come in two flavors: LIMITED and FULL.</p>
+ * <p>A FULL device has the most support possible and will enable the
+ * widest range of use cases such as:</p>
+ * <ul>
+ * <li>30 FPS at maximum resolution (== sensor resolution)</li>
+ * <li>Per frame control</li>
+ * <li>Manual sensor control</li>
+ * <li>Zero Shutter Lag (ZSL)</li>
+ * </ul>
+ * <p>A LIMITED device may have some or none of the above characteristics.
+ * To find out more refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}.</p>
+ *
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
* @see #INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
* @see #INFO_SUPPORTED_HARDWARE_LEVEL_FULL
*/
public static final Key<Integer> INFO_SUPPORTED_HARDWARE_LEVEL =
new Key<Integer>("android.info.supportedHardwareLevel", int.class);
+ /**
+ * <p>The maximum number of frames that can occur after a request
+ * (different than the previous) has been submitted, and before the
+ * result's state becomes synchronized (by setting
+ * android.sync.frameNumber to a non-negative value).</p>
+ * <p>This defines the maximum distance (in number of metadata results),
+ * between android.sync.frameNumber and the equivalent
+ * android.request.frameCount.</p>
+ * <p>In other words this acts as an upper boundary for how many frames
+ * must occur before the camera device knows for a fact that the new
+ * submitted camera settings have been applied in outgoing frames.</p>
+ * <p>For example if the distance was 2,</p>
+ * <pre><code>initial request = X (repeating)
+ * request1 = X
+ * request2 = Y
+ * request3 = Y
+ * request4 = Y
+ *
+ * where requestN has frameNumber N, and the first of the repeating
+ * initial request's has frameNumber F (and F &lt; 1).
+ *
+ * initial result = X' + { android.sync.frameNumber == F }
+ * result1 = X' + { android.sync.frameNumber == F }
+ * result2 = X' + { android.sync.frameNumber == CONVERGING }
+ * result3 = X' + { android.sync.frameNumber == CONVERGING }
+ * result4 = X' + { android.sync.frameNumber == 2 }
+ *
+ * where resultN has frameNumber N.
+ * </code></pre>
+ * <p>Since <code>result4</code> has a <code>frameNumber == 4</code> and
+ * <code>android.sync.frameNumber == 2</code>, the distance is clearly
+ * <code>4 - 2 = 2</code>.</p>
+ * @see #SYNC_MAX_LATENCY_PER_FRAME_CONTROL
+ * @see #SYNC_MAX_LATENCY_UNKNOWN
+ */
+ public static final Key<Integer> SYNC_MAX_LATENCY =
+ new Key<Integer>("android.sync.maxLatency", int.class);
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 9e8d7d1..2c53f03 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -92,7 +92,6 @@ public interface CameraDevice extends AutoCloseable {
* AE/AWB/AF should be on auto mode.
*
* @see #createCaptureRequest
- * @hide
*/
public static final int TEMPLATE_ZERO_SHUTTER_LAG = 5;
@@ -105,7 +104,6 @@ public interface CameraDevice extends AutoCloseable {
* application depending on the intended use case.
*
* @see #createCaptureRequest
- * @hide
*/
public static final int TEMPLATE_MANUAL = 6;
@@ -501,31 +499,6 @@ public interface CameraDevice extends AutoCloseable {
public void stopRepeating() throws CameraAccessException;
/**
- * <p>Wait until all the submitted requests have finished processing</p>
- *
- * <p>This method blocks until all the requests that have been submitted to
- * the camera device, either through {@link #capture capture},
- * {@link #captureBurst captureBurst},
- * {@link #setRepeatingRequest setRepeatingRequest}, or
- * {@link #setRepeatingBurst setRepeatingBurst}, have completed their
- * processing.</p>
- *
- * <p>Once this call returns successfully, the device is in an idle state,
- * and can be reconfigured with {@link #configureOutputs configureOutputs}.</p>
- *
- * <p>This method cannot be used if there is an active repeating request or
- * burst, set with {@link #setRepeatingRequest setRepeatingRequest} or
- * {@link #setRepeatingBurst setRepeatingBurst}. Call
- * {@link #stopRepeating stopRepeating} before calling this method.</p>
- *
- * @throws CameraAccessException if the camera device is no longer connected
- * @throws IllegalStateException if the camera device has been closed, the
- * device has encountered a fatal error, or if there is an active repeating
- * request or burst.
- */
- public void waitUntilIdle() throws CameraAccessException;
-
- /**
* Flush all captures currently pending and in-progress as fast as
* possible.
*
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 65b6c7a..78e7037 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;
@@ -49,6 +48,8 @@ import java.util.ArrayList;
*/
public final class CameraManager {
+ private static final String TAG = "CameraManager";
+
/**
* This should match the ICameraService definition
*/
@@ -80,6 +81,19 @@ public final class CameraManager {
mCameraService = CameraBinderDecorator.newInstance(cameraServiceRaw);
try {
+ int err = CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor();
+ if (err == CameraBinderDecorator.EOPNOTSUPP) {
+ Log.w(TAG, "HAL version doesn't vendor tags.");
+ } else {
+ CameraBinderDecorator.throwOnError(CameraMetadataNative.
+ nativeSetupGlobalVendorTagDescriptor());
+ }
+ } catch(CameraRuntimeException e) {
+ throw new IllegalStateException("Failed to setup camera vendor tags",
+ e.asChecked());
+ }
+
+ try {
mCameraService.addListener(new CameraServiceListener());
} catch(CameraRuntimeException e) {
throw new IllegalStateException("Failed to register a camera service listener",
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 1d6ff7d..a62df0f 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);
}
/**
@@ -206,6 +206,45 @@ public abstract class CameraMetadata {
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/
//
+ // Enumeration values for CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION
+ //
+
+ /**
+ * <p>The lens focus distance is not accurate, and the units used for
+ * {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance} do not correspond to any physical units.
+ * Setting the lens to the same focus distance on separate occasions may
+ * result in a different real focus distance, depending on factors such
+ * as the orientation of the device, the age of the focusing mechanism,
+ * and the device temperature. The focus distance value will still be
+ * in the range of <code>[0, {@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance}]</code>, where 0
+ * represents the farthest focus.</p>
+ *
+ * @see CaptureRequest#LENS_FOCUS_DISTANCE
+ * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE
+ * @see CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION
+ */
+ public static final int LENS_INFO_FOCUS_DISTANCE_CALIBRATION_UNCALIBRATED = 0;
+
+ /**
+ * <p>The lens focus distance is measured in diopters. However, setting the lens
+ * to the same focus distance on separate occasions may result in a
+ * different real focus distance, depending on factors such as the
+ * orientation of the device, the age of the focusing mechanism, and
+ * the device temperature.</p>
+ * @see CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION
+ */
+ public static final int LENS_INFO_FOCUS_DISTANCE_CALIBRATION_APPROXIMATE = 1;
+
+ /**
+ * <p>The lens focus distance is measured in diopters. The lens mechanism is
+ * calibrated so that setting the same focus distance is repeatable on
+ * multiple occasions with good accuracy, and the focus distance corresponds
+ * to the real physical distance to the plane of best focus.</p>
+ * @see CameraCharacteristics#LENS_INFO_FOCUS_DISTANCE_CALIBRATION
+ */
+ public static final int LENS_INFO_FOCUS_DISTANCE_CALIBRATION_CALIBRATED = 2;
+
+ //
// Enumeration values for CameraCharacteristics#LENS_FACING
//
@@ -220,13 +259,177 @@ public abstract class CameraMetadata {
public static final int LENS_FACING_BACK = 1;
//
+ // Enumeration values for CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ //
+
+ /**
+ * <p>The minimal set of capabilities that every camera
+ * device (regardless of {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel})
+ * will support.</p>
+ * <p>The full set of features supported by this capability makes
+ * the camera2 api backwards compatible with the camera1
+ * (android.hardware.Camera) API.</p>
+ * <p>TODO: @hide this. Doesn't really mean anything except
+ * act as a catch-all for all the 'base' functionality.</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE = 0;
+
+ /**
+ * <p>This is a catch-all capability to include all other
+ * tags or functionality not encapsulated by one of the other
+ * capabilities.</p>
+ * <p>A typical example is all tags marked 'optional'.</p>
+ * <p>TODO: @hide. We may not need this if we @hide all the optional
+ * tags not belonging to a capability.</p>
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_OPTIONAL = 1;
+
+ /**
+ * <p>The camera device can be manually controlled (3A algorithms such
+ * as auto exposure, and auto focus can be
+ * bypassed), this includes but is not limited to:</p>
+ * <ul>
+ * <li>Manual exposure control<ul>
+ * <li>{@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}</li>
+ * </ul>
+ * </li>
+ * <li>Manual sensitivity control<ul>
+ * <li>{@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_BASE_GAIN_FACTOR android.sensor.baseGainFactor}</li>
+ * </ul>
+ * </li>
+ * <li>Manual lens control<ul>
+ * <li>android.lens.*</li>
+ * </ul>
+ * </li>
+ * <li>Manual flash control<ul>
+ * <li>android.flash.*</li>
+ * </ul>
+ * </li>
+ * <li>Manual black level locking<ul>
+ * <li>{@link CaptureRequest#BLACK_LEVEL_LOCK android.blackLevel.lock}</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * <p>If any of the above 3A algorithms are enabled, then the camera
+ * device will accurately report the values applied by 3A in the
+ * result.</p>
+ *
+ * @see CaptureRequest#BLACK_LEVEL_LOCK
+ * @see CameraCharacteristics#SENSOR_BASE_GAIN_FACTOR
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE
+ * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE
+ * @see CaptureRequest#SENSOR_SENSITIVITY
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 2;
+
+ /**
+ * <p>TODO: This should be @hide</p>
+ * <ul>
+ * <li>Manual tonemap control<ul>
+ * <li>{@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}</li>
+ * <li>{@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}</li>
+ * <li>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}</li>
+ * <li>{@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}</li>
+ * <li>{@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</li>
+ * </ul>
+ * </li>
+ * <li>Manual white balance control<ul>
+ * <li>{@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}</li>
+ * <li>{@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains}</li>
+ * </ul>
+ * </li>
+ * <li>Lens shading map information<ul>
+ * <li>{@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}</li>
+ * <li>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * <p>If auto white balance is enabled, then the camera device
+ * will accurately report the values applied by AWB in the result.</p>
+ * <p>The camera device will also support everything in MANUAL_SENSOR
+ * except manual lens control and manual flash control.</p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_GAINS
+ * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
+ * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE
+ * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
+ * @see CaptureRequest#TONEMAP_CURVE_BLUE
+ * @see CaptureRequest#TONEMAP_CURVE_GREEN
+ * @see CaptureRequest#TONEMAP_CURVE_RED
+ * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
+ * @see CaptureRequest#TONEMAP_MODE
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_GCAM = 3;
+
+ /**
+ * <p>The camera device supports the Zero Shutter Lag use case.</p>
+ * <ul>
+ * <li>At least one input stream can be used.</li>
+ * <li>RAW_OPAQUE is supported as an output/input format</li>
+ * <li>Using RAW_OPAQUE does not cause a frame rate drop
+ * relative to the sensor's maximum capture rate (at that
+ * resolution).</li>
+ * <li>RAW_OPAQUE will be reprocessable into both YUV_420_888
+ * and JPEG formats.</li>
+ * <li>The maximum available resolution for RAW_OPAQUE streams
+ * (both input/output) will match the maximum available
+ * resolution of JPEG streams.</li>
+ * </ul>
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_ZSL = 4;
+
+ /**
+ * <p>The camera device supports outputting RAW buffers that can be
+ * saved offline into a DNG format. It can reprocess DNG
+ * files (produced from the same camera device) back into YUV.</p>
+ * <ul>
+ * <li>At least one input stream can be used.</li>
+ * <li>RAW16 is supported as output/input format.</li>
+ * <li>RAW16 is reprocessable into both YUV_420_888 and JPEG
+ * formats.</li>
+ * <li>The maximum available resolution for RAW16 streams (both
+ * input/output) will match the value in
+ * {@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE android.sensor.info.pixelArraySize}.</li>
+ * <li>All DNG-related optional metadata entries are provided
+ * by the camera device.</li>
+ * </ul>
+ *
+ * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_DNG = 5;
+
+ //
+ // Enumeration values for CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS
+ //
+
+ /**
+ * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS
+ */
+ public static final int SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT = 0;
+
+ /**
+ * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS
+ */
+ public static final int SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT = 1;
+
+ //
// Enumeration values for CameraCharacteristics#LED_AVAILABLE_LEDS
//
/**
- * <p>
- * android.led.transmit control is used
- * </p>
+ * <p>android.led.transmit control is used</p>
* @see CameraCharacteristics#LED_AVAILABLE_LEDS
* @hide
*/
@@ -247,32 +450,75 @@ public abstract class CameraMetadata {
public static final int INFO_SUPPORTED_HARDWARE_LEVEL_FULL = 1;
//
+ // Enumeration values for CameraCharacteristics#SYNC_MAX_LATENCY
+ //
+
+ /**
+ * <p>Every frame has the requests immediately applied.
+ * (and furthermore for all results,
+ * <code>android.sync.frameNumber == android.request.frameCount</code>)</p>
+ * <p>Changing controls over multiple requests one after another will
+ * produce results that have those controls applied atomically
+ * each frame.</p>
+ * <p>All FULL capability devices will have this as their maxLatency.</p>
+ * @see CameraCharacteristics#SYNC_MAX_LATENCY
+ */
+ public static final int SYNC_MAX_LATENCY_PER_FRAME_CONTROL = 0;
+
+ /**
+ * <p>Each new frame has some subset (potentially the entire set)
+ * of the past requests applied to the camera settings.</p>
+ * <p>By submitting a series of identical requests, the camera device
+ * will eventually have the camera settings applied, but it is
+ * unknown when that exact point will be.</p>
+ * @see CameraCharacteristics#SYNC_MAX_LATENCY
+ */
+ public static final int SYNC_MAX_LATENCY_UNKNOWN = -1;
+
+ //
// Enumeration values for CaptureRequest#COLOR_CORRECTION_MODE
//
/**
- * <p>
- * Use the android.colorCorrection.transform matrix
- * and android.colorCorrection.gains to do color conversion
- * </p>
+ * <p>Use the {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform} matrix
+ * and {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} to do color conversion.</p>
+ * <p>All advanced white balance adjustments (not specified
+ * by our white balance pipeline) must be disabled.</p>
+ * <p>If AWB is enabled with <code>{@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} != OFF</code>, then
+ * TRANSFORM_MATRIX is ignored. The camera device will override
+ * this value to either FAST or HIGH_QUALITY.</p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_GAINS
+ * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
+ * @see CaptureRequest#CONTROL_AWB_MODE
* @see CaptureRequest#COLOR_CORRECTION_MODE
*/
public static final int COLOR_CORRECTION_MODE_TRANSFORM_MATRIX = 0;
/**
- * <p>
- * Must not slow down frame rate relative to raw
- * bayer output
- * </p>
+ * <p>Must not slow down capture rate relative to sensor raw
+ * output.</p>
+ * <p>Advanced white balance adjustments above and beyond
+ * the specified white balance pipeline may be applied.</p>
+ * <p>If AWB is enabled with <code>{@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} != OFF</code>, then
+ * the camera device uses the last frame's AWB values
+ * (or defaults if AWB has never been run).</p>
+ *
+ * @see CaptureRequest#CONTROL_AWB_MODE
* @see CaptureRequest#COLOR_CORRECTION_MODE
*/
public static final int COLOR_CORRECTION_MODE_FAST = 1;
/**
- * <p>
- * Frame rate may be reduced by high
- * quality
- * </p>
+ * <p>Capture rate (relative to sensor raw output)
+ * may be reduced by high quality.</p>
+ * <p>Advanced white balance adjustments above and beyond
+ * the specified white balance pipeline may be applied.</p>
+ * <p>If AWB is enabled with <code>{@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} != OFF</code>, then
+ * the camera device uses the last frame's AWB values
+ * (or defaults if AWB has never been run).</p>
+ *
+ * @see CaptureRequest#CONTROL_AWB_MODE
* @see CaptureRequest#COLOR_CORRECTION_MODE
*/
public static final int COLOR_CORRECTION_MODE_HIGH_QUALITY = 2;
@@ -282,21 +528,31 @@ public abstract class CameraMetadata {
//
/**
+ * <p>The camera device will not adjust exposure duration to
+ * avoid banding problems.</p>
* @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE
*/
public static final int CONTROL_AE_ANTIBANDING_MODE_OFF = 0;
/**
+ * <p>The camera device will adjust exposure duration to
+ * avoid banding problems with 50Hz illumination sources.</p>
* @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE
*/
public static final int CONTROL_AE_ANTIBANDING_MODE_50HZ = 1;
/**
+ * <p>The camera device will adjust exposure duration to
+ * avoid banding problems with 60Hz illumination
+ * sources.</p>
* @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE
*/
public static final int CONTROL_AE_ANTIBANDING_MODE_60HZ = 2;
/**
+ * <p>The camera device will automatically adapt its
+ * antibanding routine to the current illumination
+ * conditions. This is the default.</p>
* @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE
*/
public static final int CONTROL_AE_ANTIBANDING_MODE_AUTO = 3;
@@ -306,52 +562,73 @@ public abstract class CameraMetadata {
//
/**
- * <p>
- * Autoexposure is disabled; sensor.exposureTime,
- * sensor.sensitivity and sensor.frameDuration are used
- * </p>
+ * <p>The camera device's autoexposure routine is disabled;
+ * the application-selected {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime},
+ * {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} and
+ * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} are used by the camera
+ * device, along with android.flash.* fields, if there's
+ * a flash unit for this camera device.</p>
+ *
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see CaptureRequest#SENSOR_SENSITIVITY
* @see CaptureRequest#CONTROL_AE_MODE
*/
public static final int CONTROL_AE_MODE_OFF = 0;
/**
- * <p>
- * Autoexposure is active, no flash
- * control
- * </p>
+ * <p>The camera device's autoexposure routine is active,
+ * with no flash control. The application's values for
+ * {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime},
+ * {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and
+ * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} are ignored. The
+ * application has control over the various
+ * android.flash.* fields.</p>
+ *
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see CaptureRequest#SENSOR_SENSITIVITY
* @see CaptureRequest#CONTROL_AE_MODE
*/
public static final int CONTROL_AE_MODE_ON = 1;
/**
- * <p>
- * if flash exists Autoexposure is active, auto
- * flash control; flash may be fired when precapture
- * trigger is activated, and for captures for which
- * captureIntent = STILL_CAPTURE
- * </p>
+ * <p>Like ON, except that the camera device also controls
+ * the camera's flash unit, firing it in low-light
+ * conditions. The flash may be fired during a
+ * precapture sequence (triggered by
+ * {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}) and may be fired
+ * for captures for which the
+ * {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} field is set to
+ * STILL_CAPTURE</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
+ * @see CaptureRequest#CONTROL_CAPTURE_INTENT
* @see CaptureRequest#CONTROL_AE_MODE
*/
public static final int CONTROL_AE_MODE_ON_AUTO_FLASH = 2;
/**
- * <p>
- * if flash exists Autoexposure is active, auto
- * flash control for precapture trigger and always flash
- * when captureIntent = STILL_CAPTURE
- * </p>
+ * <p>Like ON, except that the camera device also controls
+ * the camera's flash unit, always firing it for still
+ * captures. The flash may be fired during a precapture
+ * sequence (triggered by
+ * {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}) and will always
+ * be fired for captures for which the
+ * {@link CaptureRequest#CONTROL_CAPTURE_INTENT android.control.captureIntent} field is set to
+ * STILL_CAPTURE</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
+ * @see CaptureRequest#CONTROL_CAPTURE_INTENT
* @see CaptureRequest#CONTROL_AE_MODE
*/
public static final int CONTROL_AE_MODE_ON_ALWAYS_FLASH = 3;
/**
- * <p>
- * optional Automatic red eye reduction with flash.
- * If deemed necessary, red eye reduction sequence should
- * fire when precapture trigger is activated, and final
- * flash should fire when captureIntent =
- * STILL_CAPTURE
- * </p>
+ * <p>Like ON_AUTO_FLASH, but with automatic red eye
+ * reduction. If deemed necessary by the camera device,
+ * a red eye reduction flash will fire during the
+ * precapture sequence.</p>
* @see CaptureRequest#CONTROL_AE_MODE
*/
public static final int CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE = 4;
@@ -361,20 +638,15 @@ public abstract class CameraMetadata {
//
/**
- * <p>
- * The trigger is idle.
- * </p>
+ * <p>The trigger is idle.</p>
* @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
*/
public static final int CONTROL_AE_PRECAPTURE_TRIGGER_IDLE = 0;
/**
- * <p>
- * The precapture metering sequence
- * must be started. The exact effect of the precapture
- * trigger depends on the current AE mode and
- * state.
- * </p>
+ * <p>The precapture metering sequence will be started
+ * by the camera device. The exact effect of the precapture
+ * trigger depends on the current AE mode and state.</p>
* @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
*/
public static final int CONTROL_AE_PRECAPTURE_TRIGGER_START = 1;
@@ -384,55 +656,47 @@ public abstract class CameraMetadata {
//
/**
- * <p>
- * The 3A routines do not control the lens;
- * android.lens.focusDistance is controlled by the
- * application
- * </p>
+ * <p>The auto-focus routine does not control the lens;
+ * {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance} is controlled by the
+ * application</p>
+ *
+ * @see CaptureRequest#LENS_FOCUS_DISTANCE
* @see CaptureRequest#CONTROL_AF_MODE
*/
public static final int CONTROL_AF_MODE_OFF = 0;
/**
- * <p>
- * if lens is not fixed focus.
- * </p><p>
- * Use android.lens.minimumFocusDistance to determine if lens
- * is fixed focus In this mode, the lens does not move unless
+ * <p>If lens is not fixed focus.</p>
+ * <p>Use {@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} to determine if lens
+ * is fixed-focus. In this mode, the lens does not move unless
* the autofocus trigger action is called. When that trigger
* is activated, AF must transition to ACTIVE_SCAN, then to
- * the outcome of the scan (FOCUSED or
- * NOT_FOCUSED).
- * </p><p>
- * Triggering cancel AF resets the lens position to default,
- * and sets the AF state to INACTIVE.
- * </p>
+ * the outcome of the scan (FOCUSED or NOT_FOCUSED).</p>
+ * <p>Triggering AF_CANCEL resets the lens position to default,
+ * and sets the AF state to INACTIVE.</p>
+ *
+ * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE
* @see CaptureRequest#CONTROL_AF_MODE
*/
public static final int CONTROL_AF_MODE_AUTO = 1;
/**
- * <p>
- * In this mode, the lens does not move unless the
- * autofocus trigger action is called.
- * </p><p>
- * When that trigger is activated, AF must transition to
+ * <p>In this mode, the lens does not move unless the
+ * autofocus trigger action is called.</p>
+ * <p>When that trigger is activated, AF must transition to
* ACTIVE_SCAN, then to the outcome of the scan (FOCUSED or
* NOT_FOCUSED). Triggering cancel AF resets the lens
* position to default, and sets the AF state to
- * INACTIVE.
- * </p>
+ * INACTIVE.</p>
* @see CaptureRequest#CONTROL_AF_MODE
*/
public static final int CONTROL_AF_MODE_MACRO = 2;
/**
- * <p>
- * In this mode, the AF algorithm modifies the lens
+ * <p>In this mode, the AF algorithm modifies the lens
* position continually to attempt to provide a
- * constantly-in-focus image stream.
- * </p><p>
- * The focusing behavior should be suitable for good quality
+ * constantly-in-focus image stream.</p>
+ * <p>The focusing behavior should be suitable for good quality
* video recording; typically this means slower focus
* movement and no overshoots. When the AF trigger is not
* involved, the AF algorithm should start in INACTIVE state,
@@ -440,25 +704,21 @@ public abstract class CameraMetadata {
* states as appropriate. When the AF trigger is activated,
* the algorithm should immediately transition into
* AF_FOCUSED or AF_NOT_FOCUSED as appropriate, and lock the
- * lens position until a cancel AF trigger is received.
- * </p><p>
- * Once cancel is received, the algorithm should transition
+ * lens position until a cancel AF trigger is received.</p>
+ * <p>Once cancel is received, the algorithm should transition
* back to INACTIVE and resume passive scan. Note that this
* behavior is not identical to CONTINUOUS_PICTURE, since an
* ongoing PASSIVE_SCAN must immediately be
- * canceled.
- * </p>
+ * canceled.</p>
* @see CaptureRequest#CONTROL_AF_MODE
*/
public static final int CONTROL_AF_MODE_CONTINUOUS_VIDEO = 3;
/**
- * <p>
- * In this mode, the AF algorithm modifies the lens
+ * <p>In this mode, the AF algorithm modifies the lens
* position continually to attempt to provide a
- * constantly-in-focus image stream.
- * </p><p>
- * The focusing behavior should be suitable for still image
+ * constantly-in-focus image stream.</p>
+ * <p>The focusing behavior should be suitable for still image
* capture; typically this means focusing as fast as
* possible. When the AF trigger is not involved, the AF
* algorithm should start in INACTIVE state, and then
@@ -467,22 +727,18 @@ public abstract class CameraMetadata {
* trigger is activated, the algorithm should finish its
* PASSIVE_SCAN if active, and then transition into
* AF_FOCUSED or AF_NOT_FOCUSED as appropriate, and lock the
- * lens position until a cancel AF trigger is received.
- * </p><p>
- * When the AF cancel trigger is activated, the algorithm
+ * lens position until a cancel AF trigger is received.</p>
+ * <p>When the AF cancel trigger is activated, the algorithm
* should transition back to INACTIVE and then act as if it
- * has just been started.
- * </p>
+ * has just been started.</p>
* @see CaptureRequest#CONTROL_AF_MODE
*/
public static final int CONTROL_AF_MODE_CONTINUOUS_PICTURE = 4;
/**
- * <p>
- * Extended depth of field (digital focus). AF
+ * <p>Extended depth of field (digital focus). AF
* trigger is ignored, AF state should always be
- * INACTIVE.
- * </p>
+ * INACTIVE.</p>
* @see CaptureRequest#CONTROL_AF_MODE
*/
public static final int CONTROL_AF_MODE_EDOF = 5;
@@ -492,26 +748,20 @@ public abstract class CameraMetadata {
//
/**
- * <p>
- * The trigger is idle.
- * </p>
+ * <p>The trigger is idle.</p>
* @see CaptureRequest#CONTROL_AF_TRIGGER
*/
public static final int CONTROL_AF_TRIGGER_IDLE = 0;
/**
- * <p>
- * Autofocus must trigger now.
- * </p>
+ * <p>Autofocus will trigger now.</p>
* @see CaptureRequest#CONTROL_AF_TRIGGER
*/
public static final int CONTROL_AF_TRIGGER_START = 1;
/**
- * <p>
- * Autofocus must return to initial
- * state, and cancel any active trigger.
- * </p>
+ * <p>Autofocus will return to its initial
+ * state, and cancel any currently active trigger.</p>
* @see CaptureRequest#CONTROL_AF_TRIGGER
*/
public static final int CONTROL_AF_TRIGGER_CANCEL = 2;
@@ -521,46 +771,89 @@ public abstract class CameraMetadata {
//
/**
+ * <p>The camera device's auto white balance routine is disabled;
+ * the application-selected color transform matrix
+ * ({@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}) and gains
+ * ({@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains}) are used by the camera
+ * device for manual white balance control.</p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_GAINS
+ * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
* @see CaptureRequest#CONTROL_AWB_MODE
*/
public static final int CONTROL_AWB_MODE_OFF = 0;
/**
+ * <p>The camera device's auto white balance routine is active;
+ * the application's values for {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}
+ * and {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} are ignored.</p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_GAINS
+ * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
* @see CaptureRequest#CONTROL_AWB_MODE
*/
public static final int CONTROL_AWB_MODE_AUTO = 1;
/**
+ * <p>The camera device's auto white balance routine is disabled;
+ * the camera device uses incandescent light as the assumed scene
+ * illumination for white balance. While the exact white balance
+ * transforms are up to the camera device, they will approximately
+ * match the CIE standard illuminant A.</p>
* @see CaptureRequest#CONTROL_AWB_MODE
*/
public static final int CONTROL_AWB_MODE_INCANDESCENT = 2;
/**
+ * <p>The camera device's auto white balance routine is disabled;
+ * the camera device uses fluorescent light as the assumed scene
+ * illumination for white balance. While the exact white balance
+ * transforms are up to the camera device, they will approximately
+ * match the CIE standard illuminant F2.</p>
* @see CaptureRequest#CONTROL_AWB_MODE
*/
public static final int CONTROL_AWB_MODE_FLUORESCENT = 3;
/**
+ * <p>The camera device's auto white balance routine is disabled;
+ * the camera device uses warm fluorescent light as the assumed scene
+ * illumination for white balance. While the exact white balance
+ * transforms are up to the camera device, they will approximately
+ * match the CIE standard illuminant F4.</p>
* @see CaptureRequest#CONTROL_AWB_MODE
*/
public static final int CONTROL_AWB_MODE_WARM_FLUORESCENT = 4;
/**
+ * <p>The camera device's auto white balance routine is disabled;
+ * the camera device uses daylight light as the assumed scene
+ * illumination for white balance. While the exact white balance
+ * transforms are up to the camera device, they will approximately
+ * match the CIE standard illuminant D65.</p>
* @see CaptureRequest#CONTROL_AWB_MODE
*/
public static final int CONTROL_AWB_MODE_DAYLIGHT = 5;
/**
+ * <p>The camera device's auto white balance routine is disabled;
+ * the camera device uses cloudy daylight light as the assumed scene
+ * illumination for white balance.</p>
* @see CaptureRequest#CONTROL_AWB_MODE
*/
public static final int CONTROL_AWB_MODE_CLOUDY_DAYLIGHT = 6;
/**
+ * <p>The camera device's auto white balance routine is disabled;
+ * the camera device uses twilight light as the assumed scene
+ * illumination for white balance.</p>
* @see CaptureRequest#CONTROL_AWB_MODE
*/
public static final int CONTROL_AWB_MODE_TWILIGHT = 7;
/**
+ * <p>The camera device's auto white balance routine is disabled;
+ * the camera device uses shade light as the assumed scene
+ * illumination for white balance.</p>
* @see CaptureRequest#CONTROL_AWB_MODE
*/
public static final int CONTROL_AWB_MODE_SHADE = 8;
@@ -570,59 +863,47 @@ public abstract class CameraMetadata {
//
/**
- * <p>
- * This request doesn't fall into the other
+ * <p>This request doesn't fall into the other
* categories. Default to preview-like
- * behavior.
- * </p>
+ * behavior.</p>
* @see CaptureRequest#CONTROL_CAPTURE_INTENT
*/
public static final int CONTROL_CAPTURE_INTENT_CUSTOM = 0;
/**
- * <p>
- * This request is for a preview-like usecase. The
+ * <p>This request is for a preview-like usecase. The
* precapture trigger may be used to start off a metering
- * w/flash sequence
- * </p>
+ * w/flash sequence</p>
* @see CaptureRequest#CONTROL_CAPTURE_INTENT
*/
public static final int CONTROL_CAPTURE_INTENT_PREVIEW = 1;
/**
- * <p>
- * This request is for a still capture-type
- * usecase.
- * </p>
+ * <p>This request is for a still capture-type
+ * usecase.</p>
* @see CaptureRequest#CONTROL_CAPTURE_INTENT
*/
public static final int CONTROL_CAPTURE_INTENT_STILL_CAPTURE = 2;
/**
- * <p>
- * This request is for a video recording
- * usecase.
- * </p>
+ * <p>This request is for a video recording
+ * usecase.</p>
* @see CaptureRequest#CONTROL_CAPTURE_INTENT
*/
public static final int CONTROL_CAPTURE_INTENT_VIDEO_RECORD = 3;
/**
- * <p>
- * This request is for a video snapshot (still
- * image while recording video) usecase
- * </p>
+ * <p>This request is for a video snapshot (still
+ * image while recording video) usecase</p>
* @see CaptureRequest#CONTROL_CAPTURE_INTENT
*/
public static final int CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT = 4;
/**
- * <p>
- * This request is for a ZSL usecase; the
+ * <p>This request is for a ZSL usecase; the
* application will stream full-resolution images and
* reprocess one or several later for a final
- * capture
- * </p>
+ * capture</p>
* @see CaptureRequest#CONTROL_CAPTURE_INTENT
*/
public static final int CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG = 5;
@@ -632,46 +913,64 @@ public abstract class CameraMetadata {
//
/**
+ * <p>No color effect will be applied.</p>
* @see CaptureRequest#CONTROL_EFFECT_MODE
*/
public static final int CONTROL_EFFECT_MODE_OFF = 0;
/**
+ * <p>A "monocolor" effect where the image is mapped into
+ * a single color. This will typically be grayscale.</p>
* @see CaptureRequest#CONTROL_EFFECT_MODE
*/
public static final int CONTROL_EFFECT_MODE_MONO = 1;
/**
+ * <p>A "photo-negative" effect where the image's colors
+ * are inverted.</p>
* @see CaptureRequest#CONTROL_EFFECT_MODE
*/
public static final int CONTROL_EFFECT_MODE_NEGATIVE = 2;
/**
+ * <p>A "solarisation" effect (Sabattier effect) where the
+ * image is wholly or partially reversed in
+ * tone.</p>
* @see CaptureRequest#CONTROL_EFFECT_MODE
*/
public static final int CONTROL_EFFECT_MODE_SOLARIZE = 3;
/**
+ * <p>A "sepia" effect where the image is mapped into warm
+ * gray, red, and brown tones.</p>
* @see CaptureRequest#CONTROL_EFFECT_MODE
*/
public static final int CONTROL_EFFECT_MODE_SEPIA = 4;
/**
+ * <p>A "posterization" effect where the image uses
+ * discrete regions of tone rather than a continuous
+ * gradient of tones.</p>
* @see CaptureRequest#CONTROL_EFFECT_MODE
*/
public static final int CONTROL_EFFECT_MODE_POSTERIZE = 5;
/**
+ * <p>A "whiteboard" effect where the image is typically displayed
+ * as regions of white, with black or grey details.</p>
* @see CaptureRequest#CONTROL_EFFECT_MODE
*/
public static final int CONTROL_EFFECT_MODE_WHITEBOARD = 6;
/**
+ * <p>A "blackboard" effect where the image is typically displayed
+ * as regions of black, with white or grey details.</p>
* @see CaptureRequest#CONTROL_EFFECT_MODE
*/
public static final int CONTROL_EFFECT_MODE_BLACKBOARD = 7;
/**
+ * <p>An "aqua" effect where a blue hue is added to the image.</p>
* @see CaptureRequest#CONTROL_EFFECT_MODE
*/
public static final int CONTROL_EFFECT_MODE_AQUA = 8;
@@ -681,137 +980,166 @@ public abstract class CameraMetadata {
//
/**
- * <p>
- * Full application control of pipeline. All 3A
+ * <p>Full application control of pipeline. All 3A
* routines are disabled, no other settings in
- * android.control.* have any effect
- * </p>
+ * android.control.* have any effect</p>
* @see CaptureRequest#CONTROL_MODE
*/
public static final int CONTROL_MODE_OFF = 0;
/**
- * <p>
- * Use settings for each individual 3A routine.
+ * <p>Use settings for each individual 3A routine.
* Manual control of capture parameters is disabled. All
* controls in android.control.* besides sceneMode take
- * effect
- * </p>
+ * effect</p>
* @see CaptureRequest#CONTROL_MODE
*/
public static final int CONTROL_MODE_AUTO = 1;
/**
- * <p>
- * Use specific scene mode. Enabling this disables
+ * <p>Use specific scene mode. Enabling this disables
* control.aeMode, control.awbMode and control.afMode
- * controls; the HAL must ignore those settings while
+ * controls; the camera device will ignore those settings while
* USE_SCENE_MODE is active (except for FACE_PRIORITY
* scene mode). Other control entries are still active.
* This setting can only be used if availableSceneModes !=
- * UNSUPPORTED
- * </p>
+ * UNSUPPORTED</p>
* @see CaptureRequest#CONTROL_MODE
*/
public static final int CONTROL_MODE_USE_SCENE_MODE = 2;
+ /**
+ * <p>Same as OFF mode, except that this capture will not be
+ * used by camera device background auto-exposure, auto-white balance and
+ * auto-focus algorithms to update their statistics.</p>
+ * @see CaptureRequest#CONTROL_MODE
+ */
+ public static final int CONTROL_MODE_OFF_KEEP_STATE = 3;
+
//
// Enumeration values for CaptureRequest#CONTROL_SCENE_MODE
//
/**
+ * <p>Indicates that no scene modes are set for a given capture request.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
- public static final int CONTROL_SCENE_MODE_UNSUPPORTED = 0;
+ public static final int CONTROL_SCENE_MODE_DISABLED = 0;
/**
- * <p>
- * if face detection support exists Use face
- * detection data to drive 3A routines. If face detection
- * statistics are disabled, should still operate correctly
- * (but not return face detection statistics to the
- * framework).
- * </p><p>
- * Unlike the other scene modes, aeMode, awbMode, and afMode
- * remain active when FACE_PRIORITY is set. This is due to
- * compatibility concerns with the old camera
- * API
- * </p>
+ * <p>If face detection support exists, use face
+ * detection data for auto-focus, auto-white balance, and
+ * auto-exposure routines. If face detection statistics are
+ * disabled (i.e. {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode} is set to OFF),
+ * this should still operate correctly (but will not return
+ * face detection statistics to the framework).</p>
+ * <p>Unlike the other scene modes, {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode},
+ * {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}, and {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}
+ * remain active when FACE_PRIORITY is set.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_AF_MODE
+ * @see CaptureRequest#CONTROL_AWB_MODE
+ * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_FACE_PRIORITY = 1;
/**
+ * <p>Optimized for photos of quickly moving objects.
+ * Similar to SPORTS.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_ACTION = 2;
/**
+ * <p>Optimized for still photos of people.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_PORTRAIT = 3;
/**
+ * <p>Optimized for photos of distant macroscopic objects.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_LANDSCAPE = 4;
/**
+ * <p>Optimized for low-light settings.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_NIGHT = 5;
/**
+ * <p>Optimized for still photos of people in low-light
+ * settings.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_NIGHT_PORTRAIT = 6;
/**
+ * <p>Optimized for dim, indoor settings where flash must
+ * remain off.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_THEATRE = 7;
/**
+ * <p>Optimized for bright, outdoor beach settings.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_BEACH = 8;
/**
+ * <p>Optimized for bright, outdoor settings containing snow.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_SNOW = 9;
/**
+ * <p>Optimized for scenes of the setting sun.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_SUNSET = 10;
/**
+ * <p>Optimized to avoid blurry photos due to small amounts of
+ * device motion (for example: due to hand shake).</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_STEADYPHOTO = 11;
/**
+ * <p>Optimized for nighttime photos of fireworks.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_FIREWORKS = 12;
/**
+ * <p>Optimized for photos of quickly moving people.
+ * Similar to ACTION.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_SPORTS = 13;
/**
+ * <p>Optimized for dim, indoor settings with multiple moving
+ * people.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_PARTY = 14;
/**
+ * <p>Optimized for dim settings where the main light source
+ * is a flame.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_CANDLELIGHT = 15;
/**
+ * <p>Optimized for accurately capturing a photo of barcode
+ * for use by camera applications that wish to read the
+ * barcode value.</p>
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
public static final int CONTROL_SCENE_MODE_BARCODE = 16;
@@ -821,27 +1149,21 @@ public abstract class CameraMetadata {
//
/**
- * <p>
- * No edge enhancement is applied
- * </p>
+ * <p>No edge enhancement is applied</p>
* @see CaptureRequest#EDGE_MODE
*/
public static final int EDGE_MODE_OFF = 0;
/**
- * <p>
- * Must not slow down frame rate relative to raw
- * bayer output
- * </p>
+ * <p>Must not slow down frame rate relative to sensor
+ * output</p>
* @see CaptureRequest#EDGE_MODE
*/
public static final int EDGE_MODE_FAST = 1;
/**
- * <p>
- * Frame rate may be reduced by high
- * quality
- * </p>
+ * <p>Frame rate may be reduced by high
+ * quality</p>
* @see CaptureRequest#EDGE_MODE
*/
public static final int EDGE_MODE_HIGH_QUALITY = 2;
@@ -851,44 +1173,65 @@ public abstract class CameraMetadata {
//
/**
- * <p>
- * Do not fire the flash for this
- * capture
- * </p>
+ * <p>Do not fire the flash for this capture.</p>
* @see CaptureRequest#FLASH_MODE
*/
public static final int FLASH_MODE_OFF = 0;
/**
- * <p>
- * if android.flash.available is true Fire flash
- * for this capture based on firingPower,
- * firingTime.
- * </p>
+ * <p>If the flash is available and charged, fire flash
+ * for this capture based on android.flash.firingPower and
+ * android.flash.firingTime.</p>
* @see CaptureRequest#FLASH_MODE
*/
public static final int FLASH_MODE_SINGLE = 1;
/**
- * <p>
- * if android.flash.available is true Flash
- * continuously on, power set by
- * firingPower
- * </p>
+ * <p>Transition flash to continuously on.</p>
* @see CaptureRequest#FLASH_MODE
*/
public static final int FLASH_MODE_TORCH = 2;
//
+ // Enumeration values for CaptureRequest#HOT_PIXEL_MODE
+ //
+
+ /**
+ * <p>The frame rate must not be reduced relative to sensor raw output
+ * for this option.</p>
+ * <p>No hot pixel correction is applied.</p>
+ * @see CaptureRequest#HOT_PIXEL_MODE
+ */
+ public static final int HOT_PIXEL_MODE_OFF = 0;
+
+ /**
+ * <p>The frame rate must not be reduced relative to sensor raw output
+ * for this option.</p>
+ * <p>Hot pixel correction is applied.</p>
+ * @see CaptureRequest#HOT_PIXEL_MODE
+ */
+ public static final int HOT_PIXEL_MODE_FAST = 1;
+
+ /**
+ * <p>The frame rate may be reduced relative to sensor raw output
+ * for this option.</p>
+ * <p>A high-quality hot pixel correction is applied.</p>
+ * @see CaptureRequest#HOT_PIXEL_MODE
+ */
+ public static final int HOT_PIXEL_MODE_HIGH_QUALITY = 2;
+
+ //
// Enumeration values for CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
//
/**
+ * <p>Optical stabilization is unavailable.</p>
* @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
*/
public static final int LENS_OPTICAL_STABILIZATION_MODE_OFF = 0;
/**
+ * <p>Optical stabilization is enabled.</p>
* @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
*/
public static final int LENS_OPTICAL_STABILIZATION_MODE_ON = 1;
@@ -898,32 +1241,153 @@ public abstract class CameraMetadata {
//
/**
- * <p>
- * No noise reduction is applied
- * </p>
+ * <p>No noise reduction is applied</p>
* @see CaptureRequest#NOISE_REDUCTION_MODE
*/
public static final int NOISE_REDUCTION_MODE_OFF = 0;
/**
- * <p>
- * Must not slow down frame rate relative to raw
- * bayer output
- * </p>
+ * <p>Must not slow down frame rate relative to sensor
+ * output</p>
* @see CaptureRequest#NOISE_REDUCTION_MODE
*/
public static final int NOISE_REDUCTION_MODE_FAST = 1;
/**
- * <p>
- * May slow down frame rate to provide highest
- * quality
- * </p>
+ * <p>May slow down frame rate to provide highest
+ * quality</p>
* @see CaptureRequest#NOISE_REDUCTION_MODE
*/
public static final int NOISE_REDUCTION_MODE_HIGH_QUALITY = 2;
//
+ // Enumeration values for CaptureRequest#SENSOR_TEST_PATTERN_MODE
+ //
+
+ /**
+ * <p>Default. No test pattern mode is used, and the camera
+ * device returns captures from the image sensor.</p>
+ * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE
+ */
+ public static final int SENSOR_TEST_PATTERN_MODE_OFF = 0;
+
+ /**
+ * <p>Each pixel in <code>[R, G_even, G_odd, B]</code> is replaced by its
+ * respective color channel provided in
+ * {@link CaptureRequest#SENSOR_TEST_PATTERN_DATA android.sensor.testPatternData}.</p>
+ * <p>For example:</p>
+ * <pre><code>android.testPatternData = [0, 0xFFFFFFFF, 0xFFFFFFFF, 0]
+ * </code></pre>
+ * <p>All green pixels are 100% green. All red/blue pixels are black.</p>
+ * <pre><code>android.testPatternData = [0xFFFFFFFF, 0, 0xFFFFFFFF, 0]
+ * </code></pre>
+ * <p>All red pixels are 100% red. Only the odd green pixels
+ * are 100% green. All blue pixels are 100% black.</p>
+ *
+ * @see CaptureRequest#SENSOR_TEST_PATTERN_DATA
+ * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE
+ */
+ public static final int SENSOR_TEST_PATTERN_MODE_SOLID_COLOR = 1;
+
+ /**
+ * <p>All pixel data is replaced with an 8-bar color pattern.</p>
+ * <p>The vertical bars (left-to-right) are as follows:</p>
+ * <ul>
+ * <li>100% white</li>
+ * <li>yellow</li>
+ * <li>cyan</li>
+ * <li>green</li>
+ * <li>magenta</li>
+ * <li>red</li>
+ * <li>blue</li>
+ * <li>black</li>
+ * </ul>
+ * <p>In general the image would look like the following:</p>
+ * <pre><code>W Y C G M R B K
+ * W Y C G M R B K
+ * W Y C G M R B K
+ * W Y C G M R B K
+ * W Y C G M R B K
+ * . . . . . . . .
+ * . . . . . . . .
+ * . . . . . . . .
+ *
+ * (B = Blue, K = Black)
+ * </code></pre>
+ * <p>Each bar should take up 1/8 of the sensor pixel array width.
+ * When this is not possible, the bar size should be rounded
+ * down to the nearest integer and the pattern can repeat
+ * on the right side.</p>
+ * <p>Each bar's height must always take up the full sensor
+ * pixel array height.</p>
+ * <p>Each pixel in this test pattern must be set to either
+ * 0% intensity or 100% intensity.</p>
+ * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE
+ */
+ public static final int SENSOR_TEST_PATTERN_MODE_COLOR_BARS = 2;
+
+ /**
+ * <p>The test pattern is similar to COLOR_BARS, except that
+ * each bar should start at its specified color at the top,
+ * and fade to gray at the bottom.</p>
+ * <p>Furthermore each bar is further subdivided into a left and
+ * right half. The left half should have a smooth gradient,
+ * and the right half should have a quantized gradient.</p>
+ * <p>In particular, the right half's should consist of blocks of the
+ * same color for 1/16th active sensor pixel array width.</p>
+ * <p>The least significant bits in the quantized gradient should
+ * be copied from the most significant bits of the smooth gradient.</p>
+ * <p>The height of each bar should always be a multiple of 128.
+ * When this is not the case, the pattern should repeat at the bottom
+ * of the image.</p>
+ * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE
+ */
+ public static final int SENSOR_TEST_PATTERN_MODE_COLOR_BARS_FADE_TO_GRAY = 3;
+
+ /**
+ * <p>All pixel data is replaced by a pseudo-random sequence
+ * generated from a PN9 512-bit sequence (typically implemented
+ * in hardware with a linear feedback shift register).</p>
+ * <p>The generator should be reset at the beginning of each frame,
+ * and thus each subsequent raw frame with this test pattern should
+ * be exactly the same as the last.</p>
+ * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE
+ */
+ public static final int SENSOR_TEST_PATTERN_MODE_PN9 = 4;
+
+ /**
+ * <p>The first custom test pattern. All custom patterns that are
+ * available only on this camera device are at least this numeric
+ * value.</p>
+ * <p>All of the custom test patterns will be static
+ * (that is the raw image must not vary from frame to frame).</p>
+ * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE
+ */
+ public static final int SENSOR_TEST_PATTERN_MODE_CUSTOM1 = 256;
+
+ //
+ // Enumeration values for CaptureRequest#SHADING_MODE
+ //
+
+ /**
+ * <p>No lens shading correction is applied</p>
+ * @see CaptureRequest#SHADING_MODE
+ */
+ public static final int SHADING_MODE_OFF = 0;
+
+ /**
+ * <p>Must not slow down frame rate relative to sensor raw output</p>
+ * @see CaptureRequest#SHADING_MODE
+ */
+ public static final int SHADING_MODE_FAST = 1;
+
+ /**
+ * <p>Frame rate may be reduced by high quality</p>
+ * @see CaptureRequest#SHADING_MODE
+ */
+ public static final int SHADING_MODE_HIGH_QUALITY = 2;
+
+ //
// Enumeration values for CaptureRequest#STATISTICS_FACE_DETECT_MODE
//
@@ -933,19 +1397,15 @@ public abstract class CameraMetadata {
public static final int STATISTICS_FACE_DETECT_MODE_OFF = 0;
/**
- * <p>
- * Optional Return rectangle and confidence
- * only
- * </p>
+ * <p>Optional Return rectangle and confidence
+ * only</p>
* @see CaptureRequest#STATISTICS_FACE_DETECT_MODE
*/
public static final int STATISTICS_FACE_DETECT_MODE_SIMPLE = 1;
/**
- * <p>
- * Optional Return all face
- * metadata
- * </p>
+ * <p>Optional Return all face
+ * metadata</p>
* @see CaptureRequest#STATISTICS_FACE_DETECT_MODE
*/
public static final int STATISTICS_FACE_DETECT_MODE_FULL = 2;
@@ -969,28 +1429,32 @@ public abstract class CameraMetadata {
//
/**
- * <p>
- * Use the tone mapping curve specified in
- * android.tonemap.curve
- * </p>
+ * <p>Use the tone mapping curve specified in
+ * the android.tonemap.curve* entries.</p>
+ * <p>All color enhancement and tonemapping must be disabled, except
+ * for applying the tonemapping curve specified by
+ * {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}, or
+ * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}.</p>
+ * <p>Must not slow down frame rate relative to raw
+ * sensor output.</p>
+ *
+ * @see CaptureRequest#TONEMAP_CURVE_BLUE
+ * @see CaptureRequest#TONEMAP_CURVE_GREEN
+ * @see CaptureRequest#TONEMAP_CURVE_RED
* @see CaptureRequest#TONEMAP_MODE
*/
public static final int TONEMAP_MODE_CONTRAST_CURVE = 0;
/**
- * <p>
- * Must not slow down frame rate relative to raw
- * bayer output
- * </p>
+ * <p>Advanced gamma mapping and color enhancement may be applied.</p>
+ * <p>Should not slow down frame rate relative to raw sensor output.</p>
* @see CaptureRequest#TONEMAP_MODE
*/
public static final int TONEMAP_MODE_FAST = 1;
/**
- * <p>
- * Frame rate may be reduced by high
- * quality
- * </p>
+ * <p>Advanced gamma mapping and color enhancement may be applied.</p>
+ * <p>May slow down frame rate relative to raw sensor output.</p>
* @see CaptureRequest#TONEMAP_MODE
*/
public static final int TONEMAP_MODE_HIGH_QUALITY = 2;
@@ -1000,60 +1464,51 @@ public abstract class CameraMetadata {
//
/**
- * <p>
- * AE is off. When a camera device is opened, it starts in
- * this state.
- * </p>
+ * <p>AE is off or recently reset. When a camera device is opened, it starts in
+ * this state. This is a transient state, the camera device may skip reporting
+ * this state in capture result.</p>
* @see CaptureResult#CONTROL_AE_STATE
*/
public static final int CONTROL_AE_STATE_INACTIVE = 0;
/**
- * <p>
- * AE doesn't yet have a good set of control values
- * for the current scene
- * </p>
+ * <p>AE doesn't yet have a good set of control values
+ * for the current scene. This is a transient state, the camera device may skip
+ * reporting this state in capture result.</p>
* @see CaptureResult#CONTROL_AE_STATE
*/
public static final int CONTROL_AE_STATE_SEARCHING = 1;
/**
- * <p>
- * AE has a good set of control values for the
- * current scene
- * </p>
+ * <p>AE has a good set of control values for the
+ * current scene.</p>
* @see CaptureResult#CONTROL_AE_STATE
*/
public static final int CONTROL_AE_STATE_CONVERGED = 2;
/**
- * <p>
- * AE has been locked (aeMode =
- * LOCKED)
- * </p>
+ * <p>AE has been locked.</p>
* @see CaptureResult#CONTROL_AE_STATE
*/
public static final int CONTROL_AE_STATE_LOCKED = 3;
/**
- * <p>
- * AE has a good set of control values, but flash
+ * <p>AE has a good set of control values, but flash
* needs to be fired for good quality still
- * capture
- * </p>
+ * capture.</p>
* @see CaptureResult#CONTROL_AE_STATE
*/
public static final int CONTROL_AE_STATE_FLASH_REQUIRED = 4;
/**
- * <p>
- * AE has been asked to do a precapture sequence
- * (through the
- * trigger_action(CAMERA2_TRIGGER_PRECAPTURE_METERING)
- * call), and is currently executing it. Once PRECAPTURE
+ * <p>AE has been asked to do a precapture sequence
+ * (through the {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} START),
+ * and is currently executing it. Once PRECAPTURE
* completes, AE will transition to CONVERGED or
- * FLASH_REQUIRED as appropriate
- * </p>
+ * FLASH_REQUIRED as appropriate. This is a transient state, the
+ * camera device may skip reporting this state in capture result.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
* @see CaptureResult#CONTROL_AE_STATE
*/
public static final int CONTROL_AE_STATE_PRECAPTURE = 5;
@@ -1063,71 +1518,62 @@ public abstract class CameraMetadata {
//
/**
- * <p>
- * AF off or has not yet tried to scan/been asked
+ * <p>AF off or has not yet tried to scan/been asked
* to scan. When a camera device is opened, it starts in
- * this state.
- * </p>
+ * this state. This is a transient state, the camera device may
+ * skip reporting this state in capture result.</p>
* @see CaptureResult#CONTROL_AF_STATE
*/
public static final int CONTROL_AF_STATE_INACTIVE = 0;
/**
- * <p>
- * if CONTINUOUS_* modes are supported. AF is
+ * <p>if CONTINUOUS_* modes are supported. AF is
* currently doing an AF scan initiated by a continuous
- * autofocus mode
- * </p>
+ * autofocus mode. This is a transient state, the camera device may
+ * skip reporting this state in capture result.</p>
* @see CaptureResult#CONTROL_AF_STATE
*/
public static final int CONTROL_AF_STATE_PASSIVE_SCAN = 1;
/**
- * <p>
- * if CONTINUOUS_* modes are supported. AF currently
+ * <p>if CONTINUOUS_* modes are supported. AF currently
* believes it is in focus, but may restart scanning at
- * any time.
- * </p>
+ * any time. This is a transient state, the camera device may skip
+ * reporting this state in capture result.</p>
* @see CaptureResult#CONTROL_AF_STATE
*/
public static final int CONTROL_AF_STATE_PASSIVE_FOCUSED = 2;
/**
- * <p>
- * if AUTO or MACRO modes are supported. AF is doing
- * an AF scan because it was triggered by AF
- * trigger
- * </p>
+ * <p>if AUTO or MACRO modes are supported. AF is doing
+ * an AF scan because it was triggered by AF trigger. This is a
+ * transient state, the camera device may skip reporting
+ * this state in capture result.</p>
* @see CaptureResult#CONTROL_AF_STATE
*/
public static final int CONTROL_AF_STATE_ACTIVE_SCAN = 3;
/**
- * <p>
- * if any AF mode besides OFF is supported. AF
+ * <p>if any AF mode besides OFF is supported. AF
* believes it is focused correctly and is
- * locked
- * </p>
+ * locked.</p>
* @see CaptureResult#CONTROL_AF_STATE
*/
public static final int CONTROL_AF_STATE_FOCUSED_LOCKED = 4;
/**
- * <p>
- * if any AF mode besides OFF is supported. AF has
+ * <p>if any AF mode besides OFF is supported. AF has
* failed to focus successfully and is
- * locked
- * </p>
+ * locked.</p>
* @see CaptureResult#CONTROL_AF_STATE
*/
public static final int CONTROL_AF_STATE_NOT_FOCUSED_LOCKED = 5;
/**
- * <p>
- * if CONTINUOUS_* modes are supported. AF finished a
+ * <p>if CONTINUOUS_* modes are supported. AF finished a
* passive scan without finding focus, and may restart
- * scanning at any time.
- * </p>
+ * scanning at any time. This is a transient state, the camera
+ * device may skip reporting this state in capture result.</p>
* @see CaptureResult#CONTROL_AF_STATE
*/
public static final int CONTROL_AF_STATE_PASSIVE_UNFOCUSED = 6;
@@ -1137,37 +1583,30 @@ public abstract class CameraMetadata {
//
/**
- * <p>
- * AWB is not in auto mode. When a camera device is opened, it
- * starts in this state.
- * </p>
+ * <p>AWB is not in auto mode. When a camera device is opened, it
+ * starts in this state. This is a transient state, the camera device may
+ * skip reporting this state in capture result.</p>
* @see CaptureResult#CONTROL_AWB_STATE
*/
public static final int CONTROL_AWB_STATE_INACTIVE = 0;
/**
- * <p>
- * AWB doesn't yet have a good set of control
- * values for the current scene
- * </p>
+ * <p>AWB doesn't yet have a good set of control
+ * values for the current scene. This is a transient state, the camera device
+ * may skip reporting this state in capture result.</p>
* @see CaptureResult#CONTROL_AWB_STATE
*/
public static final int CONTROL_AWB_STATE_SEARCHING = 1;
/**
- * <p>
- * AWB has a good set of control values for the
- * current scene
- * </p>
+ * <p>AWB has a good set of control values for the
+ * current scene.</p>
* @see CaptureResult#CONTROL_AWB_STATE
*/
public static final int CONTROL_AWB_STATE_CONVERGED = 2;
/**
- * <p>
- * AE has been locked (aeMode =
- * LOCKED)
- * </p>
+ * <p>AWB has been locked.</p>
* @see CaptureResult#CONTROL_AWB_STATE
*/
public static final int CONTROL_AWB_STATE_LOCKED = 3;
@@ -1177,36 +1616,34 @@ public abstract class CameraMetadata {
//
/**
- * <p>
- * No flash on camera
- * </p>
+ * <p>No flash on camera</p>
* @see CaptureResult#FLASH_STATE
*/
public static final int FLASH_STATE_UNAVAILABLE = 0;
/**
- * <p>
- * if android.flash.available is true Flash is
- * charging and cannot be fired
- * </p>
+ * <p>if {@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} is true Flash is
+ * charging and cannot be fired</p>
+ *
+ * @see CameraCharacteristics#FLASH_INFO_AVAILABLE
* @see CaptureResult#FLASH_STATE
*/
public static final int FLASH_STATE_CHARGING = 1;
/**
- * <p>
- * if android.flash.available is true Flash is
- * ready to fire
- * </p>
+ * <p>if {@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} is true Flash is
+ * ready to fire</p>
+ *
+ * @see CameraCharacteristics#FLASH_INFO_AVAILABLE
* @see CaptureResult#FLASH_STATE
*/
public static final int FLASH_STATE_READY = 2;
/**
- * <p>
- * if android.flash.available is true Flash fired
- * for this capture
- * </p>
+ * <p>if {@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} is true Flash fired
+ * for this capture</p>
+ *
+ * @see CameraCharacteristics#FLASH_INFO_AVAILABLE
* @see CaptureResult#FLASH_STATE
*/
public static final int FLASH_STATE_FIRED = 3;
@@ -1216,16 +1653,134 @@ public abstract class CameraMetadata {
//
/**
+ * <p>The lens parameters ({@link CaptureRequest#LENS_FOCAL_LENGTH android.lens.focalLength}, {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance},
+ * {@link CaptureRequest#LENS_FILTER_DENSITY android.lens.filterDensity} and {@link CaptureRequest#LENS_APERTURE android.lens.aperture}) are not changing.</p>
+ *
+ * @see CaptureRequest#LENS_APERTURE
+ * @see CaptureRequest#LENS_FILTER_DENSITY
+ * @see CaptureRequest#LENS_FOCAL_LENGTH
+ * @see CaptureRequest#LENS_FOCUS_DISTANCE
* @see CaptureResult#LENS_STATE
*/
public static final int LENS_STATE_STATIONARY = 0;
/**
+ * <p>Any of the lens parameters ({@link CaptureRequest#LENS_FOCAL_LENGTH android.lens.focalLength}, {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance},
+ * {@link CaptureRequest#LENS_FILTER_DENSITY android.lens.filterDensity} or {@link CaptureRequest#LENS_APERTURE android.lens.aperture}) is changing.</p>
+ *
+ * @see CaptureRequest#LENS_APERTURE
+ * @see CaptureRequest#LENS_FILTER_DENSITY
+ * @see CaptureRequest#LENS_FOCAL_LENGTH
+ * @see CaptureRequest#LENS_FOCUS_DISTANCE
* @see CaptureResult#LENS_STATE
*/
public static final int LENS_STATE_MOVING = 1;
//
+ // Enumeration values for CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ //
+
+ /**
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_DAYLIGHT = 1;
+
+ /**
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_FLUORESCENT = 2;
+
+ /**
+ * <p>Incandescent light</p>
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_TUNGSTEN = 3;
+
+ /**
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_FLASH = 4;
+
+ /**
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_FINE_WEATHER = 9;
+
+ /**
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_CLOUDY_WEATHER = 10;
+
+ /**
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_SHADE = 11;
+
+ /**
+ * <p>D 5700 - 7100K</p>
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_DAYLIGHT_FLUORESCENT = 12;
+
+ /**
+ * <p>N 4600 - 5400K</p>
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_DAY_WHITE_FLUORESCENT = 13;
+
+ /**
+ * <p>W 3900 - 4500K</p>
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_COOL_WHITE_FLUORESCENT = 14;
+
+ /**
+ * <p>WW 3200 - 3700K</p>
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_WHITE_FLUORESCENT = 15;
+
+ /**
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_STANDARD_A = 17;
+
+ /**
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_STANDARD_B = 18;
+
+ /**
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_STANDARD_C = 19;
+
+ /**
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_D55 = 20;
+
+ /**
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_D65 = 21;
+
+ /**
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_D75 = 22;
+
+ /**
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_D50 = 23;
+
+ /**
+ * @see CaptureResult#SENSOR_REFERENCE_ILLUMINANT
+ */
+ public static final int SENSOR_REFERENCE_ILLUMINANT_ISO_STUDIO_TUNGSTEN = 24;
+
+ //
// Enumeration values for CaptureResult#STATISTICS_SCENE_FLICKER
//
@@ -1244,6 +1799,43 @@ public abstract class CameraMetadata {
*/
public static final int STATISTICS_SCENE_FLICKER_60HZ = 2;
+ //
+ // Enumeration values for CaptureResult#SYNC_FRAME_NUMBER
+ //
+
+ /**
+ * <p>The current result is not yet fully synchronized to any request.
+ * Synchronization is in progress, and reading metadata from this
+ * result may include a mix of data that have taken effect since the
+ * last synchronization time.</p>
+ * <p>In some future result, within {@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} frames,
+ * this value will update to the actual frame number frame number
+ * the result is guaranteed to be synchronized to (as long as the
+ * request settings remain constant).</p>
+ *
+ * @see CameraCharacteristics#SYNC_MAX_LATENCY
+ * @see CaptureResult#SYNC_FRAME_NUMBER
+ * @hide
+ */
+ public static final int SYNC_FRAME_NUMBER_CONVERGING = -1;
+
+ /**
+ * <p>The current result's synchronization status is unknown. The
+ * result may have already converged, or it may be in progress.
+ * Reading from this result may include some mix of settings from
+ * past requests.</p>
+ * <p>After a settings change, the new settings will eventually all
+ * take effect for the output buffers and results. However, this
+ * value will not change when that happens. Altering settings
+ * rapidly may provide outcomes using mixes of settings from recent
+ * requests.</p>
+ * <p>This value is intended primarily for backwards compatibility with
+ * the older camera implementations (for android.hardware.Camera).</p>
+ * @see CaptureResult#SYNC_FRAME_NUMBER
+ * @hide
+ */
+ public static final int SYNC_FRAME_NUMBER_UNKNOWN = -2;
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
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..a8caba0 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;
@@ -318,11 +317,52 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* modify the comment blocks at the start or end.
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/
+
/**
- * <p>
- * When android.control.awbMode is not OFF, TRANSFORM_MATRIX
- * should be ignored.
- * </p>
+ * <p>The mode control selects how the image data is converted from the
+ * sensor's native color into linear sRGB color.</p>
+ * <p>When auto-white balance is enabled with {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}, this
+ * control is overridden by the AWB routine. When AWB is disabled, the
+ * application controls how the color mapping is performed.</p>
+ * <p>We define the expected processing pipeline below. For consistency
+ * across devices, this is always the case with TRANSFORM_MATRIX.</p>
+ * <p>When either FULL or HIGH_QUALITY is used, the camera device may
+ * do additional processing but {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and
+ * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform} will still be provided by the
+ * camera device (in the results) and be roughly correct.</p>
+ * <p>Switching to TRANSFORM_MATRIX and using the data provided from
+ * FAST or HIGH_QUALITY will yield a picture with the same white point
+ * as what was produced by the camera device in the earlier frame.</p>
+ * <p>The expected processing pipeline is as follows:</p>
+ * <p><img alt="White balance processing pipeline" src="../../../../images/camera2/metadata/android.colorCorrection.mode/processing_pipeline.png" /></p>
+ * <p>The white balance is encoded by two values, a 4-channel white-balance
+ * gain vector (applied in the Bayer domain), and a 3x3 color transform
+ * matrix (applied after demosaic).</p>
+ * <p>The 4-channel white-balance gains are defined as:</p>
+ * <pre><code>{@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} = [ R G_even G_odd B ]
+ * </code></pre>
+ * <p>where <code>G_even</code> is the gain for green pixels on even rows of the
+ * output, and <code>G_odd</code> is the gain for green pixels on the odd rows.
+ * These may be identical for a given camera device implementation; if
+ * the camera device does not support a separate gain for even/odd green
+ * channels, it will use the <code>G_even</code> value, and write <code>G_odd</code> equal to
+ * <code>G_even</code> in the output result metadata.</p>
+ * <p>The matrices for color transforms are defined as a 9-entry vector:</p>
+ * <pre><code>{@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform} = [ I0 I1 I2 I3 I4 I5 I6 I7 I8 ]
+ * </code></pre>
+ * <p>which define a transform from input sensor colors, <code>P_in = [ r g b ]</code>,
+ * to output linear sRGB, <code>P_out = [ r' g' b' ]</code>,</p>
+ * <p>with colors as follows:</p>
+ * <pre><code>r' = I0r + I1g + I2b
+ * g' = I3r + I4g + I5b
+ * b' = I6r + I7g + I8b
+ * </code></pre>
+ * <p>Both the input and output value ranges must match. Overflow/underflow
+ * values are clipped to fit within the range.</p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_GAINS
+ * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
+ * @see CaptureRequest#CONTROL_AWB_MODE
* @see #COLOR_CORRECTION_MODE_TRANSFORM_MATRIX
* @see #COLOR_CORRECTION_MODE_FAST
* @see #COLOR_CORRECTION_MODE_HIGH_QUALITY
@@ -331,55 +371,80 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.colorCorrection.mode", int.class);
/**
- * <p>
- * A color transform matrix to use to transform
- * from sensor RGB color space to output linear sRGB color space
- * </p>
- * <p>
- * This matrix is either set by HAL when the request
- * android.colorCorrection.mode is not TRANSFORM_MATRIX, or
+ * <p>A color transform matrix to use to transform
+ * from sensor RGB color space to output linear sRGB color space</p>
+ * <p>This matrix is either set by the camera device when the request
+ * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not TRANSFORM_MATRIX, or
* directly by the application in the request when the
- * android.colorCorrection.mode is TRANSFORM_MATRIX.
- * </p><p>
- * In the latter case, the HAL may round the matrix to account
- * for precision issues; the final rounded matrix should be
- * reported back in this matrix result metadata.
- * </p>
+ * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is TRANSFORM_MATRIX.</p>
+ * <p>In the latter case, the camera device may round the matrix to account
+ * for precision issues; the final rounded matrix should be reported back
+ * in this matrix result metadata. The transform should keep the magnitude
+ * of the output color values within <code>[0, 1.0]</code> (assuming input color
+ * values is within the normalized range <code>[0, 1.0]</code>), or clipping may occur.</p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_MODE
*/
public static final Key<Rational[]> COLOR_CORRECTION_TRANSFORM =
new Key<Rational[]>("android.colorCorrection.transform", Rational[].class);
/**
- * <p>
- * Gains applying to Bayer color channels for
- * white-balance
- * </p>
- * <p>
- * The 4-channel white-balance gains are defined in
- * the order of [R G_even G_odd B], where G_even is the gain
- * for green pixels on even rows of the output, and G_odd
- * is the gain for greenpixels on the odd rows. if a HAL
+ * <p>Gains applying to Bayer raw color channels for
+ * white-balance.</p>
+ * <p>The 4-channel white-balance gains are defined in
+ * the order of <code>[R G_even G_odd B]</code>, where <code>G_even</code> is the gain
+ * for green pixels on even rows of the output, and <code>G_odd</code>
+ * is the gain for green pixels on the odd rows. if a HAL
* does not support a separate gain for even/odd green channels,
- * it should use the G_even value,and write G_odd equal to
- * G_even in the output result metadata.
- * </p><p>
- * This array is either set by HAL when the request
- * android.colorCorrection.mode is not TRANSFORM_MATRIX, or
+ * it should use the <code>G_even</code> value, and write <code>G_odd</code> equal to
+ * <code>G_even</code> in the output result metadata.</p>
+ * <p>This array is either set by the camera device when the request
+ * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not TRANSFORM_MATRIX, or
* directly by the application in the request when the
- * android.colorCorrection.mode is TRANSFORM_MATRIX.
- * </p><p>
- * The ouput should be the gains actually applied by the HAL to
- * the current frame.
- * </p>
+ * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is TRANSFORM_MATRIX.</p>
+ * <p>The output should be the gains actually applied by the camera device to
+ * the current frame.</p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_MODE
*/
public static final Key<float[]> COLOR_CORRECTION_GAINS =
new Key<float[]>("android.colorCorrection.gains", float[].class);
/**
- * <p>
- * Enum for controlling
- * antibanding
- * </p>
+ * <p>The desired setting for the camera device's auto-exposure
+ * algorithm's antibanding compensation.</p>
+ * <p>Some kinds of lighting fixtures, such as some fluorescent
+ * lights, flicker at the rate of the power supply frequency
+ * (60Hz or 50Hz, depending on country). While this is
+ * typically not noticeable to a person, it can be visible to
+ * a camera device. If a camera sets its exposure time to the
+ * wrong value, the flicker may become visible in the
+ * viewfinder as flicker or in a final captured image, as a
+ * set of variable-brightness bands across the image.</p>
+ * <p>Therefore, the auto-exposure routines of camera devices
+ * include antibanding routines that ensure that the chosen
+ * exposure value will not cause such banding. The choice of
+ * exposure time depends on the rate of flicker, which the
+ * camera device can detect automatically, or the expected
+ * rate can be selected by the application using this
+ * control.</p>
+ * <p>A given camera device may not support all of the possible
+ * options for the antibanding mode. The
+ * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_ANTIBANDING_MODES android.control.aeAvailableAntibandingModes} key contains
+ * the available modes for a given camera device.</p>
+ * <p>The default mode is AUTO, which must be supported by all
+ * camera devices.</p>
+ * <p>If manual exposure control is enabled (by setting
+ * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} to OFF),
+ * then this setting has no effect, and the application must
+ * ensure it selects exposure times that do not cause banding
+ * issues. The {@link CaptureResult#STATISTICS_SCENE_FLICKER android.statistics.sceneFlicker} key can assist
+ * the application in this.</p>
+ *
+ * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_ANTIBANDING_MODES
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_MODE
+ * @see CaptureResult#STATISTICS_SCENE_FLICKER
* @see #CONTROL_AE_ANTIBANDING_MODE_OFF
* @see #CONTROL_AE_ANTIBANDING_MODE_50HZ
* @see #CONTROL_AE_ANTIBANDING_MODE_60HZ
@@ -389,42 +454,66 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.control.aeAntibandingMode", int.class);
/**
- * <p>
- * Adjustment to AE target image
- * brightness
- * </p>
- * <p>
- * For example, if EV step is 0.333, '6' will mean an
+ * <p>Adjustment to AE target image
+ * brightness</p>
+ * <p>For example, if EV step is 0.333, '6' will mean an
* exposure compensation of +2 EV; -3 will mean an exposure
- * compensation of -1
- * </p>
+ * compensation of -1</p>
*/
public static final Key<Integer> CONTROL_AE_EXPOSURE_COMPENSATION =
new Key<Integer>("android.control.aeExposureCompensation", int.class);
/**
- * <p>
- * Whether AE is currently locked to its latest
- * calculated values
- * </p>
- * <p>
- * Note that even when AE is locked, the flash may be
- * fired if the AE mode is ON_AUTO_FLASH / ON_ALWAYS_FLASH /
- * ON_AUTO_FLASH_REDEYE.
- * </p>
+ * <p>Whether AE is currently locked to its latest
+ * calculated values.</p>
+ * <p>Note that even when AE is locked, the flash may be
+ * fired if the {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is ON_AUTO_FLASH / ON_ALWAYS_FLASH /
+ * ON_AUTO_FLASH_REDEYE.</p>
+ * <p>If AE precapture is triggered (see {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger})
+ * when AE is already locked, the camera device will not change the exposure time
+ * ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}) and sensitivity ({@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity})
+ * parameters. The flash may be fired if the {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}
+ * is ON_AUTO_FLASH/ON_AUTO_FLASH_REDEYE and the scene is too dark. If the
+ * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is ON_ALWAYS_FLASH, the scene may become overexposed.</p>
+ * <p>See {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} for AE lock related state transition details.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
+ * @see CaptureResult#CONTROL_AE_STATE
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_SENSITIVITY
*/
public static final Key<Boolean> CONTROL_AE_LOCK =
new Key<Boolean>("android.control.aeLock", boolean.class);
/**
- * <p>
- * Whether AE is currently updating the sensor
- * exposure and sensitivity fields
- * </p>
- * <p>
- * Only effective if android.control.mode =
- * AUTO
- * </p>
+ * <p>The desired mode for the camera device's
+ * auto-exposure routine.</p>
+ * <p>This control is only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} is
+ * AUTO.</p>
+ * <p>When set to any of the ON modes, the camera device's
+ * auto-exposure routine is enabled, overriding the
+ * application's selected exposure time, sensor sensitivity,
+ * and frame duration ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime},
+ * {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and
+ * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). If one of the FLASH modes
+ * is selected, the camera device's flash unit controls are
+ * also overridden.</p>
+ * <p>The FLASH modes are only available if the camera device
+ * has a flash unit ({@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} is <code>true</code>).</p>
+ * <p>If flash TORCH mode is desired, this field must be set to
+ * ON or OFF, and {@link CaptureRequest#FLASH_MODE android.flash.mode} set to TORCH.</p>
+ * <p>When set to any of the ON modes, the values chosen by the
+ * camera device auto-exposure routine for the overridden
+ * fields for a given capture will be available in its
+ * CaptureResult.</p>
+ *
+ * @see CaptureRequest#CONTROL_MODE
+ * @see CameraCharacteristics#FLASH_INFO_AVAILABLE
+ * @see CaptureRequest#FLASH_MODE
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see CaptureRequest#SENSOR_SENSITIVITY
* @see #CONTROL_AE_MODE_OFF
* @see #CONTROL_AE_MODE_ON
* @see #CONTROL_AE_MODE_ON_AUTO_FLASH
@@ -435,60 +524,52 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.control.aeMode", int.class);
/**
- * <p>
- * List of areas to use for
- * metering
- * </p>
- * <p>
- * Each area is a rectangle plus weight: xmin, ymin,
- * xmax, ymax, weight. The rectangle is defined inclusive of the
- * specified coordinates.
- * </p><p>
- * The coordinate system is based on the active pixel array,
+ * <p>List of areas to use for
+ * metering.</p>
+ * <p>Each area is a rectangle plus weight: xmin, ymin,
+ * xmax, ymax, weight. The rectangle is defined to be inclusive of the
+ * specified coordinates.</p>
+ * <p>The coordinate system is based on the active pixel array,
* with (0,0) being the top-left pixel in the active pixel array, and
- * (android.sensor.info.activeArraySize.width - 1,
- * android.sensor.info.activeArraySize.height - 1) being the
+ * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
* bottom-right pixel in the active pixel array. The weight
- * should be nonnegative.
- * </p><p>
- * If all regions have 0 weight, then no specific metering area
- * needs to be used by the HAL. If the metering region is
- * outside the current android.scaler.cropRegion, the HAL
- * should ignore the sections outside the region and output the
- * used sections in the frame metadata
- * </p>
+ * should be nonnegative.</p>
+ * <p>If all regions have 0 weight, then no specific metering area
+ * needs to be used by the camera device. If the metering region is
+ * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device
+ * will ignore the sections outside the region and output the
+ * used sections in the frame metadata.</p>
+ *
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
public static final Key<int[]> CONTROL_AE_REGIONS =
new Key<int[]>("android.control.aeRegions", int[].class);
/**
- * <p>
- * Range over which fps can be adjusted to
- * maintain exposure
- * </p>
- * <p>
- * Only constrains AE algorithm, not manual control
- * of android.sensor.exposureTime
- * </p>
+ * <p>Range over which fps can be adjusted to
+ * maintain exposure</p>
+ * <p>Only constrains AE algorithm, not manual control
+ * of {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}</p>
+ *
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
*/
public static final Key<int[]> CONTROL_AE_TARGET_FPS_RANGE =
new Key<int[]>("android.control.aeTargetFpsRange", int[].class);
/**
- * <p>
- * Whether the HAL must trigger precapture
- * metering.
- * </p>
- * <p>
- * This entry is normally set to IDLE, or is not
+ * <p>Whether the camera device will trigger a precapture
+ * metering sequence when it processes this request.</p>
+ * <p>This entry is normally set to IDLE, or is not
* included at all in the request settings. When included and
- * set to START, the HAL must trigger the autoexposure
- * precapture metering sequence.
- * </p><p>
- * The effect of AE precapture trigger depends on the current
- * AE mode and state; see the camera HAL device v3 header for
- * details.
- * </p>
+ * set to START, the camera device will trigger the autoexposure
+ * precapture metering sequence.</p>
+ * <p>The effect of AE precapture trigger depends on the current
+ * AE mode and state; see {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} for AE precapture
+ * state transition details.</p>
+ *
+ * @see CaptureResult#CONTROL_AE_STATE
* @see #CONTROL_AE_PRECAPTURE_TRIGGER_IDLE
* @see #CONTROL_AE_PRECAPTURE_TRIGGER_START
*/
@@ -496,10 +577,17 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.control.aePrecaptureTrigger", int.class);
/**
- * <p>
- * Whether AF is currently enabled, and what
- * mode it is set to
- * </p>
+ * <p>Whether AF is currently enabled, and what
+ * mode it is set to</p>
+ * <p>Only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} = AUTO and the lens is not fixed focus
+ * (i.e. <code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} &gt; 0</code>).</p>
+ * <p>If the lens is controlled by the camera device auto-focus algorithm,
+ * the camera device will report the current AF status in {@link CaptureResult#CONTROL_AF_STATE android.control.afState}
+ * in result metadata.</p>
+ *
+ * @see CaptureResult#CONTROL_AF_STATE
+ * @see CaptureRequest#CONTROL_MODE
+ * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE
* @see #CONTROL_AF_MODE_OFF
* @see #CONTROL_AF_MODE_AUTO
* @see #CONTROL_AF_MODE_MACRO
@@ -511,46 +599,40 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.control.afMode", int.class);
/**
- * <p>
- * List of areas to use for focus
- * estimation
- * </p>
- * <p>
- * Each area is a rectangle plus weight: xmin, ymin,
- * xmax, ymax, weight. The rectangle is defined inclusive of the
- * specified coordinates.
- * </p><p>
- * The coordinate system is based on the active pixel array,
+ * <p>List of areas to use for focus
+ * estimation.</p>
+ * <p>Each area is a rectangle plus weight: xmin, ymin,
+ * xmax, ymax, weight. The rectangle is defined to be inclusive of the
+ * specified coordinates.</p>
+ * <p>The coordinate system is based on the active pixel array,
* with (0,0) being the top-left pixel in the active pixel array, and
- * (android.sensor.info.activeArraySize.width - 1,
- * android.sensor.info.activeArraySize.height - 1) being the
+ * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
* bottom-right pixel in the active pixel array. The weight
- * should be nonnegative.
- * </p><p>
- * If all regions have 0 weight, then no specific focus area
- * needs to be used by the HAL. If the focusing region is
- * outside the current android.scaler.cropRegion, the HAL
- * should ignore the sections outside the region and output the
- * used sections in the frame metadata
- * </p>
+ * should be nonnegative.</p>
+ * <p>If all regions have 0 weight, then no specific focus area
+ * needs to be used by the camera device. If the focusing region is
+ * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device
+ * will ignore the sections outside the region and output the
+ * used sections in the frame metadata.</p>
+ *
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
public static final Key<int[]> CONTROL_AF_REGIONS =
new Key<int[]>("android.control.afRegions", int[].class);
/**
- * <p>
- * Whether the HAL must trigger autofocus.
- * </p>
- * <p>
- * This entry is normally set to IDLE, or is not
- * included at all in the request settings.
- * </p><p>
- * When included and set to START, the HAL must trigger the
- * autofocus algorithm. The effect of AF trigger depends on the
- * current AF mode and state; see the camera HAL device v3
- * header for details. When set to CANCEL, the HAL must cancel
- * any active trigger, and return to initial AF state.
- * </p>
+ * <p>Whether the camera device will trigger autofocus for this request.</p>
+ * <p>This entry is normally set to IDLE, or is not
+ * included at all in the request settings.</p>
+ * <p>When included and set to START, the camera device will trigger the
+ * autofocus algorithm. If autofocus is disabled, this trigger has no effect.</p>
+ * <p>When set to CANCEL, the camera device will cancel any active trigger,
+ * and return to its initial AF state.</p>
+ * <p>See {@link CaptureResult#CONTROL_AF_STATE android.control.afState} for what that means for each AF mode.</p>
+ *
+ * @see CaptureResult#CONTROL_AF_STATE
* @see #CONTROL_AF_TRIGGER_IDLE
* @see #CONTROL_AF_TRIGGER_START
* @see #CONTROL_AF_TRIGGER_CANCEL
@@ -559,28 +641,36 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.control.afTrigger", int.class);
/**
- * <p>
- * Whether AWB is currently locked to its
- * latest calculated values
- * </p>
- * <p>
- * Note that AWB lock is only meaningful for AUTO
+ * <p>Whether AWB is currently locked to its
+ * latest calculated values.</p>
+ * <p>Note that AWB lock is only meaningful for AUTO
* mode; in other modes, AWB is already fixed to a specific
- * setting
- * </p>
+ * setting.</p>
*/
public static final Key<Boolean> CONTROL_AWB_LOCK =
new Key<Boolean>("android.control.awbLock", boolean.class);
/**
- * <p>
- * Whether AWB is currently setting the color
+ * <p>Whether AWB is currently setting the color
* transform fields, and what its illumination target
- * is
- * </p>
- * <p>
- * [BC - AWB lock,AWB modes]
- * </p>
+ * is.</p>
+ * <p>This control is only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} is AUTO.</p>
+ * <p>When set to the ON mode, the camera device's auto white balance
+ * routine is enabled, overriding the application's selected
+ * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and
+ * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}.</p>
+ * <p>When set to the OFF mode, the camera device's auto white balance
+ * routine is disabled. The application manually controls the white
+ * balance by {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains}
+ * and {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}.</p>
+ * <p>When set to any other modes, the camera device's auto white balance
+ * routine is disabled. The camera device uses each particular illumination
+ * target for white balance adjustment.</p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_GAINS
+ * @see CaptureRequest#COLOR_CORRECTION_MODE
+ * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
+ * @see CaptureRequest#CONTROL_MODE
* @see #CONTROL_AWB_MODE_OFF
* @see #CONTROL_AWB_MODE_AUTO
* @see #CONTROL_AWB_MODE_INCANDESCENT
@@ -595,43 +685,39 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.control.awbMode", int.class);
/**
- * <p>
- * List of areas to use for illuminant
- * estimation
- * </p>
- * <p>
- * Only used in AUTO mode.
- * </p><p>
- * Each area is a rectangle plus weight: xmin, ymin,
- * xmax, ymax, weight. The rectangle is defined inclusive of the
- * specified coordinates.
- * </p><p>
- * The coordinate system is based on the active pixel array,
+ * <p>List of areas to use for illuminant
+ * estimation.</p>
+ * <p>Only used in AUTO mode.</p>
+ * <p>Each area is a rectangle plus weight: xmin, ymin,
+ * xmax, ymax, weight. The rectangle is defined to be inclusive of the
+ * specified coordinates.</p>
+ * <p>The coordinate system is based on the active pixel array,
* with (0,0) being the top-left pixel in the active pixel array, and
- * (android.sensor.info.activeArraySize.width - 1,
- * android.sensor.info.activeArraySize.height - 1) being the
+ * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
* bottom-right pixel in the active pixel array. The weight
- * should be nonnegative.
- * </p><p>
- * If all regions have 0 weight, then no specific metering area
- * needs to be used by the HAL. If the metering region is
- * outside the current android.scaler.cropRegion, the HAL
- * should ignore the sections outside the region and output the
- * used sections in the frame metadata
- * </p>
+ * should be nonnegative.</p>
+ * <p>If all regions have 0 weight, then no specific auto-white balance (AWB) area
+ * needs to be used by the camera device. If the AWB region is
+ * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device
+ * will ignore the sections outside the region and output the
+ * used sections in the frame metadata.</p>
+ *
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
public static final Key<int[]> CONTROL_AWB_REGIONS =
new Key<int[]>("android.control.awbRegions", int[].class);
/**
- * <p>
- * Information to 3A routines about the purpose
- * of this capture, to help decide optimal 3A
- * strategy
- * </p>
- * <p>
- * Only used if android.control.mode != OFF.
- * </p>
+ * <p>Information to the camera device 3A (auto-exposure,
+ * auto-focus, auto-white balance) routines about the purpose
+ * of this capture, to help the camera device to decide optimal 3A
+ * strategy.</p>
+ * <p>This control is only effective if <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code>
+ * and any 3A routine is active.</p>
+ *
+ * @see CaptureRequest#CONTROL_MODE
* @see #CONTROL_CAPTURE_INTENT_CUSTOM
* @see #CONTROL_CAPTURE_INTENT_PREVIEW
* @see #CONTROL_CAPTURE_INTENT_STILL_CAPTURE
@@ -643,10 +729,17 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.control.captureIntent", int.class);
/**
- * <p>
- * Whether any special color effect is in use.
- * Only used if android.control.mode != OFF
- * </p>
+ * <p>A special color effect to apply.</p>
+ * <p>When this mode is set, a color effect will be applied
+ * to images produced by the camera device. The interpretation
+ * and implementation of these color effects is left to the
+ * implementor of the camera device, and should not be
+ * depended on to be consistent (or present) across all
+ * devices.</p>
+ * <p>A color effect will only be applied if
+ * {@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF.</p>
+ *
+ * @see CaptureRequest#CONTROL_MODE
* @see #CONTROL_EFFECT_MODE_OFF
* @see #CONTROL_EFFECT_MODE_MONO
* @see #CONTROL_EFFECT_MODE_NEGATIVE
@@ -661,23 +754,50 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.control.effectMode", int.class);
/**
- * <p>
- * Overall mode of 3A control
- * routines
- * </p>
+ * <p>Overall mode of 3A control
+ * routines.</p>
+ * <p>High-level 3A control. When set to OFF, all 3A control
+ * by the camera device is disabled. The application must set the fields for
+ * capture parameters itself.</p>
+ * <p>When set to AUTO, the individual algorithm controls in
+ * android.control.* are in effect, such as {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}.</p>
+ * <p>When set to USE_SCENE_MODE, the individual controls in
+ * android.control.* are mostly disabled, and the camera device implements
+ * one of the scene mode settings (such as ACTION, SUNSET, or PARTY)
+ * as it wishes. The camera device scene mode 3A settings are provided by
+ * android.control.sceneModeOverrides.</p>
+ * <p>When set to OFF_KEEP_STATE, it is similar to OFF mode, the only difference
+ * is that this frame will not be used by camera device background 3A statistics
+ * update, as if this frame is never captured. This mode can be used in the scenario
+ * where the application doesn't want a 3A manual control capture to affect
+ * the subsequent auto 3A capture results.</p>
+ *
+ * @see CaptureRequest#CONTROL_AF_MODE
* @see #CONTROL_MODE_OFF
* @see #CONTROL_MODE_AUTO
* @see #CONTROL_MODE_USE_SCENE_MODE
+ * @see #CONTROL_MODE_OFF_KEEP_STATE
*/
public static final Key<Integer> CONTROL_MODE =
new Key<Integer>("android.control.mode", int.class);
/**
- * <p>
- * Which scene mode is active when
- * android.control.mode = SCENE_MODE
- * </p>
- * @see #CONTROL_SCENE_MODE_UNSUPPORTED
+ * <p>A camera mode optimized for conditions typical in a particular
+ * capture setting.</p>
+ * <p>This is the mode that that is active when
+ * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} == USE_SCENE_MODE</code>. Aside from FACE_PRIORITY,
+ * these modes will disable {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode},
+ * {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}, and {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} while in use.</p>
+ * <p>The interpretation and implementation of these scene modes is left
+ * to the implementor of the camera device. Their behavior will not be
+ * consistent across all devices, and any given device may only implement
+ * a subset of these modes.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_AF_MODE
+ * @see CaptureRequest#CONTROL_AWB_MODE
+ * @see CaptureRequest#CONTROL_MODE
+ * @see #CONTROL_SCENE_MODE_DISABLED
* @see #CONTROL_SCENE_MODE_FACE_PRIORITY
* @see #CONTROL_SCENE_MODE_ACTION
* @see #CONTROL_SCENE_MODE_PORTRAIT
@@ -699,24 +819,27 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.control.sceneMode", int.class);
/**
- * <p>
- * Whether video stabilization is
- * active
- * </p>
- * <p>
- * If enabled, video stabilization can modify the
- * android.scaler.cropRegion to keep the video stream
- * stabilized
- * </p>
+ * <p>Whether video stabilization is
+ * active</p>
+ * <p>If enabled, video stabilization can modify the
+ * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream
+ * stabilized</p>
+ *
+ * @see CaptureRequest#SCALER_CROP_REGION
*/
public static final Key<Boolean> CONTROL_VIDEO_STABILIZATION_MODE =
new Key<Boolean>("android.control.videoStabilizationMode", boolean.class);
/**
- * <p>
- * Operation mode for edge
- * enhancement
- * </p>
+ * <p>Operation mode for edge
+ * enhancement.</p>
+ * <p>Edge/sharpness/detail enhancement. OFF means no
+ * enhancement will be applied by the camera device.</p>
+ * <p>FAST/HIGH_QUALITY both mean camera device determined enhancement
+ * will be applied. HIGH_QUALITY mode indicates that the
+ * camera device will use the highest-quality enhancement algorithms,
+ * even if it slows down capture rate. FAST means the camera device will
+ * not slow down capture rate when applying edge enhancement.</p>
* @see #EDGE_MODE_OFF
* @see #EDGE_MODE_FAST
* @see #EDGE_MODE_HIGH_QUALITY
@@ -725,9 +848,25 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.edge.mode", int.class);
/**
- * <p>
- * Select flash operation mode
- * </p>
+ * <p>The desired mode for for the camera device's flash control.</p>
+ * <p>This control is only effective when flash unit is available
+ * (<code>{@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} == true</code>).</p>
+ * <p>When this control is used, the {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} must be set to ON or OFF.
+ * Otherwise, the camera device auto-exposure related flash control (ON_AUTO_FLASH,
+ * ON_ALWAYS_FLASH, or ON_AUTO_FLASH_REDEYE) will override this control.</p>
+ * <p>When set to OFF, the camera device will not fire flash for this capture.</p>
+ * <p>When set to SINGLE, the camera device will fire flash regardless of the camera
+ * device's auto-exposure routine's result. When used in still capture case, this
+ * control should be used along with AE precapture metering sequence
+ * ({@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}), otherwise, the image may be incorrectly exposed.</p>
+ * <p>When set to TORCH, the flash will be on continuously. This mode can be used
+ * for use cases such as preview, auto-focus assist, still capture, or video recording.</p>
+ * <p>The flash status will be reported by {@link CaptureResult#FLASH_STATE android.flash.state} in the capture result metadata.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
+ * @see CameraCharacteristics#FLASH_INFO_AVAILABLE
+ * @see CaptureResult#FLASH_STATE
* @see #FLASH_MODE_OFF
* @see #FLASH_MODE_SINGLE
* @see #FLASH_MODE_TORCH
@@ -736,128 +875,167 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.flash.mode", int.class);
/**
- * <p>
- * GPS coordinates to include in output JPEG
- * EXIF
- * </p>
+ * <p>Set operational mode for hot pixel correction.</p>
+ * <p>Hotpixel correction interpolates out, or otherwise removes, pixels
+ * that do not accurately encode the incoming light (i.e. pixels that
+ * are stuck at an arbitrary value).</p>
+ * @see #HOT_PIXEL_MODE_OFF
+ * @see #HOT_PIXEL_MODE_FAST
+ * @see #HOT_PIXEL_MODE_HIGH_QUALITY
+ */
+ public static final Key<Integer> HOT_PIXEL_MODE =
+ new Key<Integer>("android.hotPixel.mode", int.class);
+
+ /**
+ * <p>GPS coordinates to include in output JPEG
+ * EXIF</p>
*/
public static final Key<double[]> JPEG_GPS_COORDINATES =
new Key<double[]>("android.jpeg.gpsCoordinates", double[].class);
/**
- * <p>
- * 32 characters describing GPS algorithm to
- * include in EXIF
- * </p>
+ * <p>32 characters describing GPS algorithm to
+ * include in EXIF</p>
*/
public static final Key<String> JPEG_GPS_PROCESSING_METHOD =
new Key<String>("android.jpeg.gpsProcessingMethod", String.class);
/**
- * <p>
- * Time GPS fix was made to include in
- * EXIF
- * </p>
+ * <p>Time GPS fix was made to include in
+ * EXIF</p>
*/
public static final Key<Long> JPEG_GPS_TIMESTAMP =
new Key<Long>("android.jpeg.gpsTimestamp", long.class);
/**
- * <p>
- * Orientation of JPEG image to
- * write
- * </p>
+ * <p>Orientation of JPEG image to
+ * write</p>
*/
public static final Key<Integer> JPEG_ORIENTATION =
new Key<Integer>("android.jpeg.orientation", int.class);
/**
- * <p>
- * Compression quality of the final JPEG
- * image
- * </p>
- * <p>
- * 85-95 is typical usage range
- * </p>
+ * <p>Compression quality of the final JPEG
+ * image</p>
+ * <p>85-95 is typical usage range</p>
*/
public static final Key<Byte> JPEG_QUALITY =
new Key<Byte>("android.jpeg.quality", byte.class);
/**
- * <p>
- * Compression quality of JPEG
- * thumbnail
- * </p>
+ * <p>Compression quality of JPEG
+ * thumbnail</p>
*/
public static final Key<Byte> JPEG_THUMBNAIL_QUALITY =
new Key<Byte>("android.jpeg.thumbnailQuality", byte.class);
/**
- * <p>
- * Resolution of embedded JPEG
- * thumbnail
- * </p>
+ * <p>Resolution of embedded JPEG thumbnail</p>
+ * <p>When set to (0, 0) value, the JPEG EXIF will not contain thumbnail,
+ * but the captured JPEG will still be a valid image.</p>
+ * <p>When a jpeg image capture is issued, the thumbnail size selected should have
+ * the same aspect ratio as the jpeg image.</p>
*/
public static final Key<android.hardware.camera2.Size> JPEG_THUMBNAIL_SIZE =
new Key<android.hardware.camera2.Size>("android.jpeg.thumbnailSize", android.hardware.camera2.Size.class);
/**
- * <p>
- * Size of the lens aperture
- * </p>
- * <p>
- * Will not be supported on most devices. Can only
- * pick from supported list
- * </p>
+ * <p>The ratio of lens focal length to the effective
+ * aperture diameter.</p>
+ * <p>This will only be supported on the camera devices that
+ * have variable aperture lens. The aperture value can only be
+ * one of the values listed in {@link CameraCharacteristics#LENS_INFO_AVAILABLE_APERTURES android.lens.info.availableApertures}.</p>
+ * <p>When this is supported and {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is OFF,
+ * this can be set along with {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime},
+ * {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}
+ * to achieve manual exposure control.</p>
+ * <p>The requested aperture value may take several frames to reach the
+ * requested value; the camera device will report the current (intermediate)
+ * aperture size in capture result metadata while the aperture is changing.
+ * While the aperture is still changing, {@link CaptureResult#LENS_STATE android.lens.state} will be set to MOVING.</p>
+ * <p>When this is supported and {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is one of
+ * the ON modes, this will be overridden by the camera device
+ * auto-exposure algorithm, the overridden values are then provided
+ * back to the user in the corresponding result.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CameraCharacteristics#LENS_INFO_AVAILABLE_APERTURES
+ * @see CaptureResult#LENS_STATE
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see CaptureRequest#SENSOR_SENSITIVITY
*/
public static final Key<Float> LENS_APERTURE =
new Key<Float>("android.lens.aperture", float.class);
/**
- * <p>
- * State of lens neutral density
- * filter(s)
- * </p>
- * <p>
- * Will not be supported on most devices. Can only
- * pick from supported list
- * </p>
+ * <p>State of lens neutral density filter(s).</p>
+ * <p>This will not be supported on most camera devices. On devices
+ * where this is supported, this may only be set to one of the
+ * values included in {@link CameraCharacteristics#LENS_INFO_AVAILABLE_FILTER_DENSITIES android.lens.info.availableFilterDensities}.</p>
+ * <p>Lens filters are typically used to lower the amount of light the
+ * sensor is exposed to (measured in steps of EV). As used here, an EV
+ * step is the standard logarithmic representation, which are
+ * non-negative, and inversely proportional to the amount of light
+ * hitting the sensor. For example, setting this to 0 would result
+ * in no reduction of the incoming light, and setting this to 2 would
+ * mean that the filter is set to reduce incoming light by two stops
+ * (allowing 1/4 of the prior amount of light to the sensor).</p>
+ * <p>It may take several frames before the lens filter density changes
+ * to the requested value. While the filter density is still changing,
+ * {@link CaptureResult#LENS_STATE android.lens.state} will be set to MOVING.</p>
+ *
+ * @see CameraCharacteristics#LENS_INFO_AVAILABLE_FILTER_DENSITIES
+ * @see CaptureResult#LENS_STATE
*/
public static final Key<Float> LENS_FILTER_DENSITY =
new Key<Float>("android.lens.filterDensity", float.class);
/**
- * <p>
- * Lens optical zoom setting
- * </p>
- * <p>
- * Will not be supported on most devices.
- * </p>
+ * <p>The current lens focal length; used for optical zoom.</p>
+ * <p>This setting controls the physical focal length of the camera
+ * device's lens. Changing the focal length changes the field of
+ * view of the camera device, and is usually used for optical zoom.</p>
+ * <p>Like {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance} and {@link CaptureRequest#LENS_APERTURE android.lens.aperture}, this
+ * setting won't be applied instantaneously, and it may take several
+ * frames before the lens can change to the requested focal length.
+ * While the focal length is still changing, {@link CaptureResult#LENS_STATE android.lens.state} will
+ * be set to MOVING.</p>
+ * <p>This is expected not to be supported on most devices.</p>
+ *
+ * @see CaptureRequest#LENS_APERTURE
+ * @see CaptureRequest#LENS_FOCUS_DISTANCE
+ * @see CaptureResult#LENS_STATE
*/
public static final Key<Float> LENS_FOCAL_LENGTH =
new Key<Float>("android.lens.focalLength", float.class);
/**
- * <p>
- * Distance to plane of sharpest focus,
- * measured from frontmost surface of the lens
- * </p>
- * <p>
- * 0 = infinity focus. Used value should be clamped
- * to (0,minimum focus distance)
- * </p>
+ * <p>Distance to plane of sharpest focus,
+ * measured from frontmost surface of the lens</p>
+ * <p>0 means infinity focus. Used value will be clamped
+ * to [0, {@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance}].</p>
+ * <p>Like {@link CaptureRequest#LENS_FOCAL_LENGTH android.lens.focalLength}, this setting won't be applied
+ * instantaneously, and it may take several frames before the lens
+ * can move to the requested focus distance. While the lens is still moving,
+ * {@link CaptureResult#LENS_STATE android.lens.state} will be set to MOVING.</p>
+ *
+ * @see CaptureRequest#LENS_FOCAL_LENGTH
+ * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE
+ * @see CaptureResult#LENS_STATE
*/
public static final Key<Float> LENS_FOCUS_DISTANCE =
new Key<Float>("android.lens.focusDistance", float.class);
/**
- * <p>
- * Whether optical image stabilization is
- * enabled.
- * </p>
- * <p>
- * Will not be supported on most devices.
- * </p>
+ * <p>Sets whether the camera device uses optical image stabilization (OIS)
+ * when capturing images.</p>
+ * <p>OIS is used to compensate for motion blur due to small movements of
+ * the camera during capture. Unlike digital image stabilization, OIS makes
+ * use of mechanical elements to stabilize the camera sensor, and thus
+ * allows for longer exposure times before camera shake becomes
+ * apparent.</p>
+ * <p>This is not expected to be supported on most devices.</p>
* @see #LENS_OPTICAL_STABILIZATION_MODE_OFF
* @see #LENS_OPTICAL_STABILIZATION_MODE_ON
*/
@@ -865,10 +1043,15 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.lens.opticalStabilizationMode", int.class);
/**
- * <p>
- * Mode of operation for the noise reduction
- * algorithm
- * </p>
+ * <p>Mode of operation for the noise reduction
+ * algorithm</p>
+ * <p>Noise filtering control. OFF means no noise reduction
+ * will be applied by the camera device.</p>
+ * <p>FAST/HIGH_QUALITY both mean camera device determined noise filtering
+ * will be applied. HIGH_QUALITY mode indicates that the camera device
+ * will use the highest-quality noise filtering algorithms,
+ * even if it slows down capture rate. FAST means the camera device should not
+ * slow down capture rate when applying noise filtering.</p>
* @see #NOISE_REDUCTION_MODE_OFF
* @see #NOISE_REDUCTION_MODE_FAST
* @see #NOISE_REDUCTION_MODE_HIGH_QUALITY
@@ -877,44 +1060,33 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.noiseReduction.mode", int.class);
/**
- * <p>
- * An application-specified ID for the current
+ * <p>An application-specified ID for the current
* request. Must be maintained unchanged in output
- * frame
- * </p>
- *
+ * frame</p>
* @hide
*/
public static final Key<Integer> REQUEST_ID =
new Key<Integer>("android.request.id", int.class);
/**
- * <p>
- * (x, y, width, height).
- * </p><p>
- * A rectangle with the top-level corner of (x,y) and size
+ * <p>(x, y, width, height).</p>
+ * <p>A rectangle with the top-level corner of (x,y) and size
* (width, height). The region of the sensor that is used for
* output. Each stream must use this rectangle to produce its
* output, cropping to a smaller region if necessary to
- * maintain the stream's aspect ratio.
- * </p><p>
- * HAL2.x uses only (x, y, width)
- * </p>
- * <p>
- * Any additional per-stream cropping must be done to
- * maximize the final pixel area of the stream.
- * </p><p>
- * For example, if the crop region is set to a 4:3 aspect
+ * maintain the stream's aspect ratio.</p>
+ * <p>HAL2.x uses only (x, y, width)</p>
+ * <p>Any additional per-stream cropping must be done to
+ * maximize the final pixel area of the stream.</p>
+ * <p>For example, if the crop region is set to a 4:3 aspect
* ratio, then 4:3 streams should use the exact crop
* region. 16:9 streams should further crop vertically
- * (letterbox).
- * </p><p>
- * Conversely, if the crop region is set to a 16:9, then 4:3
+ * (letterbox).</p>
+ * <p>Conversely, if the crop region is set to a 16:9, then 4:3
* outputs should crop horizontally (pillarbox), and 16:9
* streams should match exactly. These additional crops must
- * be centered within the crop region.
- * </p><p>
- * The output streams must maintain square pixels at all
+ * be centered within the crop region.</p>
+ * <p>The output streams must maintain square pixels at all
* times, no matter what the relative aspect ratios of the
* crop region and the stream are. Negative values for
* corner are allowed for raw output if full pixel array is
@@ -923,69 +1095,189 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
* for raw output, where only a few fixed scales may be
* possible. The width and height of the crop region cannot
* be set to be smaller than floor( activeArraySize.width /
- * android.scaler.maxDigitalZoom ) and floor(
- * activeArraySize.height / android.scaler.maxDigitalZoom),
- * respectively.
- * </p>
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} ) and floor(
+ * activeArraySize.height /
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom}), respectively.</p>
+ *
+ * @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
*/
public static final Key<android.graphics.Rect> SCALER_CROP_REGION =
new Key<android.graphics.Rect>("android.scaler.cropRegion", android.graphics.Rect.class);
/**
- * <p>
- * Duration each pixel is exposed to
- * light.
- * </p><p>
- * If the sensor can't expose this exact duration, it should shorten the
- * duration exposed to the nearest possible value (rather than expose longer).
- * </p>
- * <p>
- * 1/10000 - 30 sec range. No bulb mode
- * </p>
+ * <p>Duration each pixel is exposed to
+ * light.</p>
+ * <p>If the sensor can't expose this exact duration, it should shorten the
+ * duration exposed to the nearest possible value (rather than expose longer).</p>
+ * <p>1/10000 - 30 sec range. No bulb mode</p>
*/
public static final Key<Long> SENSOR_EXPOSURE_TIME =
new Key<Long>("android.sensor.exposureTime", long.class);
/**
- * <p>
- * Duration from start of frame exposure to
- * start of next frame exposure
- * </p>
- * <p>
- * Exposure time has priority, so duration is set to
- * max(duration, exposure time + overhead)
- * </p>
+ * <p>Duration from start of frame exposure to
+ * start of next frame exposure.</p>
+ * <p>The maximum frame rate that can be supported by a camera subsystem is
+ * a function of many factors:</p>
+ * <ul>
+ * <li>Requested resolutions of output image streams</li>
+ * <li>Availability of binning / skipping modes on the imager</li>
+ * <li>The bandwidth of the imager interface</li>
+ * <li>The bandwidth of the various ISP processing blocks</li>
+ * </ul>
+ * <p>Since these factors can vary greatly between different ISPs and
+ * sensors, the camera abstraction tries to represent the bandwidth
+ * restrictions with as simple a model as possible.</p>
+ * <p>The model presented has the following characteristics:</p>
+ * <ul>
+ * <li>The image sensor is always configured to output the smallest
+ * resolution possible given the application's requested output stream
+ * sizes. The smallest resolution is defined as being at least as large
+ * as the largest requested output stream size; the camera pipeline must
+ * never digitally upsample sensor data when the crop region covers the
+ * whole sensor. In general, this means that if only small output stream
+ * resolutions are configured, the sensor can provide a higher frame
+ * rate.</li>
+ * <li>Since any request may use any or all the currently configured
+ * output streams, the sensor and ISP must be configured to support
+ * scaling a single capture to all the streams at the same time. This
+ * means the camera pipeline must be ready to produce the largest
+ * requested output size without any delay. Therefore, the overall
+ * frame rate of a given configured stream set is governed only by the
+ * largest requested stream resolution.</li>
+ * <li>Using more than one output stream in a request does not affect the
+ * frame duration.</li>
+ * <li>Certain format-streams may need to do additional background processing
+ * before data is consumed/produced by that stream. These processors
+ * can run concurrently to the rest of the camera pipeline, but
+ * cannot process more than 1 capture at a time.</li>
+ * </ul>
+ * <p>The necessary information for the application, given the model above,
+ * is provided via the {@link CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS android.scaler.availableMinFrameDurations} field.
+ * These are used to determine the maximum frame rate / minimum frame
+ * duration that is possible for a given stream configuration.</p>
+ * <p>Specifically, the application can use the following rules to
+ * determine the minimum frame duration it can request from the camera
+ * device:</p>
+ * <ol>
+ * <li>Let the set of currently configured input/output streams
+ * be called <code>S</code>.</li>
+ * <li>Find the minimum frame durations for each stream in <code>S</code>, by
+ * looking it up in {@link CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS android.scaler.availableMinFrameDurations} (with
+ * its respective size/format). Let this set of frame durations be called
+ * <code>F</code>.</li>
+ * <li>For any given request <code>R</code>, the minimum frame duration allowed
+ * for <code>R</code> is the maximum out of all values in <code>F</code>. Let the streams
+ * used in <code>R</code> be called <code>S_r</code>.</li>
+ * </ol>
+ * <p>If none of the streams in <code>S_r</code> have a stall time (listed in
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS android.scaler.availableStallDurations}), then the frame duration in
+ * <code>F</code> determines the steady state frame rate that the application will
+ * get if it uses <code>R</code> as a repeating request. Let this special kind
+ * of request be called <code>Rsimple</code>.</p>
+ * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved
+ * by a single capture of a new request <code>Rstall</code> (which has at least
+ * one in-use stream with a non-0 stall time) and if <code>Rstall</code> has the
+ * same minimum frame duration this will not cause a frame rate loss
+ * if all buffers from the previous <code>Rstall</code> have already been
+ * delivered.</p>
+ * <p>For more details about stalling, see
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS android.scaler.availableStallDurations}.</p>
+ *
+ * @see CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS
+ * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS
*/
public static final Key<Long> SENSOR_FRAME_DURATION =
new Key<Long>("android.sensor.frameDuration", long.class);
/**
- * <p>
- * Gain applied to image data. Must be
+ * <p>Gain applied to image data. Must be
* implemented through analog gain only if set to values
- * below 'maximum analog sensitivity'.
- * </p><p>
- * If the sensor can't apply this exact gain, it should lessen the
- * gain to the nearest possible value (rather than gain more).
- * </p>
- * <p>
- * ISO 12232:2006 REI method
- * </p>
+ * below 'maximum analog sensitivity'.</p>
+ * <p>If the sensor can't apply this exact gain, it should lessen the
+ * gain to the nearest possible value (rather than gain more).</p>
+ * <p>ISO 12232:2006 REI method</p>
*/
public static final Key<Integer> SENSOR_SENSITIVITY =
new Key<Integer>("android.sensor.sensitivity", int.class);
/**
- * <p>
- * State of the face detector
- * unit
- * </p>
- * <p>
- * Whether face detection is enabled, and whether it
+ * <p>A pixel <code>[R, G_even, G_odd, B]</code> that supplies the test pattern
+ * when {@link CaptureRequest#SENSOR_TEST_PATTERN_MODE android.sensor.testPatternMode} is SOLID_COLOR.</p>
+ * <p>Each color channel is treated as an unsigned 32-bit integer.
+ * The camera device then uses the most significant X bits
+ * that correspond to how many bits are in its Bayer raw sensor
+ * output.</p>
+ * <p>For example, a sensor with RAW10 Bayer output would use the
+ * 10 most significant bits from each color channel.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_TEST_PATTERN_MODE
+ */
+ public static final Key<int[]> SENSOR_TEST_PATTERN_DATA =
+ new Key<int[]>("android.sensor.testPatternData", int[].class);
+
+ /**
+ * <p>When enabled, the sensor sends a test pattern instead of
+ * doing a real exposure from the camera.</p>
+ * <p>When a test pattern is enabled, all manual sensor controls specified
+ * by android.sensor.* should be ignored. All other controls should
+ * work as normal.</p>
+ * <p>For example, if manual flash is enabled, flash firing should still
+ * occur (and that the test pattern remain unmodified, since the flash
+ * would not actually affect it).</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ * @see #SENSOR_TEST_PATTERN_MODE_OFF
+ * @see #SENSOR_TEST_PATTERN_MODE_SOLID_COLOR
+ * @see #SENSOR_TEST_PATTERN_MODE_COLOR_BARS
+ * @see #SENSOR_TEST_PATTERN_MODE_COLOR_BARS_FADE_TO_GRAY
+ * @see #SENSOR_TEST_PATTERN_MODE_PN9
+ * @see #SENSOR_TEST_PATTERN_MODE_CUSTOM1
+ */
+ public static final Key<Integer> SENSOR_TEST_PATTERN_MODE =
+ new Key<Integer>("android.sensor.testPatternMode", int.class);
+
+ /**
+ * <p>Quality of lens shading correction applied
+ * to the image data.</p>
+ * <p>When set to OFF mode, no lens shading correction will be applied by the
+ * camera device, and an identity lens shading map data will be provided
+ * if <code>{@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON</code>. For example, for lens
+ * shading map with size specified as <code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ]</code>,
+ * the output {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} for this case will be an identity map
+ * shown below:</p>
+ * <pre><code>[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ]
+ * </code></pre>
+ * <p>When set to other modes, lens shading correction will be applied by the
+ * camera device. Applications can request lens shading map data by setting
+ * {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} to ON, and then the camera device will provide
+ * lens shading map data in {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}, with size specified
+ * by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}.</p>
+ *
+ * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE
+ * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
+ * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
+ * @see #SHADING_MODE_OFF
+ * @see #SHADING_MODE_FAST
+ * @see #SHADING_MODE_HIGH_QUALITY
+ */
+ public static final Key<Integer> SHADING_MODE =
+ new Key<Integer>("android.shading.mode", int.class);
+
+ /**
+ * <p>State of the face detector
+ * unit</p>
+ * <p>Whether face detection is enabled, and whether it
* should output just the basic fields or the full set of
* fields. Value must be one of the
- * android.statistics.info.availableFaceDetectModes.
- * </p>
+ * {@link CameraCharacteristics#STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES android.statistics.info.availableFaceDetectModes}.</p>
+ *
+ * @see CameraCharacteristics#STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES
* @see #STATISTICS_FACE_DETECT_MODE_OFF
* @see #STATISTICS_FACE_DETECT_MODE_SIMPLE
* @see #STATISTICS_FACE_DETECT_MODE_FULL
@@ -994,15 +1286,13 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.statistics.faceDetectMode", int.class);
/**
- * <p>
- * Whether the HAL needs to output the lens
- * shading map in output result metadata
- * </p>
- * <p>
- * When set to ON,
- * android.statistics.lensShadingMap must be provided in
- * the output result metdata.
- * </p>
+ * <p>Whether the camera device will output the lens
+ * shading map in output result metadata.</p>
+ * <p>When set to ON,
+ * {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} must be provided in
+ * the output result metadata.</p>
+ *
+ * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
* @see #STATISTICS_LENS_SHADING_MAP_MODE_OFF
* @see #STATISTICS_LENS_SHADING_MAP_MODE_ON
*/
@@ -1010,61 +1300,107 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.statistics.lensShadingMapMode", int.class);
/**
- * <p>
- * Table mapping blue input values to output
- * values
- * </p>
- * <p>
- * Tonemapping / contrast / gamma curve for the blue
- * channel, to use when android.tonemap.mode is CONTRAST_CURVE.
- * </p><p>
- * See android.tonemap.curveRed for more details.
- * </p>
+ * <p>Tonemapping / contrast / gamma curve for the blue
+ * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * CONTRAST_CURVE.</p>
+ * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p>
+ *
+ * @see CaptureRequest#TONEMAP_CURVE_RED
+ * @see CaptureRequest#TONEMAP_MODE
*/
public static final Key<float[]> TONEMAP_CURVE_BLUE =
new Key<float[]>("android.tonemap.curveBlue", float[].class);
/**
- * <p>
- * Table mapping green input values to output
- * values
- * </p>
- * <p>
- * Tonemapping / contrast / gamma curve for the green
- * channel, to use when android.tonemap.mode is CONTRAST_CURVE.
- * </p><p>
- * See android.tonemap.curveRed for more details.
- * </p>
+ * <p>Tonemapping / contrast / gamma curve for the green
+ * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * CONTRAST_CURVE.</p>
+ * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p>
+ *
+ * @see CaptureRequest#TONEMAP_CURVE_RED
+ * @see CaptureRequest#TONEMAP_MODE
*/
public static final Key<float[]> TONEMAP_CURVE_GREEN =
new Key<float[]>("android.tonemap.curveGreen", float[].class);
/**
- * <p>
- * Table mapping red input values to output
- * values
- * </p>
- * <p>
- * Tonemapping / contrast / gamma curve for the red
- * channel, to use when android.tonemap.mode is CONTRAST_CURVE.
- * </p><p>
- * Since the input and output ranges may vary depending on
- * the camera pipeline, the input and output pixel values
- * are represented by normalized floating-point values
- * between 0 and 1, with 0 == black and 1 == white.
- * </p><p>
- * The curve should be linearly interpolated between the
- * defined points. The points will be listed in increasing
- * order of P_IN. For example, if the array is: [0.0, 0.0,
- * 0.3, 0.5, 1.0, 1.0], then the input->output mapping
- * for a few sample points would be: 0 -> 0, 0.15 ->
- * 0.25, 0.3 -> 0.5, 0.5 -> 0.64
- * </p>
+ * <p>Tonemapping / contrast / gamma curve for the red
+ * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * CONTRAST_CURVE.</p>
+ * <p>Each channel's curve is defined by an array of control points:</p>
+ * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} =
+ * [ P0in, P0out, P1in, P1out, P2in, P2out, P3in, P3out, ..., PNin, PNout ]
+ * 2 &lt;= N &lt;= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre>
+ * <p>These are sorted in order of increasing <code>Pin</code>; it is always
+ * guaranteed that input values 0.0 and 1.0 are included in the list to
+ * define a complete mapping. For input values between control points,
+ * the camera device must linearly interpolate between the control
+ * points.</p>
+ * <p>Each curve can have an independent number of points, and the number
+ * of points can be less than max (that is, the request doesn't have to
+ * always provide a curve with number of points equivalent to
+ * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p>
+ * <p>A few examples, and their corresponding graphical mappings; these
+ * only specify the red channel and the precision is limited to 4
+ * digits, for conciseness.</p>
+ * <p>Linear mapping:</p>
+ * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 0, 1.0, 1.0 ]
+ * </code></pre>
+ * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p>
+ * <p>Invert mapping:</p>
+ * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 1.0, 1.0, 0 ]
+ * </code></pre>
+ * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p>
+ * <p>Gamma 1/2.2 mapping, with 16 control points:</p>
+ * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [
+ * 0.0000, 0.0000, 0.0667, 0.2920, 0.1333, 0.4002, 0.2000, 0.4812,
+ * 0.2667, 0.5484, 0.3333, 0.6069, 0.4000, 0.6594, 0.4667, 0.7072,
+ * 0.5333, 0.7515, 0.6000, 0.7928, 0.6667, 0.8317, 0.7333, 0.8685,
+ * 0.8000, 0.9035, 0.8667, 0.9370, 0.9333, 0.9691, 1.0000, 1.0000 ]
+ * </code></pre>
+ * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p>
+ * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p>
+ * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [
+ * 0.0000, 0.0000, 0.0667, 0.2864, 0.1333, 0.4007, 0.2000, 0.4845,
+ * 0.2667, 0.5532, 0.3333, 0.6125, 0.4000, 0.6652, 0.4667, 0.7130,
+ * 0.5333, 0.7569, 0.6000, 0.7977, 0.6667, 0.8360, 0.7333, 0.8721,
+ * 0.8000, 0.9063, 0.8667, 0.9389, 0.9333, 0.9701, 1.0000, 1.0000 ]
+ * </code></pre>
+ * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
+ *
+ * @see CaptureRequest#TONEMAP_CURVE_RED
+ * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
+ * @see CaptureRequest#TONEMAP_MODE
*/
public static final Key<float[]> TONEMAP_CURVE_RED =
new Key<float[]>("android.tonemap.curveRed", float[].class);
/**
+ * <p>High-level global contrast/gamma/tonemapping control.</p>
+ * <p>When switching to an application-defined contrast curve by setting
+ * {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} to CONTRAST_CURVE, the curve is defined
+ * per-channel with a set of <code>(in, out)</code> points that specify the
+ * mapping from input high-bit-depth pixel value to the output
+ * low-bit-depth value. Since the actual pixel ranges of both input
+ * and output may change depending on the camera pipeline, the values
+ * are specified by normalized floating-point numbers.</p>
+ * <p>More-complex color mapping operations such as 3D color look-up
+ * tables, selective chroma enhancement, or other non-linear color
+ * transforms will be disabled when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * CONTRAST_CURVE.</p>
+ * <p>When using either FAST or HIGH_QUALITY, the camera device will
+ * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed},
+ * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, and {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}.
+ * These values are always available, and as close as possible to the
+ * actually used nonlinear/nonglobal transforms.</p>
+ * <p>If a request is sent with TRANSFORM_MATRIX with the camera device's
+ * provided curve in FAST or HIGH_QUALITY, the image's tonemap will be
+ * roughly the same.</p>
+ *
+ * @see CaptureRequest#TONEMAP_CURVE_BLUE
+ * @see CaptureRequest#TONEMAP_CURVE_GREEN
+ * @see CaptureRequest#TONEMAP_CURVE_RED
+ * @see CaptureRequest#TONEMAP_MODE
* @see #TONEMAP_MODE_CONTRAST_CURVE
* @see #TONEMAP_MODE_FAST
* @see #TONEMAP_MODE_HIGH_QUALITY
@@ -1073,49 +1409,59 @@ public final class CaptureRequest extends CameraMetadata implements Parcelable {
new Key<Integer>("android.tonemap.mode", int.class);
/**
- * <p>
- * This LED is nominally used to indicate to the user
+ * <p>This LED is nominally used to indicate to the user
* that the camera is powered on and may be streaming images back to the
* Application Processor. In certain rare circumstances, the OS may
* disable this when video is processed locally and not transmitted to
- * any untrusted applications.
- * </p><p>
- * In particular, the LED *must* always be on when the data could be
- * transmitted off the device. The LED *should* always be on whenever
- * data is stored locally on the device.
- * </p><p>
- * The LED *may* be off if a trusted application is using the data that
- * doesn't violate the above rules.
- * </p>
- *
+ * any untrusted applications.</p>
+ * <p>In particular, the LED <em>must</em> always be on when the data could be
+ * transmitted off the device. The LED <em>should</em> always be on whenever
+ * data is stored locally on the device.</p>
+ * <p>The LED <em>may</em> be off if a trusted application is using the data that
+ * doesn't violate the above rules.</p>
* @hide
*/
public static final Key<Boolean> LED_TRANSMIT =
new Key<Boolean>("android.led.transmit", boolean.class);
/**
- * <p>
- * Whether black-level compensation is locked
- * to its current values, or is free to vary
- * </p>
- * <p>
- * When set to ON, the values used for black-level
- * compensation must not change until the lock is set to
- * OFF
- * </p><p>
- * Since changes to certain capture parameters (such as
+ * <p>Whether black-level compensation is locked
+ * to its current values, or is free to vary.</p>
+ * <p>When set to ON, the values used for black-level
+ * compensation will not change until the lock is set to
+ * OFF.</p>
+ * <p>Since changes to certain capture parameters (such as
* exposure time) may require resetting of black level
- * compensation, the HAL must report whether setting the
- * black level lock was successful in the output result
- * metadata.
- * </p><p>
- * The black level locking must happen at the sensor, and not at the ISP.
- * If for some reason black level locking is no longer legal (for example,
- * the analog gain has changed, which forces black levels to be
- * recalculated), then the HAL is free to override this request (and it
- * must report 'OFF' when this does happen) until the next time locking
- * is legal again.
- * </p>
+ * compensation, the camera device must report whether setting
+ * the black level lock was successful in the output result
+ * metadata.</p>
+ * <p>For example, if a sequence of requests is as follows:</p>
+ * <ul>
+ * <li>Request 1: Exposure = 10ms, Black level lock = OFF</li>
+ * <li>Request 2: Exposure = 10ms, Black level lock = ON</li>
+ * <li>Request 3: Exposure = 10ms, Black level lock = ON</li>
+ * <li>Request 4: Exposure = 20ms, Black level lock = ON</li>
+ * <li>Request 5: Exposure = 20ms, Black level lock = ON</li>
+ * <li>Request 6: Exposure = 20ms, Black level lock = ON</li>
+ * </ul>
+ * <p>And the exposure change in Request 4 requires the camera
+ * device to reset the black level offsets, then the output
+ * result metadata is expected to be:</p>
+ * <ul>
+ * <li>Result 1: Exposure = 10ms, Black level lock = OFF</li>
+ * <li>Result 2: Exposure = 10ms, Black level lock = ON</li>
+ * <li>Result 3: Exposure = 10ms, Black level lock = ON</li>
+ * <li>Result 4: Exposure = 20ms, Black level lock = OFF</li>
+ * <li>Result 5: Exposure = 20ms, Black level lock = ON</li>
+ * <li>Result 6: Exposure = 20ms, Black level lock = ON</li>
+ * </ul>
+ * <p>This indicates to the application that on frame 4, black
+ * levels were reset due to exposure value changes, and pixel
+ * values may not be consistent across captures.</p>
+ * <p>The camera device will maintain the lock to the extent
+ * possible, only overriding the lock to OFF when changes to
+ * other request parameters require a black level recalculation
+ * or reset.</p>
*/
public static final Key<Boolean> BLACK_LEVEL_LOCK =
new Key<Boolean>("android.blackLevel.lock", boolean.class);
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 535b963..0f2c7f7 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;
/**
@@ -124,104 +122,308 @@ public final class CaptureResult extends CameraMetadata {
* modify the comment blocks at the start or end.
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~*/
+
/**
- * <p>
- * A color transform matrix to use to transform
- * from sensor RGB color space to output linear sRGB color space
- * </p>
- * <p>
- * This matrix is either set by HAL when the request
- * android.colorCorrection.mode is not TRANSFORM_MATRIX, or
+ * <p>A color transform matrix to use to transform
+ * from sensor RGB color space to output linear sRGB color space</p>
+ * <p>This matrix is either set by the camera device when the request
+ * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not TRANSFORM_MATRIX, or
* directly by the application in the request when the
- * android.colorCorrection.mode is TRANSFORM_MATRIX.
- * </p><p>
- * In the latter case, the HAL may round the matrix to account
- * for precision issues; the final rounded matrix should be
- * reported back in this matrix result metadata.
- * </p>
+ * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is TRANSFORM_MATRIX.</p>
+ * <p>In the latter case, the camera device may round the matrix to account
+ * for precision issues; the final rounded matrix should be reported back
+ * in this matrix result metadata. The transform should keep the magnitude
+ * of the output color values within <code>[0, 1.0]</code> (assuming input color
+ * values is within the normalized range <code>[0, 1.0]</code>), or clipping may occur.</p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_MODE
*/
public static final Key<Rational[]> COLOR_CORRECTION_TRANSFORM =
new Key<Rational[]>("android.colorCorrection.transform", Rational[].class);
/**
- * <p>
- * Gains applying to Bayer color channels for
- * white-balance
- * </p>
- * <p>
- * The 4-channel white-balance gains are defined in
- * the order of [R G_even G_odd B], where G_even is the gain
- * for green pixels on even rows of the output, and G_odd
- * is the gain for greenpixels on the odd rows. if a HAL
+ * <p>Gains applying to Bayer raw color channels for
+ * white-balance.</p>
+ * <p>The 4-channel white-balance gains are defined in
+ * the order of <code>[R G_even G_odd B]</code>, where <code>G_even</code> is the gain
+ * for green pixels on even rows of the output, and <code>G_odd</code>
+ * is the gain for green pixels on the odd rows. if a HAL
* does not support a separate gain for even/odd green channels,
- * it should use the G_even value,and write G_odd equal to
- * G_even in the output result metadata.
- * </p><p>
- * This array is either set by HAL when the request
- * android.colorCorrection.mode is not TRANSFORM_MATRIX, or
+ * it should use the <code>G_even</code> value, and write <code>G_odd</code> equal to
+ * <code>G_even</code> in the output result metadata.</p>
+ * <p>This array is either set by the camera device when the request
+ * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not TRANSFORM_MATRIX, or
* directly by the application in the request when the
- * android.colorCorrection.mode is TRANSFORM_MATRIX.
- * </p><p>
- * The ouput should be the gains actually applied by the HAL to
- * the current frame.
- * </p>
+ * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is TRANSFORM_MATRIX.</p>
+ * <p>The output should be the gains actually applied by the camera device to
+ * the current frame.</p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_MODE
*/
public static final Key<float[]> COLOR_CORRECTION_GAINS =
new Key<float[]>("android.colorCorrection.gains", float[].class);
/**
- * <p>
- * The ID sent with the latest
- * CAMERA2_TRIGGER_PRECAPTURE_METERING call
- * </p>
- * <p>
- * Must be 0 if no
+ * <p>The ID sent with the latest
+ * CAMERA2_TRIGGER_PRECAPTURE_METERING call</p>
+ * <p>Must be 0 if no
* CAMERA2_TRIGGER_PRECAPTURE_METERING trigger received yet
* by HAL. Always updated even if AE algorithm ignores the
- * trigger
- * </p>
- *
+ * trigger</p>
* @hide
*/
public static final Key<Integer> CONTROL_AE_PRECAPTURE_ID =
new Key<Integer>("android.control.aePrecaptureId", int.class);
/**
- * <p>
- * List of areas to use for
- * metering
- * </p>
- * <p>
- * Each area is a rectangle plus weight: xmin, ymin,
- * xmax, ymax, weight. The rectangle is defined inclusive of the
- * specified coordinates.
- * </p><p>
- * The coordinate system is based on the active pixel array,
+ * <p>The desired mode for the camera device's
+ * auto-exposure routine.</p>
+ * <p>This control is only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} is
+ * AUTO.</p>
+ * <p>When set to any of the ON modes, the camera device's
+ * auto-exposure routine is enabled, overriding the
+ * application's selected exposure time, sensor sensitivity,
+ * and frame duration ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime},
+ * {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and
+ * {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}). If one of the FLASH modes
+ * is selected, the camera device's flash unit controls are
+ * also overridden.</p>
+ * <p>The FLASH modes are only available if the camera device
+ * has a flash unit ({@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} is <code>true</code>).</p>
+ * <p>If flash TORCH mode is desired, this field must be set to
+ * ON or OFF, and {@link CaptureRequest#FLASH_MODE android.flash.mode} set to TORCH.</p>
+ * <p>When set to any of the ON modes, the values chosen by the
+ * camera device auto-exposure routine for the overridden
+ * fields for a given capture will be available in its
+ * CaptureResult.</p>
+ *
+ * @see CaptureRequest#CONTROL_MODE
+ * @see CameraCharacteristics#FLASH_INFO_AVAILABLE
+ * @see CaptureRequest#FLASH_MODE
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see CaptureRequest#SENSOR_SENSITIVITY
+ * @see #CONTROL_AE_MODE_OFF
+ * @see #CONTROL_AE_MODE_ON
+ * @see #CONTROL_AE_MODE_ON_AUTO_FLASH
+ * @see #CONTROL_AE_MODE_ON_ALWAYS_FLASH
+ * @see #CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
+ */
+ public static final Key<Integer> CONTROL_AE_MODE =
+ new Key<Integer>("android.control.aeMode", int.class);
+
+ /**
+ * <p>List of areas to use for
+ * metering.</p>
+ * <p>Each area is a rectangle plus weight: xmin, ymin,
+ * xmax, ymax, weight. The rectangle is defined to be inclusive of the
+ * specified coordinates.</p>
+ * <p>The coordinate system is based on the active pixel array,
* with (0,0) being the top-left pixel in the active pixel array, and
- * (android.sensor.info.activeArraySize.width - 1,
- * android.sensor.info.activeArraySize.height - 1) being the
+ * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
* bottom-right pixel in the active pixel array. The weight
- * should be nonnegative.
- * </p><p>
- * If all regions have 0 weight, then no specific metering area
- * needs to be used by the HAL. If the metering region is
- * outside the current android.scaler.cropRegion, the HAL
- * should ignore the sections outside the region and output the
- * used sections in the frame metadata
- * </p>
+ * should be nonnegative.</p>
+ * <p>If all regions have 0 weight, then no specific metering area
+ * needs to be used by the camera device. If the metering region is
+ * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device
+ * will ignore the sections outside the region and output the
+ * used sections in the frame metadata.</p>
+ *
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
public static final Key<int[]> CONTROL_AE_REGIONS =
new Key<int[]>("android.control.aeRegions", int[].class);
/**
- * <p>
- * Current state of AE algorithm
- * </p>
- * <p>
- * Whenever the AE algorithm state changes, a
- * MSG_AUTOEXPOSURE notification must be send if a
- * notification callback is registered.
- * </p>
+ * <p>Current state of AE algorithm</p>
+ * <p>Switching between or enabling AE modes ({@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}) always
+ * resets the AE state to INACTIVE. Similarly, switching between {@link CaptureRequest#CONTROL_MODE android.control.mode},
+ * or {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} if <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} == USE_SCENE_MODE</code> resets all
+ * the algorithm states to INACTIVE.</p>
+ * <p>The camera device can do several state transitions between two results, if it is
+ * allowed by the state transition table. For example: INACTIVE may never actually be
+ * seen in a result.</p>
+ * <p>The state in the result is the state for this image (in sync with this image): if
+ * AE state becomes CONVERGED, then the image data associated with this result should
+ * be good to use.</p>
+ * <p>Below are state transition tables for different AE modes.</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center"></td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Camera device auto exposure algorithm is disabled</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <p>When {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is AE_MODE_ON_*:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Camera device initiates AE scan</td>
+ * <td align="center">SEARCHING</td>
+ * <td align="center">Values changing</td>
+ * </tr>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td align="center">LOCKED</td>
+ * <td align="center">Values locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">SEARCHING</td>
+ * <td align="center">Camera device finishes AE scan</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Good values, not changing</td>
+ * </tr>
+ * <tr>
+ * <td align="center">SEARCHING</td>
+ * <td align="center">Camera device finishes AE scan</td>
+ * <td align="center">FLASH_REQUIRED</td>
+ * <td align="center">Converged but too dark w/o flash</td>
+ * </tr>
+ * <tr>
+ * <td align="center">SEARCHING</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td align="center">LOCKED</td>
+ * <td align="center">Values locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Camera device initiates AE scan</td>
+ * <td align="center">SEARCHING</td>
+ * <td align="center">Values changing</td>
+ * </tr>
+ * <tr>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td align="center">LOCKED</td>
+ * <td align="center">Values locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">FLASH_REQUIRED</td>
+ * <td align="center">Camera device initiates AE scan</td>
+ * <td align="center">SEARCHING</td>
+ * <td align="center">Values changing</td>
+ * </tr>
+ * <tr>
+ * <td align="center">FLASH_REQUIRED</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td align="center">LOCKED</td>
+ * <td align="center">Values locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">LOCKED</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+ * <td align="center">SEARCHING</td>
+ * <td align="center">Values not good after unlock</td>
+ * </tr>
+ * <tr>
+ * <td align="center">LOCKED</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Values good after unlock</td>
+ * </tr>
+ * <tr>
+ * <td align="center">LOCKED</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+ * <td align="center">FLASH_REQUIRED</td>
+ * <td align="center">Exposure good, but too dark</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PRECAPTURE</td>
+ * <td align="center">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Ready for high-quality capture</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PRECAPTURE</td>
+ * <td align="center">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td align="center">LOCKED</td>
+ * <td align="center">Ready for high-quality capture</td>
+ * </tr>
+ * <tr>
+ * <td align="center">Any state</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START</td>
+ * <td align="center">PRECAPTURE</td>
+ * <td align="center">Start AE precapture metering sequence</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <p>For the above table, the camera device may skip reporting any state changes that happen
+ * without application intervention (i.e. mode switch, trigger, locking). Any state that
+ * can be skipped in that manner is called a transient state.</p>
+ * <p>For example, for above AE modes (AE_MODE_ON_*), in addition to the state transitions
+ * listed in above table, it is also legal for the camera device to skip one or more
+ * transient states between two results. See below table for examples:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Camera device finished AE scan</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Values are already good, transient states are skipped by camera device.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">Any state</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
+ * <td align="center">FLASH_REQUIRED</td>
+ * <td align="center">Converged but too dark w/o flash after a precapture sequence, transient states are skipped by camera device.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">Any state</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Converged after a precapture sequence, transient states are skipped by camera device.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Camera device finished AE scan</td>
+ * <td align="center">FLASH_REQUIRED</td>
+ * <td align="center">Converged but too dark w/o flash after a new scan, transient states are skipped by camera device.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">FLASH_REQUIRED</td>
+ * <td align="center">Camera device finished AE scan</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Converged after a new scan, transient states are skipped by camera device.</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * @see CaptureRequest#CONTROL_AE_LOCK
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
+ * @see CaptureRequest#CONTROL_MODE
+ * @see CaptureRequest#CONTROL_SCENE_MODE
* @see #CONTROL_AE_STATE_INACTIVE
* @see #CONTROL_AE_STATE_SEARCHING
* @see #CONTROL_AE_STATE_CONVERGED
@@ -233,10 +435,17 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.control.aeState", int.class);
/**
- * <p>
- * Whether AF is currently enabled, and what
- * mode it is set to
- * </p>
+ * <p>Whether AF is currently enabled, and what
+ * mode it is set to</p>
+ * <p>Only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} = AUTO and the lens is not fixed focus
+ * (i.e. <code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} &gt; 0</code>).</p>
+ * <p>If the lens is controlled by the camera device auto-focus algorithm,
+ * the camera device will report the current AF status in {@link CaptureResult#CONTROL_AF_STATE android.control.afState}
+ * in result metadata.</p>
+ *
+ * @see CaptureResult#CONTROL_AF_STATE
+ * @see CaptureRequest#CONTROL_MODE
+ * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE
* @see #CONTROL_AF_MODE_OFF
* @see #CONTROL_AF_MODE_AUTO
* @see #CONTROL_AF_MODE_MACRO
@@ -248,41 +457,415 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.control.afMode", int.class);
/**
- * <p>
- * List of areas to use for focus
- * estimation
- * </p>
- * <p>
- * Each area is a rectangle plus weight: xmin, ymin,
- * xmax, ymax, weight. The rectangle is defined inclusive of the
- * specified coordinates.
- * </p><p>
- * The coordinate system is based on the active pixel array,
+ * <p>List of areas to use for focus
+ * estimation.</p>
+ * <p>Each area is a rectangle plus weight: xmin, ymin,
+ * xmax, ymax, weight. The rectangle is defined to be inclusive of the
+ * specified coordinates.</p>
+ * <p>The coordinate system is based on the active pixel array,
* with (0,0) being the top-left pixel in the active pixel array, and
- * (android.sensor.info.activeArraySize.width - 1,
- * android.sensor.info.activeArraySize.height - 1) being the
+ * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
* bottom-right pixel in the active pixel array. The weight
- * should be nonnegative.
- * </p><p>
- * If all regions have 0 weight, then no specific focus area
- * needs to be used by the HAL. If the focusing region is
- * outside the current android.scaler.cropRegion, the HAL
- * should ignore the sections outside the region and output the
- * used sections in the frame metadata
- * </p>
+ * should be nonnegative.</p>
+ * <p>If all regions have 0 weight, then no specific focus area
+ * needs to be used by the camera device. If the focusing region is
+ * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device
+ * will ignore the sections outside the region and output the
+ * used sections in the frame metadata.</p>
+ *
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
public static final Key<int[]> CONTROL_AF_REGIONS =
new Key<int[]>("android.control.afRegions", int[].class);
/**
- * <p>
- * Current state of AF algorithm
- * </p>
- * <p>
- * Whenever the AF algorithm state changes, a
- * MSG_AUTOFOCUS notification must be send if a notification
- * callback is registered.
- * </p>
+ * <p>Current state of AF algorithm.</p>
+ * <p>Switching between or enabling AF modes ({@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}) always
+ * resets the AF state to INACTIVE. Similarly, switching between {@link CaptureRequest#CONTROL_MODE android.control.mode},
+ * or {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} if <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} == USE_SCENE_MODE</code> resets all
+ * the algorithm states to INACTIVE.</p>
+ * <p>The camera device can do several state transitions between two results, if it is
+ * allowed by the state transition table. For example: INACTIVE may never actually be
+ * seen in a result.</p>
+ * <p>The state in the result is the state for this image (in sync with this image): if
+ * AF state becomes FOCUSED, then the image data associated with this result should
+ * be sharp.</p>
+ * <p>Below are state transition tables for different AF modes.</p>
+ * <p>When {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} is AF_MODE_OFF or AF_MODE_EDOF:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center"></td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Never changes</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <p>When {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} is AF_MODE_AUTO or AF_MODE_MACRO:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">ACTIVE_SCAN</td>
+ * <td align="center">Start AF sweep, Lens now moving</td>
+ * </tr>
+ * <tr>
+ * <td align="center">ACTIVE_SCAN</td>
+ * <td align="center">AF sweep done</td>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">Focused, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">ACTIVE_SCAN</td>
+ * <td align="center">AF sweep done</td>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">Not focused, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">ACTIVE_SCAN</td>
+ * <td align="center">AF_CANCEL</td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Cancel/reset AF, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">AF_CANCEL</td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Cancel/reset AF</td>
+ * </tr>
+ * <tr>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">ACTIVE_SCAN</td>
+ * <td align="center">Start new sweep, Lens now moving</td>
+ * </tr>
+ * <tr>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">AF_CANCEL</td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Cancel/reset AF</td>
+ * </tr>
+ * <tr>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">ACTIVE_SCAN</td>
+ * <td align="center">Start new sweep, Lens now moving</td>
+ * </tr>
+ * <tr>
+ * <td align="center">Any state</td>
+ * <td align="center">Mode change</td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center"></td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <p>For the above table, the camera device may skip reporting any state changes that happen
+ * without application intervention (i.e. mode switch, trigger, locking). Any state that
+ * can be skipped in that manner is called a transient state.</p>
+ * <p>For example, for these AF modes (AF_MODE_AUTO and AF_MODE_MACRO), in addition to the
+ * state transitions listed in above table, it is also legal for the camera device to skip
+ * one or more transient states between two results. See below table for examples:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">Focus is already good or good after a scan, lens is now locked.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">Focus failed after a scan, lens is now locked.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">Focus is already good or good after a scan, lens is now locked.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">Focus is good after a scan, lens is not locked.</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <p>When {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} is AF_MODE_CONTINUOUS_VIDEO:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Camera device initiates new scan</td>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">Start AF scan, Lens now moving</td>
+ * </tr>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">AF state query, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">Camera device completes current scan</td>
+ * <td align="center">PASSIVE_FOCUSED</td>
+ * <td align="center">End AF scan, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">Camera device fails current scan</td>
+ * <td align="center">PASSIVE_UNFOCUSED</td>
+ * <td align="center">End AF scan, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">Immediate trans. If focus is good, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">Immediate trans. if focus is bad, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">AF_CANCEL</td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Reset lens position, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_FOCUSED</td>
+ * <td align="center">Camera device initiates new scan</td>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">Start AF scan, Lens now moving</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_UNFOCUSED</td>
+ * <td align="center">Camera device initiates new scan</td>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">Start AF scan, Lens now moving</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_FOCUSED</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">Immediate trans. Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_UNFOCUSED</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">Immediate trans. Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">No effect</td>
+ * </tr>
+ * <tr>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">AF_CANCEL</td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Restart AF scan</td>
+ * </tr>
+ * <tr>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">No effect</td>
+ * </tr>
+ * <tr>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">AF_CANCEL</td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Restart AF scan</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <p>When {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} is AF_MODE_CONTINUOUS_PICTURE:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Camera device initiates new scan</td>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">Start AF scan, Lens now moving</td>
+ * </tr>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">AF state query, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">Camera device completes current scan</td>
+ * <td align="center">PASSIVE_FOCUSED</td>
+ * <td align="center">End AF scan, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">Camera device fails current scan</td>
+ * <td align="center">PASSIVE_UNFOCUSED</td>
+ * <td align="center">End AF scan, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">Eventual trans. once focus good, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">Eventual trans. if cannot focus, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">AF_CANCEL</td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Reset lens position, Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_FOCUSED</td>
+ * <td align="center">Camera device initiates new scan</td>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">Start AF scan, Lens now moving</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_UNFOCUSED</td>
+ * <td align="center">Camera device initiates new scan</td>
+ * <td align="center">PASSIVE_SCAN</td>
+ * <td align="center">Start AF scan, Lens now moving</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_FOCUSED</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">Immediate trans. Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">PASSIVE_UNFOCUSED</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">Immediate trans. Lens now locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">No effect</td>
+ * </tr>
+ * <tr>
+ * <td align="center">FOCUSED_LOCKED</td>
+ * <td align="center">AF_CANCEL</td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Restart AF scan</td>
+ * </tr>
+ * <tr>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">AF_TRIGGER</td>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">No effect</td>
+ * </tr>
+ * <tr>
+ * <td align="center">NOT_FOCUSED_LOCKED</td>
+ * <td align="center">AF_CANCEL</td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Restart AF scan</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <p>When switch between AF_MODE_CONTINUOUS_* (CAF modes) and AF_MODE_AUTO/AF_MODE_MACRO
+ * (AUTO modes), the initial INACTIVE or PASSIVE_SCAN states may be skipped by the
+ * camera device. When a trigger is included in a mode switch request, the trigger
+ * will be evaluated in the context of the new mode in the request.
+ * See below table for examples:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">any state</td>
+ * <td align="center">CAF--&gt;AUTO mode switch</td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Mode switch without trigger, initial state must be INACTIVE</td>
+ * </tr>
+ * <tr>
+ * <td align="center">any state</td>
+ * <td align="center">CAF--&gt;AUTO mode switch with AF_TRIGGER</td>
+ * <td align="center">trigger-reachable states from INACTIVE</td>
+ * <td align="center">Mode switch with trigger, INACTIVE is skipped</td>
+ * </tr>
+ * <tr>
+ * <td align="center">any state</td>
+ * <td align="center">AUTO--&gt;CAF mode switch</td>
+ * <td align="center">passively reachable states from INACTIVE</td>
+ * <td align="center">Mode switch without trigger, passive transient state is skipped</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * @see CaptureRequest#CONTROL_AF_MODE
+ * @see CaptureRequest#CONTROL_MODE
+ * @see CaptureRequest#CONTROL_SCENE_MODE
* @see #CONTROL_AF_STATE_INACTIVE
* @see #CONTROL_AF_STATE_PASSIVE_SCAN
* @see #CONTROL_AF_STATE_PASSIVE_FOCUSED
@@ -295,30 +878,37 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.control.afState", int.class);
/**
- * <p>
- * The ID sent with the latest
- * CAMERA2_TRIGGER_AUTOFOCUS call
- * </p>
- * <p>
- * Must be 0 if no CAMERA2_TRIGGER_AUTOFOCUS trigger
+ * <p>The ID sent with the latest
+ * CAMERA2_TRIGGER_AUTOFOCUS call</p>
+ * <p>Must be 0 if no CAMERA2_TRIGGER_AUTOFOCUS trigger
* received yet by HAL. Always updated even if AF algorithm
- * ignores the trigger
- * </p>
- *
+ * ignores the trigger</p>
* @hide
*/
public static final Key<Integer> CONTROL_AF_TRIGGER_ID =
new Key<Integer>("android.control.afTriggerId", int.class);
/**
- * <p>
- * Whether AWB is currently setting the color
+ * <p>Whether AWB is currently setting the color
* transform fields, and what its illumination target
- * is
- * </p>
- * <p>
- * [BC - AWB lock,AWB modes]
- * </p>
+ * is.</p>
+ * <p>This control is only effective if {@link CaptureRequest#CONTROL_MODE android.control.mode} is AUTO.</p>
+ * <p>When set to the ON mode, the camera device's auto white balance
+ * routine is enabled, overriding the application's selected
+ * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and
+ * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}.</p>
+ * <p>When set to the OFF mode, the camera device's auto white balance
+ * routine is disabled. The application manually controls the white
+ * balance by {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains}
+ * and {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}.</p>
+ * <p>When set to any other modes, the camera device's auto white balance
+ * routine is disabled. The camera device uses each particular illumination
+ * target for white balance adjustment.</p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_GAINS
+ * @see CaptureRequest#COLOR_CORRECTION_MODE
+ * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
+ * @see CaptureRequest#CONTROL_MODE
* @see #CONTROL_AWB_MODE_OFF
* @see #CONTROL_AWB_MODE_AUTO
* @see #CONTROL_AWB_MODE_INCANDESCENT
@@ -333,43 +923,152 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.control.awbMode", int.class);
/**
- * <p>
- * List of areas to use for illuminant
- * estimation
- * </p>
- * <p>
- * Only used in AUTO mode.
- * </p><p>
- * Each area is a rectangle plus weight: xmin, ymin,
- * xmax, ymax, weight. The rectangle is defined inclusive of the
- * specified coordinates.
- * </p><p>
- * The coordinate system is based on the active pixel array,
+ * <p>List of areas to use for illuminant
+ * estimation.</p>
+ * <p>Only used in AUTO mode.</p>
+ * <p>Each area is a rectangle plus weight: xmin, ymin,
+ * xmax, ymax, weight. The rectangle is defined to be inclusive of the
+ * specified coordinates.</p>
+ * <p>The coordinate system is based on the active pixel array,
* with (0,0) being the top-left pixel in the active pixel array, and
- * (android.sensor.info.activeArraySize.width - 1,
- * android.sensor.info.activeArraySize.height - 1) being the
+ * ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
* bottom-right pixel in the active pixel array. The weight
- * should be nonnegative.
- * </p><p>
- * If all regions have 0 weight, then no specific metering area
- * needs to be used by the HAL. If the metering region is
- * outside the current android.scaler.cropRegion, the HAL
- * should ignore the sections outside the region and output the
- * used sections in the frame metadata
- * </p>
+ * should be nonnegative.</p>
+ * <p>If all regions have 0 weight, then no specific auto-white balance (AWB) area
+ * needs to be used by the camera device. If the AWB region is
+ * outside the current {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, the camera device
+ * will ignore the sections outside the region and output the
+ * used sections in the frame metadata.</p>
+ *
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
public static final Key<int[]> CONTROL_AWB_REGIONS =
new Key<int[]>("android.control.awbRegions", int[].class);
/**
- * <p>
- * Current state of AWB algorithm
- * </p>
- * <p>
- * Whenever the AWB algorithm state changes, a
- * MSG_AUTOWHITEBALANCE notification must be send if a
- * notification callback is registered.
- * </p>
+ * <p>Current state of AWB algorithm</p>
+ * <p>Switching between or enabling AWB modes ({@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode}) always
+ * resets the AWB state to INACTIVE. Similarly, switching between {@link CaptureRequest#CONTROL_MODE android.control.mode},
+ * or {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} if <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} == USE_SCENE_MODE</code> resets all
+ * the algorithm states to INACTIVE.</p>
+ * <p>The camera device can do several state transitions between two results, if it is
+ * allowed by the state transition table. So INACTIVE may never actually be seen in
+ * a result.</p>
+ * <p>The state in the result is the state for this image (in sync with this image): if
+ * AWB state becomes CONVERGED, then the image data associated with this result should
+ * be good to use.</p>
+ * <p>Below are state transition tables for different AWB modes.</p>
+ * <p>When <code>{@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} != AWB_MODE_AUTO</code>:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center"></td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Camera device auto white balance algorithm is disabled</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <p>When {@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} is AWB_MODE_AUTO:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Camera device initiates AWB scan</td>
+ * <td align="center">SEARCHING</td>
+ * <td align="center">Values changing</td>
+ * </tr>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
+ * <td align="center">LOCKED</td>
+ * <td align="center">Values locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">SEARCHING</td>
+ * <td align="center">Camera device finishes AWB scan</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Good values, not changing</td>
+ * </tr>
+ * <tr>
+ * <td align="center">SEARCHING</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
+ * <td align="center">LOCKED</td>
+ * <td align="center">Values locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Camera device initiates AWB scan</td>
+ * <td align="center">SEARCHING</td>
+ * <td align="center">Values changing</td>
+ * </tr>
+ * <tr>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
+ * <td align="center">LOCKED</td>
+ * <td align="center">Values locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">LOCKED</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
+ * <td align="center">SEARCHING</td>
+ * <td align="center">Values not good after unlock</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <p>For the above table, the camera device may skip reporting any state changes that happen
+ * without application intervention (i.e. mode switch, trigger, locking). Any state that
+ * can be skipped in that manner is called a transient state.</p>
+ * <p>For example, for this AWB mode (AWB_MODE_AUTO), in addition to the state transitions
+ * listed in above table, it is also legal for the camera device to skip one or more
+ * transient states between two results. See below table for examples:</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th align="center">State</th>
+ * <th align="center">Transition Cause</th>
+ * <th align="center">New State</th>
+ * <th align="center">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Camera device finished AWB scan</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Values are already good, transient states are skipped by camera device.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">LOCKED</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Values good after unlock, transient states are skipped by camera device.</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * @see CaptureRequest#CONTROL_AWB_LOCK
+ * @see CaptureRequest#CONTROL_AWB_MODE
+ * @see CaptureRequest#CONTROL_MODE
+ * @see CaptureRequest#CONTROL_SCENE_MODE
* @see #CONTROL_AWB_STATE_INACTIVE
* @see #CONTROL_AWB_STATE_SEARCHING
* @see #CONTROL_AWB_STATE_CONVERGED
@@ -379,22 +1078,43 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.control.awbState", int.class);
/**
- * <p>
- * Overall mode of 3A control
- * routines
- * </p>
+ * <p>Overall mode of 3A control
+ * routines.</p>
+ * <p>High-level 3A control. When set to OFF, all 3A control
+ * by the camera device is disabled. The application must set the fields for
+ * capture parameters itself.</p>
+ * <p>When set to AUTO, the individual algorithm controls in
+ * android.control.* are in effect, such as {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode}.</p>
+ * <p>When set to USE_SCENE_MODE, the individual controls in
+ * android.control.* are mostly disabled, and the camera device implements
+ * one of the scene mode settings (such as ACTION, SUNSET, or PARTY)
+ * as it wishes. The camera device scene mode 3A settings are provided by
+ * android.control.sceneModeOverrides.</p>
+ * <p>When set to OFF_KEEP_STATE, it is similar to OFF mode, the only difference
+ * is that this frame will not be used by camera device background 3A statistics
+ * update, as if this frame is never captured. This mode can be used in the scenario
+ * where the application doesn't want a 3A manual control capture to affect
+ * the subsequent auto 3A capture results.</p>
+ *
+ * @see CaptureRequest#CONTROL_AF_MODE
* @see #CONTROL_MODE_OFF
* @see #CONTROL_MODE_AUTO
* @see #CONTROL_MODE_USE_SCENE_MODE
+ * @see #CONTROL_MODE_OFF_KEEP_STATE
*/
public static final Key<Integer> CONTROL_MODE =
new Key<Integer>("android.control.mode", int.class);
/**
- * <p>
- * Operation mode for edge
- * enhancement
- * </p>
+ * <p>Operation mode for edge
+ * enhancement.</p>
+ * <p>Edge/sharpness/detail enhancement. OFF means no
+ * enhancement will be applied by the camera device.</p>
+ * <p>FAST/HIGH_QUALITY both mean camera device determined enhancement
+ * will be applied. HIGH_QUALITY mode indicates that the
+ * camera device will use the highest-quality enhancement algorithms,
+ * even if it slows down capture rate. FAST means the camera device will
+ * not slow down capture rate when applying edge enhancement.</p>
* @see #EDGE_MODE_OFF
* @see #EDGE_MODE_FAST
* @see #EDGE_MODE_HIGH_QUALITY
@@ -403,9 +1123,25 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.edge.mode", int.class);
/**
- * <p>
- * Select flash operation mode
- * </p>
+ * <p>The desired mode for for the camera device's flash control.</p>
+ * <p>This control is only effective when flash unit is available
+ * (<code>{@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} == true</code>).</p>
+ * <p>When this control is used, the {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} must be set to ON or OFF.
+ * Otherwise, the camera device auto-exposure related flash control (ON_AUTO_FLASH,
+ * ON_ALWAYS_FLASH, or ON_AUTO_FLASH_REDEYE) will override this control.</p>
+ * <p>When set to OFF, the camera device will not fire flash for this capture.</p>
+ * <p>When set to SINGLE, the camera device will fire flash regardless of the camera
+ * device's auto-exposure routine's result. When used in still capture case, this
+ * control should be used along with AE precapture metering sequence
+ * ({@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}), otherwise, the image may be incorrectly exposed.</p>
+ * <p>When set to TORCH, the flash will be on continuously. This mode can be used
+ * for use cases such as preview, auto-focus assist, still capture, or video recording.</p>
+ * <p>The flash status will be reported by {@link CaptureResult#FLASH_STATE android.flash.state} in the capture result metadata.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
+ * @see CameraCharacteristics#FLASH_INFO_AVAILABLE
+ * @see CaptureResult#FLASH_STATE
* @see #FLASH_MODE_OFF
* @see #FLASH_MODE_SINGLE
* @see #FLASH_MODE_TORCH
@@ -414,10 +1150,13 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.flash.mode", int.class);
/**
- * <p>
- * Current state of the flash
- * unit
- * </p>
+ * <p>Current state of the flash
+ * unit.</p>
+ * <p>When the camera device doesn't have flash unit
+ * (i.e. <code>{@link CameraCharacteristics#FLASH_INFO_AVAILABLE android.flash.info.available} == false</code>), this state will always be UNAVAILABLE.
+ * Other states indicate the current flash status.</p>
+ *
+ * @see CameraCharacteristics#FLASH_INFO_AVAILABLE
* @see #FLASH_STATE_UNAVAILABLE
* @see #FLASH_STATE_CHARGING
* @see #FLASH_STATE_READY
@@ -427,140 +1166,181 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.flash.state", int.class);
/**
- * <p>
- * GPS coordinates to include in output JPEG
- * EXIF
- * </p>
+ * <p>List of <code>(x, y)</code> coordinates of hot/defective pixels on the
+ * sensor, where <code>(x, y)</code> lies between <code>(0, 0)</code>, which is the top-left
+ * of the pixel array, and the width,height of the pixel array given in
+ * {@link CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE android.sensor.info.pixelArraySize}. This may include hot pixels
+ * that lie outside of the active array bounds given by
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE
+ */
+ public static final Key<int[]> HOT_PIXEL_MAP =
+ new Key<int[]>("android.hotPixel.map", int[].class);
+
+ /**
+ * <p>Set operational mode for hot pixel correction.</p>
+ * <p>Hotpixel correction interpolates out, or otherwise removes, pixels
+ * that do not accurately encode the incoming light (i.e. pixels that
+ * are stuck at an arbitrary value).</p>
+ * @see #HOT_PIXEL_MODE_OFF
+ * @see #HOT_PIXEL_MODE_FAST
+ * @see #HOT_PIXEL_MODE_HIGH_QUALITY
+ */
+ public static final Key<Integer> HOT_PIXEL_MODE =
+ new Key<Integer>("android.hotPixel.mode", int.class);
+
+ /**
+ * <p>GPS coordinates to include in output JPEG
+ * EXIF</p>
*/
public static final Key<double[]> JPEG_GPS_COORDINATES =
new Key<double[]>("android.jpeg.gpsCoordinates", double[].class);
/**
- * <p>
- * 32 characters describing GPS algorithm to
- * include in EXIF
- * </p>
+ * <p>32 characters describing GPS algorithm to
+ * include in EXIF</p>
*/
public static final Key<String> JPEG_GPS_PROCESSING_METHOD =
new Key<String>("android.jpeg.gpsProcessingMethod", String.class);
/**
- * <p>
- * Time GPS fix was made to include in
- * EXIF
- * </p>
+ * <p>Time GPS fix was made to include in
+ * EXIF</p>
*/
public static final Key<Long> JPEG_GPS_TIMESTAMP =
new Key<Long>("android.jpeg.gpsTimestamp", long.class);
/**
- * <p>
- * Orientation of JPEG image to
- * write
- * </p>
+ * <p>Orientation of JPEG image to
+ * write</p>
*/
public static final Key<Integer> JPEG_ORIENTATION =
new Key<Integer>("android.jpeg.orientation", int.class);
/**
- * <p>
- * Compression quality of the final JPEG
- * image
- * </p>
- * <p>
- * 85-95 is typical usage range
- * </p>
+ * <p>Compression quality of the final JPEG
+ * image</p>
+ * <p>85-95 is typical usage range</p>
*/
public static final Key<Byte> JPEG_QUALITY =
new Key<Byte>("android.jpeg.quality", byte.class);
/**
- * <p>
- * Compression quality of JPEG
- * thumbnail
- * </p>
+ * <p>Compression quality of JPEG
+ * thumbnail</p>
*/
public static final Key<Byte> JPEG_THUMBNAIL_QUALITY =
new Key<Byte>("android.jpeg.thumbnailQuality", byte.class);
/**
- * <p>
- * Resolution of embedded JPEG
- * thumbnail
- * </p>
+ * <p>Resolution of embedded JPEG thumbnail</p>
+ * <p>When set to (0, 0) value, the JPEG EXIF will not contain thumbnail,
+ * but the captured JPEG will still be a valid image.</p>
+ * <p>When a jpeg image capture is issued, the thumbnail size selected should have
+ * the same aspect ratio as the jpeg image.</p>
*/
public static final Key<android.hardware.camera2.Size> JPEG_THUMBNAIL_SIZE =
new Key<android.hardware.camera2.Size>("android.jpeg.thumbnailSize", android.hardware.camera2.Size.class);
/**
- * <p>
- * Size of the lens aperture
- * </p>
- * <p>
- * Will not be supported on most devices. Can only
- * pick from supported list
- * </p>
+ * <p>The ratio of lens focal length to the effective
+ * aperture diameter.</p>
+ * <p>This will only be supported on the camera devices that
+ * have variable aperture lens. The aperture value can only be
+ * one of the values listed in {@link CameraCharacteristics#LENS_INFO_AVAILABLE_APERTURES android.lens.info.availableApertures}.</p>
+ * <p>When this is supported and {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is OFF,
+ * this can be set along with {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime},
+ * {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity}, and {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}
+ * to achieve manual exposure control.</p>
+ * <p>The requested aperture value may take several frames to reach the
+ * requested value; the camera device will report the current (intermediate)
+ * aperture size in capture result metadata while the aperture is changing.
+ * While the aperture is still changing, {@link CaptureResult#LENS_STATE android.lens.state} will be set to MOVING.</p>
+ * <p>When this is supported and {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is one of
+ * the ON modes, this will be overridden by the camera device
+ * auto-exposure algorithm, the overridden values are then provided
+ * back to the user in the corresponding result.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CameraCharacteristics#LENS_INFO_AVAILABLE_APERTURES
+ * @see CaptureResult#LENS_STATE
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @see CaptureRequest#SENSOR_SENSITIVITY
*/
public static final Key<Float> LENS_APERTURE =
new Key<Float>("android.lens.aperture", float.class);
/**
- * <p>
- * State of lens neutral density
- * filter(s)
- * </p>
- * <p>
- * Will not be supported on most devices. Can only
- * pick from supported list
- * </p>
+ * <p>State of lens neutral density filter(s).</p>
+ * <p>This will not be supported on most camera devices. On devices
+ * where this is supported, this may only be set to one of the
+ * values included in {@link CameraCharacteristics#LENS_INFO_AVAILABLE_FILTER_DENSITIES android.lens.info.availableFilterDensities}.</p>
+ * <p>Lens filters are typically used to lower the amount of light the
+ * sensor is exposed to (measured in steps of EV). As used here, an EV
+ * step is the standard logarithmic representation, which are
+ * non-negative, and inversely proportional to the amount of light
+ * hitting the sensor. For example, setting this to 0 would result
+ * in no reduction of the incoming light, and setting this to 2 would
+ * mean that the filter is set to reduce incoming light by two stops
+ * (allowing 1/4 of the prior amount of light to the sensor).</p>
+ * <p>It may take several frames before the lens filter density changes
+ * to the requested value. While the filter density is still changing,
+ * {@link CaptureResult#LENS_STATE android.lens.state} will be set to MOVING.</p>
+ *
+ * @see CameraCharacteristics#LENS_INFO_AVAILABLE_FILTER_DENSITIES
+ * @see CaptureResult#LENS_STATE
*/
public static final Key<Float> LENS_FILTER_DENSITY =
new Key<Float>("android.lens.filterDensity", float.class);
/**
- * <p>
- * Lens optical zoom setting
- * </p>
- * <p>
- * Will not be supported on most devices.
- * </p>
+ * <p>The current lens focal length; used for optical zoom.</p>
+ * <p>This setting controls the physical focal length of the camera
+ * device's lens. Changing the focal length changes the field of
+ * view of the camera device, and is usually used for optical zoom.</p>
+ * <p>Like {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance} and {@link CaptureRequest#LENS_APERTURE android.lens.aperture}, this
+ * setting won't be applied instantaneously, and it may take several
+ * frames before the lens can change to the requested focal length.
+ * While the focal length is still changing, {@link CaptureResult#LENS_STATE android.lens.state} will
+ * be set to MOVING.</p>
+ * <p>This is expected not to be supported on most devices.</p>
+ *
+ * @see CaptureRequest#LENS_APERTURE
+ * @see CaptureRequest#LENS_FOCUS_DISTANCE
+ * @see CaptureResult#LENS_STATE
*/
public static final Key<Float> LENS_FOCAL_LENGTH =
new Key<Float>("android.lens.focalLength", float.class);
/**
- * <p>
- * Distance to plane of sharpest focus,
- * measured from frontmost surface of the lens
- * </p>
- * <p>
- * Should be zero for fixed-focus cameras
- * </p>
+ * <p>Distance to plane of sharpest focus,
+ * measured from frontmost surface of the lens</p>
+ * <p>Should be zero for fixed-focus cameras</p>
*/
public static final Key<Float> LENS_FOCUS_DISTANCE =
new Key<Float>("android.lens.focusDistance", float.class);
/**
- * <p>
- * The range of scene distances that are in
- * sharp focus (depth of field)
- * </p>
- * <p>
- * If variable focus not supported, can still report
- * fixed depth of field range
- * </p>
+ * <p>The range of scene distances that are in
+ * sharp focus (depth of field)</p>
+ * <p>If variable focus not supported, can still report
+ * fixed depth of field range</p>
*/
public static final Key<float[]> LENS_FOCUS_RANGE =
new Key<float[]>("android.lens.focusRange", float[].class);
/**
- * <p>
- * Whether optical image stabilization is
- * enabled.
- * </p>
- * <p>
- * Will not be supported on most devices.
- * </p>
+ * <p>Sets whether the camera device uses optical image stabilization (OIS)
+ * when capturing images.</p>
+ * <p>OIS is used to compensate for motion blur due to small movements of
+ * the camera during capture. Unlike digital image stabilization, OIS makes
+ * use of mechanical elements to stabilize the camera sensor, and thus
+ * allows for longer exposure times before camera shake becomes
+ * apparent.</p>
+ * <p>This is not expected to be supported on most devices.</p>
* @see #LENS_OPTICAL_STABILIZATION_MODE_OFF
* @see #LENS_OPTICAL_STABILIZATION_MODE_ON
*/
@@ -568,9 +1348,35 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.lens.opticalStabilizationMode", int.class);
/**
- * <p>
- * Current lens status
- * </p>
+ * <p>Current lens status.</p>
+ * <p>For lens parameters {@link CaptureRequest#LENS_FOCAL_LENGTH android.lens.focalLength}, {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance},
+ * {@link CaptureRequest#LENS_FILTER_DENSITY android.lens.filterDensity} and {@link CaptureRequest#LENS_APERTURE android.lens.aperture}, when changes are requested,
+ * they may take several frames to reach the requested values. This state indicates
+ * the current status of the lens parameters.</p>
+ * <p>When the state is STATIONARY, the lens parameters are not changing. This could be
+ * either because the parameters are all fixed, or because the lens has had enough
+ * time to reach the most recently-requested values.
+ * If all these lens parameters are not changable for a camera device, as listed below:</p>
+ * <ul>
+ * <li>Fixed focus (<code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} == 0</code>), which means
+ * {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance} parameter will always be 0.</li>
+ * <li>Fixed focal length ({@link CameraCharacteristics#LENS_INFO_AVAILABLE_FOCAL_LENGTHS android.lens.info.availableFocalLengths} contains single value),
+ * which means the optical zoom is not supported.</li>
+ * <li>No ND filter ({@link CameraCharacteristics#LENS_INFO_AVAILABLE_FILTER_DENSITIES android.lens.info.availableFilterDensities} contains only 0).</li>
+ * <li>Fixed aperture ({@link CameraCharacteristics#LENS_INFO_AVAILABLE_APERTURES android.lens.info.availableApertures} contains single value).</li>
+ * </ul>
+ * <p>Then this state will always be STATIONARY.</p>
+ * <p>When the state is MOVING, it indicates that at least one of the lens parameters
+ * is changing.</p>
+ *
+ * @see CaptureRequest#LENS_APERTURE
+ * @see CaptureRequest#LENS_FILTER_DENSITY
+ * @see CaptureRequest#LENS_FOCAL_LENGTH
+ * @see CaptureRequest#LENS_FOCUS_DISTANCE
+ * @see CameraCharacteristics#LENS_INFO_AVAILABLE_APERTURES
+ * @see CameraCharacteristics#LENS_INFO_AVAILABLE_FILTER_DENSITIES
+ * @see CameraCharacteristics#LENS_INFO_AVAILABLE_FOCAL_LENGTHS
+ * @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE
* @see #LENS_STATE_STATIONARY
* @see #LENS_STATE_MOVING
*/
@@ -578,10 +1384,15 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.lens.state", int.class);
/**
- * <p>
- * Mode of operation for the noise reduction
- * algorithm
- * </p>
+ * <p>Mode of operation for the noise reduction
+ * algorithm</p>
+ * <p>Noise filtering control. OFF means no noise reduction
+ * will be applied by the camera device.</p>
+ * <p>FAST/HIGH_QUALITY both mean camera device determined noise filtering
+ * will be applied. HIGH_QUALITY mode indicates that the camera device
+ * will use the highest-quality noise filtering algorithms,
+ * even if it slows down capture rate. FAST means the camera device should not
+ * slow down capture rate when applying noise filtering.</p>
* @see #NOISE_REDUCTION_MODE_OFF
* @see #NOISE_REDUCTION_MODE_FAST
* @see #NOISE_REDUCTION_MODE_HIGH_QUALITY
@@ -590,14 +1401,11 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.noiseReduction.mode", int.class);
/**
- * <p>
- * Whether a result given to the framework is the
+ * <p>Whether a result given to the framework is the
* final one for the capture, or only a partial that contains a
* subset of the full set of dynamic metadata
- * values.
- * </p>
- * <p>
- * The entries in the result metadata buffers for a
+ * values.</p>
+ * <p>The entries in the result metadata buffers for a
* single capture may not overlap, except for this entry. The
* FINAL buffers must retain FIFO ordering relative to the
* requests that generate them, so the FINAL buffer for frame 3 must
@@ -605,68 +1413,64 @@ public final class CaptureResult extends CameraMetadata {
* before the FINAL buffer for frame 4. PARTIAL buffers may be returned
* in any order relative to other frames, but all PARTIAL buffers for a given
* capture must arrive before the FINAL buffer for that capture. This entry may
- * only be used by the HAL if quirks.usePartialResult is set to 1.
- * </p>
- *
- * <b>Optional</b> - This value may be null on some devices.
- *
+ * only be used by the camera device if quirks.usePartialResult is set to 1.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* @hide
*/
public static final Key<Boolean> QUIRKS_PARTIAL_RESULT =
new Key<Boolean>("android.quirks.partialResult", boolean.class);
/**
- * <p>
- * A frame counter set by the framework. This value monotonically
+ * <p>A frame counter set by the framework. This value monotonically
* increases with every new result (that is, each new result has a unique
- * frameCount value).
- * </p>
- * <p>
- * Reset on release()
- * </p>
+ * frameCount value).</p>
+ * <p>Reset on release()</p>
*/
public static final Key<Integer> REQUEST_FRAME_COUNT =
new Key<Integer>("android.request.frameCount", int.class);
/**
- * <p>
- * An application-specified ID for the current
+ * <p>An application-specified ID for the current
* request. Must be maintained unchanged in output
- * frame
- * </p>
- *
+ * frame</p>
* @hide
*/
public static final Key<Integer> REQUEST_ID =
new Key<Integer>("android.request.id", int.class);
/**
- * <p>
- * (x, y, width, height).
- * </p><p>
- * A rectangle with the top-level corner of (x,y) and size
+ * <p>Specifies the number of pipeline stages the frame went
+ * through from when it was exposed to when the final completed result
+ * was available to the framework.</p>
+ * <p>Depending on what settings are used in the request, and
+ * what streams are configured, the data may undergo less processing,
+ * and some pipeline stages skipped.</p>
+ * <p>See {@link CameraCharacteristics#REQUEST_PIPELINE_MAX_DEPTH android.request.pipelineMaxDepth} for more details.</p>
+ *
+ * @see CameraCharacteristics#REQUEST_PIPELINE_MAX_DEPTH
+ */
+ public static final Key<Byte> REQUEST_PIPELINE_DEPTH =
+ new Key<Byte>("android.request.pipelineDepth", byte.class);
+
+ /**
+ * <p>(x, y, width, height).</p>
+ * <p>A rectangle with the top-level corner of (x,y) and size
* (width, height). The region of the sensor that is used for
* output. Each stream must use this rectangle to produce its
* output, cropping to a smaller region if necessary to
- * maintain the stream's aspect ratio.
- * </p><p>
- * HAL2.x uses only (x, y, width)
- * </p>
- * <p>
- * Any additional per-stream cropping must be done to
- * maximize the final pixel area of the stream.
- * </p><p>
- * For example, if the crop region is set to a 4:3 aspect
+ * maintain the stream's aspect ratio.</p>
+ * <p>HAL2.x uses only (x, y, width)</p>
+ * <p>Any additional per-stream cropping must be done to
+ * maximize the final pixel area of the stream.</p>
+ * <p>For example, if the crop region is set to a 4:3 aspect
* ratio, then 4:3 streams should use the exact crop
* region. 16:9 streams should further crop vertically
- * (letterbox).
- * </p><p>
- * Conversely, if the crop region is set to a 16:9, then 4:3
+ * (letterbox).</p>
+ * <p>Conversely, if the crop region is set to a 16:9, then 4:3
* outputs should crop horizontally (pillarbox), and 16:9
* streams should match exactly. These additional crops must
- * be centered within the crop region.
- * </p><p>
- * The output streams must maintain square pixels at all
+ * be centered within the crop region.</p>
+ * <p>The output streams must maintain square pixels at all
* times, no matter what the relative aspect ratios of the
* crop region and the stream are. Negative values for
* corner are allowed for raw output if full pixel array is
@@ -675,100 +1479,337 @@ public final class CaptureResult extends CameraMetadata {
* for raw output, where only a few fixed scales may be
* possible. The width and height of the crop region cannot
* be set to be smaller than floor( activeArraySize.width /
- * android.scaler.maxDigitalZoom ) and floor(
- * activeArraySize.height / android.scaler.maxDigitalZoom),
- * respectively.
- * </p>
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} ) and floor(
+ * activeArraySize.height /
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom}), respectively.</p>
+ *
+ * @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
*/
public static final Key<android.graphics.Rect> SCALER_CROP_REGION =
new Key<android.graphics.Rect>("android.scaler.cropRegion", android.graphics.Rect.class);
/**
- * <p>
- * Duration each pixel is exposed to
- * light.
- * </p><p>
- * If the sensor can't expose this exact duration, it should shorten the
- * duration exposed to the nearest possible value (rather than expose longer).
- * </p>
- * <p>
- * 1/10000 - 30 sec range. No bulb mode
- * </p>
+ * <p>Duration each pixel is exposed to
+ * light.</p>
+ * <p>If the sensor can't expose this exact duration, it should shorten the
+ * duration exposed to the nearest possible value (rather than expose longer).</p>
+ * <p>1/10000 - 30 sec range. No bulb mode</p>
*/
public static final Key<Long> SENSOR_EXPOSURE_TIME =
new Key<Long>("android.sensor.exposureTime", long.class);
/**
- * <p>
- * Duration from start of frame exposure to
- * start of next frame exposure
- * </p>
- * <p>
- * Exposure time has priority, so duration is set to
- * max(duration, exposure time + overhead)
- * </p>
+ * <p>Duration from start of frame exposure to
+ * start of next frame exposure.</p>
+ * <p>The maximum frame rate that can be supported by a camera subsystem is
+ * a function of many factors:</p>
+ * <ul>
+ * <li>Requested resolutions of output image streams</li>
+ * <li>Availability of binning / skipping modes on the imager</li>
+ * <li>The bandwidth of the imager interface</li>
+ * <li>The bandwidth of the various ISP processing blocks</li>
+ * </ul>
+ * <p>Since these factors can vary greatly between different ISPs and
+ * sensors, the camera abstraction tries to represent the bandwidth
+ * restrictions with as simple a model as possible.</p>
+ * <p>The model presented has the following characteristics:</p>
+ * <ul>
+ * <li>The image sensor is always configured to output the smallest
+ * resolution possible given the application's requested output stream
+ * sizes. The smallest resolution is defined as being at least as large
+ * as the largest requested output stream size; the camera pipeline must
+ * never digitally upsample sensor data when the crop region covers the
+ * whole sensor. In general, this means that if only small output stream
+ * resolutions are configured, the sensor can provide a higher frame
+ * rate.</li>
+ * <li>Since any request may use any or all the currently configured
+ * output streams, the sensor and ISP must be configured to support
+ * scaling a single capture to all the streams at the same time. This
+ * means the camera pipeline must be ready to produce the largest
+ * requested output size without any delay. Therefore, the overall
+ * frame rate of a given configured stream set is governed only by the
+ * largest requested stream resolution.</li>
+ * <li>Using more than one output stream in a request does not affect the
+ * frame duration.</li>
+ * <li>Certain format-streams may need to do additional background processing
+ * before data is consumed/produced by that stream. These processors
+ * can run concurrently to the rest of the camera pipeline, but
+ * cannot process more than 1 capture at a time.</li>
+ * </ul>
+ * <p>The necessary information for the application, given the model above,
+ * is provided via the {@link CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS android.scaler.availableMinFrameDurations} field.
+ * These are used to determine the maximum frame rate / minimum frame
+ * duration that is possible for a given stream configuration.</p>
+ * <p>Specifically, the application can use the following rules to
+ * determine the minimum frame duration it can request from the camera
+ * device:</p>
+ * <ol>
+ * <li>Let the set of currently configured input/output streams
+ * be called <code>S</code>.</li>
+ * <li>Find the minimum frame durations for each stream in <code>S</code>, by
+ * looking it up in {@link CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS android.scaler.availableMinFrameDurations} (with
+ * its respective size/format). Let this set of frame durations be called
+ * <code>F</code>.</li>
+ * <li>For any given request <code>R</code>, the minimum frame duration allowed
+ * for <code>R</code> is the maximum out of all values in <code>F</code>. Let the streams
+ * used in <code>R</code> be called <code>S_r</code>.</li>
+ * </ol>
+ * <p>If none of the streams in <code>S_r</code> have a stall time (listed in
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS android.scaler.availableStallDurations}), then the frame duration in
+ * <code>F</code> determines the steady state frame rate that the application will
+ * get if it uses <code>R</code> as a repeating request. Let this special kind
+ * of request be called <code>Rsimple</code>.</p>
+ * <p>A repeating request <code>Rsimple</code> can be <em>occasionally</em> interleaved
+ * by a single capture of a new request <code>Rstall</code> (which has at least
+ * one in-use stream with a non-0 stall time) and if <code>Rstall</code> has the
+ * same minimum frame duration this will not cause a frame rate loss
+ * if all buffers from the previous <code>Rstall</code> have already been
+ * delivered.</p>
+ * <p>For more details about stalling, see
+ * {@link CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS android.scaler.availableStallDurations}.</p>
+ *
+ * @see CameraCharacteristics#SCALER_AVAILABLE_MIN_FRAME_DURATIONS
+ * @see CameraCharacteristics#SCALER_AVAILABLE_STALL_DURATIONS
*/
public static final Key<Long> SENSOR_FRAME_DURATION =
new Key<Long>("android.sensor.frameDuration", long.class);
/**
- * <p>
- * Gain applied to image data. Must be
+ * <p>Gain applied to image data. Must be
* implemented through analog gain only if set to values
- * below 'maximum analog sensitivity'.
- * </p><p>
- * If the sensor can't apply this exact gain, it should lessen the
- * gain to the nearest possible value (rather than gain more).
- * </p>
- * <p>
- * ISO 12232:2006 REI method
- * </p>
+ * below 'maximum analog sensitivity'.</p>
+ * <p>If the sensor can't apply this exact gain, it should lessen the
+ * gain to the nearest possible value (rather than gain more).</p>
+ * <p>ISO 12232:2006 REI method</p>
*/
public static final Key<Integer> SENSOR_SENSITIVITY =
new Key<Integer>("android.sensor.sensitivity", int.class);
/**
- * <p>
- * Time at start of exposure of first
- * row
- * </p>
- * <p>
- * Monotonic, should be synced to other timestamps in
- * system
- * </p>
+ * <p>Time at start of exposure of first
+ * row</p>
+ * <p>Monotonic, should be synced to other timestamps in
+ * system</p>
*/
public static final Key<Long> SENSOR_TIMESTAMP =
new Key<Long>("android.sensor.timestamp", long.class);
/**
- * <p>
- * The temperature of the sensor, sampled at the time
- * exposure began for this frame.
- * </p><p>
- * The thermal diode being queried should be inside the sensor PCB, or
- * somewhere close to it.
- * </p>
- *
- * <b>Optional</b> - This value may be null on some devices.
+ * <p>The temperature of the sensor, sampled at the time
+ * exposure began for this frame.</p>
+ * <p>The thermal diode being queried should be inside the sensor PCB, or
+ * somewhere close to it.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ * <p><b>Full capability</b> -
+ * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
- * <b>{@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL}</b> -
- * Present on all devices that report being FULL level hardware devices in the
- * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL HARDWARE_LEVEL} key.
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
*/
public static final Key<Float> SENSOR_TEMPERATURE =
new Key<Float>("android.sensor.temperature", float.class);
/**
- * <p>
- * State of the face detector
- * unit
- * </p>
- * <p>
- * Whether face detection is enabled, and whether it
+ * <p>A reference illumination source roughly matching the current scene
+ * illumination, which is used to describe the sensor color space
+ * transformations.</p>
+ * <p>The values in this tag correspond to the values defined for the
+ * EXIF LightSource tag. These illuminants are standard light sources
+ * that are often used for calibrating camera devices.</p>
+ * @see #SENSOR_REFERENCE_ILLUMINANT_DAYLIGHT
+ * @see #SENSOR_REFERENCE_ILLUMINANT_FLUORESCENT
+ * @see #SENSOR_REFERENCE_ILLUMINANT_TUNGSTEN
+ * @see #SENSOR_REFERENCE_ILLUMINANT_FLASH
+ * @see #SENSOR_REFERENCE_ILLUMINANT_FINE_WEATHER
+ * @see #SENSOR_REFERENCE_ILLUMINANT_CLOUDY_WEATHER
+ * @see #SENSOR_REFERENCE_ILLUMINANT_SHADE
+ * @see #SENSOR_REFERENCE_ILLUMINANT_DAYLIGHT_FLUORESCENT
+ * @see #SENSOR_REFERENCE_ILLUMINANT_DAY_WHITE_FLUORESCENT
+ * @see #SENSOR_REFERENCE_ILLUMINANT_COOL_WHITE_FLUORESCENT
+ * @see #SENSOR_REFERENCE_ILLUMINANT_WHITE_FLUORESCENT
+ * @see #SENSOR_REFERENCE_ILLUMINANT_STANDARD_A
+ * @see #SENSOR_REFERENCE_ILLUMINANT_STANDARD_B
+ * @see #SENSOR_REFERENCE_ILLUMINANT_STANDARD_C
+ * @see #SENSOR_REFERENCE_ILLUMINANT_D55
+ * @see #SENSOR_REFERENCE_ILLUMINANT_D65
+ * @see #SENSOR_REFERENCE_ILLUMINANT_D75
+ * @see #SENSOR_REFERENCE_ILLUMINANT_D50
+ * @see #SENSOR_REFERENCE_ILLUMINANT_ISO_STUDIO_TUNGSTEN
+ */
+ public static final Key<Integer> SENSOR_REFERENCE_ILLUMINANT =
+ new Key<Integer>("android.sensor.referenceIlluminant", int.class);
+
+ /**
+ * <p>A per-device calibration transform matrix to be applied after the
+ * color space transform when rendering the raw image buffer.</p>
+ * <p>This matrix is expressed as a 3x3 matrix in row-major-order, and
+ * contains a per-device calibration transform that maps colors
+ * from reference camera color space (i.e. the "golden module"
+ * colorspace) into this camera device's linear native sensor color
+ * space for the current scene illumination and white balance choice.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM =
+ new Key<Rational[]>("android.sensor.calibrationTransform", Rational[].class);
+
+ /**
+ * <p>A matrix that transforms color values from CIE XYZ color space to
+ * reference camera color space when rendering the raw image buffer.</p>
+ * <p>This matrix is expressed as a 3x3 matrix in row-major-order, and
+ * contains a color transform matrix that maps colors from the CIE
+ * XYZ color space to the reference camera raw color space (i.e. the
+ * "golden module" colorspace) for the current scene illumination and
+ * white balance choice.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM =
+ new Key<Rational[]>("android.sensor.colorTransform", Rational[].class);
+
+ /**
+ * <p>A matrix that transforms white balanced camera colors to the CIE XYZ
+ * colorspace with a D50 whitepoint.</p>
+ * <p>This matrix is expressed as a 3x3 matrix in row-major-order, and contains
+ * a color transform matrix that maps a unit vector in the linear native
+ * sensor color space to the D50 whitepoint in CIE XYZ color space.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ public static final Key<Rational[]> SENSOR_FORWARD_MATRIX =
+ new Key<Rational[]>("android.sensor.forwardMatrix", Rational[].class);
+
+ /**
+ * <p>The estimated white balance at the time of capture.</p>
+ * <p>The estimated white balance encoded as the RGB values of the
+ * perfectly neutral color point in the linear native sensor color space.
+ * The order of the values is R, G, B; where R is in the lowest index.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ public static final Key<Rational[]> SENSOR_NEUTRAL_COLOR_POINT =
+ new Key<Rational[]>("android.sensor.neutralColorPoint", Rational[].class);
+
+ /**
+ * <p>A mapping containing a hue shift, saturation scale, and value scale
+ * for each pixel.</p>
+ * <p>hue_samples, saturation_samples, and value_samples are given in
+ * {@link CameraCharacteristics#SENSOR_PROFILE_HUE_SAT_MAP_DIMENSIONS android.sensor.profileHueSatMapDimensions}.</p>
+ * <p>Each entry of this map contains three floats corresponding to the
+ * hue shift, saturation scale, and value scale, respectively; where the
+ * hue shift has the lowest index. The map entries are stored in the tag
+ * in nested loop order, with the value divisions in the outer loop, the
+ * hue divisions in the middle loop, and the saturation divisions in the
+ * inner loop. All zero input saturation entries are required to have a
+ * value scale factor of 1.0.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#SENSOR_PROFILE_HUE_SAT_MAP_DIMENSIONS
+ */
+ public static final Key<float[]> SENSOR_PROFILE_HUE_SAT_MAP =
+ new Key<float[]>("android.sensor.profileHueSatMap", float[].class);
+
+ /**
+ * <p>A list of x,y samples defining a tone-mapping curve for gamma adjustment.</p>
+ * <p>This tag contains a default tone curve that can be applied while
+ * processing the image as a starting point for user adjustments.
+ * The curve is specified as a list of value pairs in linear gamma.
+ * The curve is interpolated using a cubic spline.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ public static final Key<float[]> SENSOR_PROFILE_TONE_CURVE =
+ new Key<float[]>("android.sensor.profileToneCurve", float[].class);
+
+ /**
+ * <p>The worst-case divergence between Bayer green channels.</p>
+ * <p>This value is an estimate of the worst case split between the
+ * Bayer green channels in the red and blue rows in the sensor color
+ * filter array.</p>
+ * <p>The green split is calculated as follows:</p>
+ * <ol>
+ * <li>A representative 5x5 pixel window W within the active
+ * sensor array is chosen.</li>
+ * <li>The arithmetic mean of the green channels from the red
+ * rows (mean_Gr) within W is computed.</li>
+ * <li>The arithmetic mean of the green channels from the blue
+ * rows (mean_Gb) within W is computed.</li>
+ * <li>The maximum ratio R of the two means is computed as follows:
+ * <code>R = max((mean_Gr + 1)/(mean_Gb + 1), (mean_Gb + 1)/(mean_Gr + 1))</code></li>
+ * </ol>
+ * <p>The ratio R is the green split divergence reported for this property,
+ * which represents how much the green channels differ in the mosaic
+ * pattern. This value is typically used to determine the treatment of
+ * the green mosaic channels when demosaicing.</p>
+ * <p>The green split value can be roughly interpreted as follows:</p>
+ * <ul>
+ * <li>R &lt; 1.03 is a negligible split (&lt;3% divergence).</li>
+ * <li>1.20 &lt;= R &gt;= 1.03 will require some software
+ * correction to avoid demosaic errors (3-20% divergence).</li>
+ * <li>R &gt; 1.20 will require strong software correction to produce
+ * a usuable image (&gt;20% divergence).</li>
+ * </ul>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ public static final Key<Float> SENSOR_GREEN_SPLIT =
+ new Key<Float>("android.sensor.greenSplit", float.class);
+
+ /**
+ * <p>When enabled, the sensor sends a test pattern instead of
+ * doing a real exposure from the camera.</p>
+ * <p>When a test pattern is enabled, all manual sensor controls specified
+ * by android.sensor.* should be ignored. All other controls should
+ * work as normal.</p>
+ * <p>For example, if manual flash is enabled, flash firing should still
+ * occur (and that the test pattern remain unmodified, since the flash
+ * would not actually affect it).</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ * @see #SENSOR_TEST_PATTERN_MODE_OFF
+ * @see #SENSOR_TEST_PATTERN_MODE_SOLID_COLOR
+ * @see #SENSOR_TEST_PATTERN_MODE_COLOR_BARS
+ * @see #SENSOR_TEST_PATTERN_MODE_COLOR_BARS_FADE_TO_GRAY
+ * @see #SENSOR_TEST_PATTERN_MODE_PN9
+ * @see #SENSOR_TEST_PATTERN_MODE_CUSTOM1
+ */
+ public static final Key<Integer> SENSOR_TEST_PATTERN_MODE =
+ new Key<Integer>("android.sensor.testPatternMode", int.class);
+
+ /**
+ * <p>Quality of lens shading correction applied
+ * to the image data.</p>
+ * <p>When set to OFF mode, no lens shading correction will be applied by the
+ * camera device, and an identity lens shading map data will be provided
+ * if <code>{@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON</code>. For example, for lens
+ * shading map with size specified as <code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ]</code>,
+ * the output {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} for this case will be an identity map
+ * shown below:</p>
+ * <pre><code>[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
+ * 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ]
+ * </code></pre>
+ * <p>When set to other modes, lens shading correction will be applied by the
+ * camera device. Applications can request lens shading map data by setting
+ * {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} to ON, and then the camera device will provide
+ * lens shading map data in {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}, with size specified
+ * by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}.</p>
+ *
+ * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE
+ * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
+ * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
+ * @see #SHADING_MODE_OFF
+ * @see #SHADING_MODE_FAST
+ * @see #SHADING_MODE_HIGH_QUALITY
+ */
+ public static final Key<Integer> SHADING_MODE =
+ new Key<Integer>("android.shading.mode", int.class);
+
+ /**
+ * <p>State of the face detector
+ * unit</p>
+ * <p>Whether face detection is enabled, and whether it
* should output just the basic fields or the full set of
* fields. Value must be one of the
- * android.statistics.info.availableFaceDetectModes.
- * </p>
+ * {@link CameraCharacteristics#STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES android.statistics.info.availableFaceDetectModes}.</p>
+ *
+ * @see CameraCharacteristics#STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES
* @see #STATISTICS_FACE_DETECT_MODE_OFF
* @see #STATISTICS_FACE_DETECT_MODE_SIMPLE
* @see #STATISTICS_FACE_DETECT_MODE_FULL
@@ -777,129 +1818,151 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.statistics.faceDetectMode", int.class);
/**
- * <p>
- * List of unique IDs for detected
- * faces
- * </p>
- * <p>
- * Only available if faceDetectMode == FULL
- * </p>
+ * <p>List of unique IDs for detected
+ * faces</p>
+ * <p>Only available if faceDetectMode == FULL</p>
+ * @hide
*/
public static final Key<int[]> STATISTICS_FACE_IDS =
new Key<int[]>("android.statistics.faceIds", int[].class);
/**
- * <p>
- * List of landmarks for detected
- * faces
- * </p>
- * <p>
- * Only available if faceDetectMode == FULL
- * </p>
+ * <p>List of landmarks for detected
+ * faces</p>
+ * <p>Only available if faceDetectMode == FULL</p>
+ * @hide
*/
public static final Key<int[]> STATISTICS_FACE_LANDMARKS =
new Key<int[]>("android.statistics.faceLandmarks", int[].class);
/**
- * <p>
- * List of the bounding rectangles for detected
- * faces
- * </p>
- * <p>
- * Only available if faceDetectMode != OFF
- * </p>
+ * <p>List of the bounding rectangles for detected
+ * faces</p>
+ * <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);
/**
- * <p>
- * List of the face confidence scores for
- * detected faces
- * </p>
- * <p>
- * Only available if faceDetectMode != OFF. The value should be
- * meaningful (for example, setting 100 at all times is illegal).
- * </p>
+ * <p>List of the face confidence scores for
+ * detected faces</p>
+ * <p>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);
/**
- * <p>
- * A low-resolution map of lens shading, per
- * color channel
- * </p>
- * <p>
- * Assume bilinear interpolation of map. The least
- * shaded section of the image should have a gain factor
- * of 1; all other sections should have gains above 1.
- * the map should be on the order of 30-40 rows, and
- * must be smaller than 64x64.
- * </p><p>
- * When android.colorCorrection.mode = TRANSFORM_MATRIX, the map
- * must take into account the colorCorrection settings.
- * </p>
+ * <p>The shading map is a low-resolution floating-point map
+ * that lists the coefficients used to correct for vignetting, for each
+ * Bayer color channel.</p>
+ * <p>The least shaded section of the image should have a gain factor
+ * of 1; all other sections should have gains above 1.</p>
+ * <p>When {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} = TRANSFORM_MATRIX, the map
+ * must take into account the colorCorrection settings.</p>
+ * <p>The shading map is for the entire active pixel array, and is not
+ * affected by the crop region specified in the request. Each shading map
+ * entry is the value of the shading compensation map over a specific
+ * pixel on the sensor. Specifically, with a (N x M) resolution shading
+ * map, and an active pixel array size (W x H), shading map entry
+ * (x,y) ϵ (0 ... N-1, 0 ... M-1) is the value of the shading map at
+ * pixel ( ((W-1)/(N-1)) * x, ((H-1)/(M-1)) * y) for the four color channels.
+ * The map is assumed to be bilinearly interpolated between the sample points.</p>
+ * <p>The channel order is [R, Geven, Godd, B], where Geven is the green
+ * channel for the even rows of a Bayer pattern, and Godd is the odd rows.
+ * The shading map is stored in a fully interleaved format, and its size
+ * is provided in the camera static metadata by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}.</p>
+ * <p>The shading map should have on the order of 30-40 rows and columns,
+ * and must be smaller than 64x64.</p>
+ * <p>As an example, given a very small map defined as:</p>
+ * <pre><code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ]
+ * {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} =
+ * [ 1.3, 1.2, 1.15, 1.2, 1.2, 1.2, 1.15, 1.2,
+ * 1.1, 1.2, 1.2, 1.2, 1.3, 1.2, 1.3, 1.3,
+ * 1.2, 1.2, 1.25, 1.1, 1.1, 1.1, 1.1, 1.0,
+ * 1.0, 1.0, 1.0, 1.0, 1.2, 1.3, 1.25, 1.2,
+ * 1.3, 1.2, 1.2, 1.3, 1.2, 1.15, 1.1, 1.2,
+ * 1.2, 1.1, 1.0, 1.2, 1.3, 1.15, 1.2, 1.3 ]
+ * </code></pre>
+ * <p>The low-resolution scaling map images for each channel are
+ * (displayed using nearest-neighbor interpolation):</p>
+ * <p><img alt="Red lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/red_shading.png" />
+ * <img alt="Green (even rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_e_shading.png" />
+ * <img alt="Green (odd rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" />
+ * <img alt="Blue lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p>
+ * <p>As a visualization only, inverting the full-color map to recover an
+ * image of a gray wall (using bicubic interpolation for visual quality) as captured by the sensor gives:</p>
+ * <p><img alt="Image of a uniform white wall (inverse shading map)" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_MODE
+ * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE
+ * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
*/
public static final Key<float[]> STATISTICS_LENS_SHADING_MAP =
new Key<float[]>("android.statistics.lensShadingMap", float[].class);
/**
- * <p>
- * The best-fit color channel gains calculated
- * by the HAL's statistics units for the current output frame
- * </p>
- * <p>
- * This may be different than the gains used for this frame,
+ * <p>The best-fit color channel gains calculated
+ * by the camera device's statistics units for the current output frame.</p>
+ * <p>This may be different than the gains used for this frame,
* since statistics processing on data from a new frame
* typically completes after the transform has already been
- * applied to that frame.
- * </p><p>
- * The 4 channel gains are defined in Bayer domain,
- * see android.colorCorrection.gains for details.
- * </p><p>
- * This value should always be calculated by the AWB block,
- * regardless of the android.control.* current values.
- * </p>
+ * applied to that frame.</p>
+ * <p>The 4 channel gains are defined in Bayer domain,
+ * see {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} for details.</p>
+ * <p>This value should always be calculated by the AWB block,
+ * regardless of the android.control.* current values.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_GAINS
+ * @hide
*/
public static final Key<float[]> STATISTICS_PREDICTED_COLOR_GAINS =
new Key<float[]>("android.statistics.predictedColorGains", float[].class);
/**
- * <p>
- * The best-fit color transform matrix estimate
- * calculated by the HAL's statistics units for the current
- * output frame
- * </p>
- * <p>
- * The HAL must provide the estimate from its
+ * <p>The best-fit color transform matrix estimate
+ * calculated by the camera device's statistics units for the current
+ * output frame.</p>
+ * <p>The camera device will provide the estimate from its
* statistics unit on the white balance transforms to use
- * for the next frame. These are the values the HAL believes
+ * for the next frame. These are the values the camera device believes
* are the best fit for the current output frame. This may
* be different than the transform used for this frame, since
* statistics processing on data from a new frame typically
* completes after the transform has already been applied to
- * that frame.
- * </p><p>
- * These estimates must be provided for all frames, even if
- * capture settings and color transforms are set by the application.
- * </p><p>
- * This value should always be calculated by the AWB block,
- * regardless of the android.control.* current values.
- * </p>
+ * that frame.</p>
+ * <p>These estimates must be provided for all frames, even if
+ * capture settings and color transforms are set by the application.</p>
+ * <p>This value should always be calculated by the AWB block,
+ * regardless of the android.control.* current values.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ * @hide
*/
public static final Key<Rational[]> STATISTICS_PREDICTED_COLOR_TRANSFORM =
new Key<Rational[]>("android.statistics.predictedColorTransform", Rational[].class);
/**
- * <p>
- * The HAL estimated scene illumination lighting
- * frequency
- * </p>
- * <p>
- * Report NONE if there doesn't appear to be flickering
- * illumination
- * </p>
+ * <p>The camera device estimated scene illumination lighting
+ * frequency.</p>
+ * <p>Many light sources, such as most fluorescent lights, flicker at a rate
+ * that depends on the local utility power standards. This flicker must be
+ * accounted for by auto-exposure routines to avoid artifacts in captured images.
+ * The camera device uses this entry to tell the application what the scene
+ * illuminant frequency is.</p>
+ * <p>When manual exposure control is enabled
+ * (<code>{@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} == OFF</code> or <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} == OFF</code>),
+ * the {@link CaptureRequest#CONTROL_AE_ANTIBANDING_MODE android.control.aeAntibandingMode} doesn't do the antibanding, and the
+ * application can ensure it selects exposure times that do not cause banding
+ * issues by looking into this metadata field. See {@link CaptureRequest#CONTROL_AE_ANTIBANDING_MODE android.control.aeAntibandingMode}
+ * for more details.</p>
+ * <p>Report NONE if there doesn't appear to be flickering illumination.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_ANTIBANDING_MODE
+ * @see CaptureRequest#CONTROL_AE_MODE
+ * @see CaptureRequest#CONTROL_MODE
* @see #STATISTICS_SCENE_FLICKER_NONE
* @see #STATISTICS_SCENE_FLICKER_50HZ
* @see #STATISTICS_SCENE_FLICKER_60HZ
@@ -908,61 +1971,107 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.statistics.sceneFlicker", int.class);
/**
- * <p>
- * Table mapping blue input values to output
- * values
- * </p>
- * <p>
- * Tonemapping / contrast / gamma curve for the blue
- * channel, to use when android.tonemap.mode is CONTRAST_CURVE.
- * </p><p>
- * See android.tonemap.curveRed for more details.
- * </p>
+ * <p>Tonemapping / contrast / gamma curve for the blue
+ * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * CONTRAST_CURVE.</p>
+ * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p>
+ *
+ * @see CaptureRequest#TONEMAP_CURVE_RED
+ * @see CaptureRequest#TONEMAP_MODE
*/
public static final Key<float[]> TONEMAP_CURVE_BLUE =
new Key<float[]>("android.tonemap.curveBlue", float[].class);
/**
- * <p>
- * Table mapping green input values to output
- * values
- * </p>
- * <p>
- * Tonemapping / contrast / gamma curve for the green
- * channel, to use when android.tonemap.mode is CONTRAST_CURVE.
- * </p><p>
- * See android.tonemap.curveRed for more details.
- * </p>
+ * <p>Tonemapping / contrast / gamma curve for the green
+ * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * CONTRAST_CURVE.</p>
+ * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p>
+ *
+ * @see CaptureRequest#TONEMAP_CURVE_RED
+ * @see CaptureRequest#TONEMAP_MODE
*/
public static final Key<float[]> TONEMAP_CURVE_GREEN =
new Key<float[]>("android.tonemap.curveGreen", float[].class);
/**
- * <p>
- * Table mapping red input values to output
- * values
- * </p>
- * <p>
- * Tonemapping / contrast / gamma curve for the red
- * channel, to use when android.tonemap.mode is CONTRAST_CURVE.
- * </p><p>
- * Since the input and output ranges may vary depending on
- * the camera pipeline, the input and output pixel values
- * are represented by normalized floating-point values
- * between 0 and 1, with 0 == black and 1 == white.
- * </p><p>
- * The curve should be linearly interpolated between the
- * defined points. The points will be listed in increasing
- * order of P_IN. For example, if the array is: [0.0, 0.0,
- * 0.3, 0.5, 1.0, 1.0], then the input->output mapping
- * for a few sample points would be: 0 -> 0, 0.15 ->
- * 0.25, 0.3 -> 0.5, 0.5 -> 0.64
- * </p>
+ * <p>Tonemapping / contrast / gamma curve for the red
+ * channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * CONTRAST_CURVE.</p>
+ * <p>Each channel's curve is defined by an array of control points:</p>
+ * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} =
+ * [ P0in, P0out, P1in, P1out, P2in, P2out, P3in, P3out, ..., PNin, PNout ]
+ * 2 &lt;= N &lt;= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre>
+ * <p>These are sorted in order of increasing <code>Pin</code>; it is always
+ * guaranteed that input values 0.0 and 1.0 are included in the list to
+ * define a complete mapping. For input values between control points,
+ * the camera device must linearly interpolate between the control
+ * points.</p>
+ * <p>Each curve can have an independent number of points, and the number
+ * of points can be less than max (that is, the request doesn't have to
+ * always provide a curve with number of points equivalent to
+ * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p>
+ * <p>A few examples, and their corresponding graphical mappings; these
+ * only specify the red channel and the precision is limited to 4
+ * digits, for conciseness.</p>
+ * <p>Linear mapping:</p>
+ * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 0, 1.0, 1.0 ]
+ * </code></pre>
+ * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p>
+ * <p>Invert mapping:</p>
+ * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 1.0, 1.0, 0 ]
+ * </code></pre>
+ * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p>
+ * <p>Gamma 1/2.2 mapping, with 16 control points:</p>
+ * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [
+ * 0.0000, 0.0000, 0.0667, 0.2920, 0.1333, 0.4002, 0.2000, 0.4812,
+ * 0.2667, 0.5484, 0.3333, 0.6069, 0.4000, 0.6594, 0.4667, 0.7072,
+ * 0.5333, 0.7515, 0.6000, 0.7928, 0.6667, 0.8317, 0.7333, 0.8685,
+ * 0.8000, 0.9035, 0.8667, 0.9370, 0.9333, 0.9691, 1.0000, 1.0000 ]
+ * </code></pre>
+ * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p>
+ * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p>
+ * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [
+ * 0.0000, 0.0000, 0.0667, 0.2864, 0.1333, 0.4007, 0.2000, 0.4845,
+ * 0.2667, 0.5532, 0.3333, 0.6125, 0.4000, 0.6652, 0.4667, 0.7130,
+ * 0.5333, 0.7569, 0.6000, 0.7977, 0.6667, 0.8360, 0.7333, 0.8721,
+ * 0.8000, 0.9063, 0.8667, 0.9389, 0.9333, 0.9701, 1.0000, 1.0000 ]
+ * </code></pre>
+ * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
+ *
+ * @see CaptureRequest#TONEMAP_CURVE_RED
+ * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
+ * @see CaptureRequest#TONEMAP_MODE
*/
public static final Key<float[]> TONEMAP_CURVE_RED =
new Key<float[]>("android.tonemap.curveRed", float[].class);
/**
+ * <p>High-level global contrast/gamma/tonemapping control.</p>
+ * <p>When switching to an application-defined contrast curve by setting
+ * {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} to CONTRAST_CURVE, the curve is defined
+ * per-channel with a set of <code>(in, out)</code> points that specify the
+ * mapping from input high-bit-depth pixel value to the output
+ * low-bit-depth value. Since the actual pixel ranges of both input
+ * and output may change depending on the camera pipeline, the values
+ * are specified by normalized floating-point numbers.</p>
+ * <p>More-complex color mapping operations such as 3D color look-up
+ * tables, selective chroma enhancement, or other non-linear color
+ * transforms will be disabled when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * CONTRAST_CURVE.</p>
+ * <p>When using either FAST or HIGH_QUALITY, the camera device will
+ * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed},
+ * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, and {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}.
+ * These values are always available, and as close as possible to the
+ * actually used nonlinear/nonglobal transforms.</p>
+ * <p>If a request is sent with TRANSFORM_MATRIX with the camera device's
+ * provided curve in FAST or HIGH_QUALITY, the image's tonemap will be
+ * roughly the same.</p>
+ *
+ * @see CaptureRequest#TONEMAP_CURVE_BLUE
+ * @see CaptureRequest#TONEMAP_CURVE_GREEN
+ * @see CaptureRequest#TONEMAP_CURVE_RED
+ * @see CaptureRequest#TONEMAP_MODE
* @see #TONEMAP_MODE_CONTRAST_CURVE
* @see #TONEMAP_MODE_FAST
* @see #TONEMAP_MODE_HIGH_QUALITY
@@ -971,53 +2080,95 @@ public final class CaptureResult extends CameraMetadata {
new Key<Integer>("android.tonemap.mode", int.class);
/**
- * <p>
- * This LED is nominally used to indicate to the user
+ * <p>This LED is nominally used to indicate to the user
* that the camera is powered on and may be streaming images back to the
* Application Processor. In certain rare circumstances, the OS may
* disable this when video is processed locally and not transmitted to
- * any untrusted applications.
- * </p><p>
- * In particular, the LED *must* always be on when the data could be
- * transmitted off the device. The LED *should* always be on whenever
- * data is stored locally on the device.
- * </p><p>
- * The LED *may* be off if a trusted application is using the data that
- * doesn't violate the above rules.
- * </p>
- *
+ * any untrusted applications.</p>
+ * <p>In particular, the LED <em>must</em> always be on when the data could be
+ * transmitted off the device. The LED <em>should</em> always be on whenever
+ * data is stored locally on the device.</p>
+ * <p>The LED <em>may</em> be off if a trusted application is using the data that
+ * doesn't violate the above rules.</p>
* @hide
*/
public static final Key<Boolean> LED_TRANSMIT =
new Key<Boolean>("android.led.transmit", boolean.class);
/**
- * <p>
- * Whether black-level compensation is locked
- * to its current values, or is free to vary
- * </p>
- * <p>
- * When set to ON, the values used for black-level
- * compensation must not change until the lock is set to
- * OFF
- * </p><p>
- * Since changes to certain capture parameters (such as
- * exposure time) may require resetting of black level
- * compensation, the HAL must report whether setting the
- * black level lock was successful in the output result
- * metadata.
- * </p><p>
- * The black level locking must happen at the sensor, and not at the ISP.
- * If for some reason black level locking is no longer legal (for example,
- * the analog gain has changed, which forces black levels to be
- * recalculated), then the HAL is free to override this request (and it
- * must report 'OFF' when this does happen) until the next time locking
- * is legal again.
- * </p>
+ * <p>Whether black-level compensation is locked
+ * to its current values, or is free to vary.</p>
+ * <p>Whether the black level offset was locked for this frame. Should be
+ * ON if {@link CaptureRequest#BLACK_LEVEL_LOCK android.blackLevel.lock} was ON in the capture request, unless
+ * a change in other capture settings forced the camera device to
+ * perform a black level reset.</p>
+ *
+ * @see CaptureRequest#BLACK_LEVEL_LOCK
*/
public static final Key<Boolean> BLACK_LEVEL_LOCK =
new Key<Boolean>("android.blackLevel.lock", boolean.class);
+ /**
+ * <p>The frame number corresponding to the last request
+ * with which the output result (metadata + buffers) has been fully
+ * synchronized.</p>
+ * <p>When a request is submitted to the camera device, there is usually a
+ * delay of several frames before the controls get applied. A camera
+ * device may either choose to account for this delay by implementing a
+ * pipeline and carefully submit well-timed atomic control updates, or
+ * it may start streaming control changes that span over several frame
+ * boundaries.</p>
+ * <p>In the latter case, whenever a request's settings change relative to
+ * the previous submitted request, the full set of changes may take
+ * multiple frame durations to fully take effect. Some settings may
+ * take effect sooner (in less frame durations) than others.</p>
+ * <p>While a set of control changes are being propagated, this value
+ * will be CONVERGING.</p>
+ * <p>Once it is fully known that a set of control changes have been
+ * finished propagating, and the resulting updated control settings
+ * have been read back by the camera device, this value will be set
+ * to a non-negative frame number (corresponding to the request to
+ * which the results have synchronized to).</p>
+ * <p>Older camera device implementations may not have a way to detect
+ * when all camera controls have been applied, and will always set this
+ * value to UNKNOWN.</p>
+ * <p>FULL capability devices will always have this value set to the
+ * frame number of the request corresponding to this result.</p>
+ * <p><em>Further details</em>:</p>
+ * <ul>
+ * <li>Whenever a request differs from the last request, any future
+ * results not yet returned may have this value set to CONVERGING (this
+ * could include any in-progress captures not yet returned by the camera
+ * device, for more details see pipeline considerations below).</li>
+ * <li>Submitting a series of multiple requests that differ from the
+ * previous request (e.g. r1, r2, r3 s.t. r1 != r2 != r3)
+ * moves the new synchronization frame to the last non-repeating
+ * request (using the smallest frame number from the contiguous list of
+ * repeating requests).</li>
+ * <li>Submitting the same request repeatedly will not change this value
+ * to CONVERGING, if it was already a non-negative value.</li>
+ * <li>When this value changes to non-negative, that means that all of the
+ * metadata controls from the request have been applied, all of the
+ * metadata controls from the camera device have been read to the
+ * updated values (into the result), and all of the graphics buffers
+ * corresponding to this result are also synchronized to the request.</li>
+ * </ul>
+ * <p><em>Pipeline considerations</em>:</p>
+ * <p>Submitting a request with updated controls relative to the previously
+ * submitted requests may also invalidate the synchronization state
+ * of all the results corresponding to currently in-flight requests.</p>
+ * <p>In other words, results for this current request and up to
+ * {@link CameraCharacteristics#REQUEST_PIPELINE_MAX_DEPTH android.request.pipelineMaxDepth} prior requests may have their
+ * android.sync.frameNumber change to CONVERGING.</p>
+ *
+ * @see CameraCharacteristics#REQUEST_PIPELINE_MAX_DEPTH
+ * @see #SYNC_FRAME_NUMBER_CONVERGING
+ * @see #SYNC_FRAME_NUMBER_UNKNOWN
+ * @hide
+ */
+ public static final Key<Integer> SYNC_FRAME_NUMBER =
+ new Key<Integer>("android.sync.frameNumber", int.class);
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index 40586f0..2c8a5c2 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -106,9 +106,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
private final Runnable mCallOnClosed = new Runnable() {
public void run() {
- if (!CameraDevice.this.isClosed()) {
- mDeviceListener.onClosed(CameraDevice.this);
- }
+ mDeviceListener.onClosed(CameraDevice.this);
}
};
@@ -351,8 +349,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
}
- @Override
- public void waitUntilIdle() throws CameraAccessException {
+ private void waitUntilIdle() throws CameraAccessException {
synchronized (mLock) {
checkIfCameraClosed();
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 072c5bb..0d4a4cb 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -105,6 +105,18 @@ public class CameraMetadataNative extends CameraMetadata implements Parcelable {
}
/**
+ * Set the global client-side vendor tag descriptor to allow use of vendor
+ * tags in camera applications.
+ *
+ * @return int A native status_t value corresponding to one of the
+ * {@link CameraBinderDecorator} integer constants.
+ * @see CameraBinderDecorator#throwOnError
+ *
+ * @hide
+ */
+ public static native int nativeSetupGlobalVendorTagDescriptor();
+
+ /**
* Set a camera metadata field to a value. The field definitions can be
* found in {@link CameraCharacteristics}, {@link CaptureResult}, and
* {@link CaptureRequest}.
@@ -448,7 +460,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 +469,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 +565,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 +605,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 +632,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/camera2/utils/CameraBinderDecorator.java b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java
index e535e00..328ccbe 100644
--- a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java
+++ b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java
@@ -64,47 +64,7 @@ public class CameraBinderDecorator {
// int return type => status_t => convert to exception
if (m.getReturnType() == Integer.TYPE) {
int returnValue = (Integer) result;
-
- switch (returnValue) {
- case NO_ERROR:
- return;
- case PERMISSION_DENIED:
- throw new SecurityException("Lacking privileges to access camera service");
- case ALREADY_EXISTS:
- // This should be handled at the call site. Typically this isn't bad,
- // just means we tried to do an operation that already completed.
- return;
- case BAD_VALUE:
- throw new IllegalArgumentException("Bad argument passed to camera service");
- case DEAD_OBJECT:
- UncheckedThrow.throwAnyException(new CameraRuntimeException(
- CAMERA_DISCONNECTED));
- case EACCES:
- UncheckedThrow.throwAnyException(new CameraRuntimeException(
- CAMERA_DISABLED));
- case EBUSY:
- UncheckedThrow.throwAnyException(new CameraRuntimeException(
- CAMERA_IN_USE));
- case EUSERS:
- UncheckedThrow.throwAnyException(new CameraRuntimeException(
- MAX_CAMERAS_IN_USE));
- case ENODEV:
- UncheckedThrow.throwAnyException(new CameraRuntimeException(
- CAMERA_DISCONNECTED));
- case EOPNOTSUPP:
- UncheckedThrow.throwAnyException(new CameraRuntimeException(
- CAMERA_DEPRECATED_HAL));
- }
-
- /**
- * Trap the rest of the negative return values. If we have known
- * error codes i.e. ALREADY_EXISTS that aren't really runtime
- * errors, then add them to the top switch statement
- */
- if (returnValue < 0) {
- throw new UnsupportedOperationException(String.format("Unknown error %d",
- returnValue));
- }
+ throwOnError(returnValue);
}
}
@@ -131,6 +91,54 @@ public class CameraBinderDecorator {
}
/**
+ * Throw error codes returned by the camera service as exceptions.
+ *
+ * @param errorFlag error to throw as an exception.
+ */
+ public static void throwOnError(int errorFlag) {
+ switch (errorFlag) {
+ case NO_ERROR:
+ return;
+ case PERMISSION_DENIED:
+ throw new SecurityException("Lacking privileges to access camera service");
+ case ALREADY_EXISTS:
+ // This should be handled at the call site. Typically this isn't bad,
+ // just means we tried to do an operation that already completed.
+ return;
+ case BAD_VALUE:
+ throw new IllegalArgumentException("Bad argument passed to camera service");
+ case DEAD_OBJECT:
+ UncheckedThrow.throwAnyException(new CameraRuntimeException(
+ CAMERA_DISCONNECTED));
+ case EACCES:
+ UncheckedThrow.throwAnyException(new CameraRuntimeException(
+ CAMERA_DISABLED));
+ case EBUSY:
+ UncheckedThrow.throwAnyException(new CameraRuntimeException(
+ CAMERA_IN_USE));
+ case EUSERS:
+ UncheckedThrow.throwAnyException(new CameraRuntimeException(
+ MAX_CAMERAS_IN_USE));
+ case ENODEV:
+ UncheckedThrow.throwAnyException(new CameraRuntimeException(
+ CAMERA_DISCONNECTED));
+ case EOPNOTSUPP:
+ UncheckedThrow.throwAnyException(new CameraRuntimeException(
+ CAMERA_DEPRECATED_HAL));
+ }
+
+ /**
+ * Trap the rest of the negative return values. If we have known
+ * error codes i.e. ALREADY_EXISTS that aren't really runtime
+ * errors, then add them to the top switch statement
+ */
+ if (errorFlag < 0) {
+ throw new UnsupportedOperationException(String.format("Unknown error %d",
+ errorFlag));
+ }
+ }
+
+ /**
* <p>
* Wraps the type T with a proxy that will check 'status_t' return codes
* from the native side of the camera service, and throw Java exceptions
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/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index f1e7e98..465d142 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -19,6 +19,7 @@ package android.hardware.input;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.KeyboardLayout;
import android.hardware.input.IInputDevicesChangedListener;
+import android.hardware.input.TouchCalibration;
import android.os.IBinder;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -39,6 +40,11 @@ interface IInputManager {
// applications, the caller must have the INJECT_EVENTS permission.
boolean injectInputEvent(in InputEvent ev, int mode);
+ // Calibrate input device position
+ TouchCalibration getTouchCalibrationForInputDevice(String inputDeviceDescriptor, int rotation);
+ void setTouchCalibrationForInputDevice(String inputDeviceDescriptor, int rotation,
+ in TouchCalibration calibration);
+
// Keyboard layouts configuration.
KeyboardLayout[] getKeyboardLayouts();
KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index a2aeafb..e3a3830 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -500,6 +500,45 @@ public final class InputManager {
}
/**
+ * Gets the TouchCalibration applied to the specified input device's coordinates.
+ *
+ * @param inputDeviceDescriptor The input device descriptor.
+ * @return The TouchCalibration currently assigned for use with the given
+ * input device. If none is set, an identity TouchCalibration is returned.
+ *
+ * @hide
+ */
+ public TouchCalibration getTouchCalibration(String inputDeviceDescriptor, int surfaceRotation) {
+ try {
+ return mIm.getTouchCalibrationForInputDevice(inputDeviceDescriptor, surfaceRotation);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not get calibration matrix for input device.", ex);
+ return TouchCalibration.IDENTITY;
+ }
+ }
+
+ /**
+ * Sets the TouchCalibration to apply to the specified input device's coordinates.
+ * <p>
+ * This method may have the side-effect of causing the input device in question
+ * to be reconfigured. Requires {@link android.Manifest.permissions.SET_INPUT_CALIBRATION}.
+ * </p>
+ *
+ * @param inputDeviceDescriptor The input device descriptor.
+ * @param calibration The calibration to be applied
+ *
+ * @hide
+ */
+ public void setTouchCalibration(String inputDeviceDescriptor, int surfaceRotation,
+ TouchCalibration calibration) {
+ try {
+ mIm.setTouchCalibrationForInputDevice(inputDeviceDescriptor, surfaceRotation, calibration);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not set calibration matrix for input device.", ex);
+ }
+ }
+
+ /**
* Gets the mouse pointer speed.
* <p>
* Only returns the permanent mouse pointer speed. Ignores any temporary pointer
diff --git a/core/java/android/hardware/input/TouchCalibration.aidl b/core/java/android/hardware/input/TouchCalibration.aidl
new file mode 100644
index 0000000..2c28774
--- /dev/null
+++ b/core/java/android/hardware/input/TouchCalibration.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable TouchCalibration;
diff --git a/core/java/android/hardware/input/TouchCalibration.java b/core/java/android/hardware/input/TouchCalibration.java
new file mode 100644
index 0000000..025fad0
--- /dev/null
+++ b/core/java/android/hardware/input/TouchCalibration.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Encapsulates calibration data for input devices.
+ *
+ * @hide
+ */
+public class TouchCalibration implements Parcelable {
+
+ public static final TouchCalibration IDENTITY = new TouchCalibration();
+
+ public static final Parcelable.Creator<TouchCalibration> CREATOR
+ = new Parcelable.Creator<TouchCalibration>() {
+ public TouchCalibration createFromParcel(Parcel in) {
+ return new TouchCalibration(in);
+ }
+
+ public TouchCalibration[] newArray(int size) {
+ return new TouchCalibration[size];
+ }
+ };
+
+ private final float mXScale, mXYMix, mXOffset;
+ private final float mYXMix, mYScale, mYOffset;
+
+ /**
+ * Create a new TouchCalibration initialized to the identity transformation.
+ */
+ public TouchCalibration() {
+ this(1,0,0,0,1,0);
+ }
+
+ /**
+ * Create a new TouchCalibration from affine transformation paramters.
+ * @param xScale Influence of input x-axis value on output x-axis value.
+ * @param xyMix Influence of input y-axis value on output x-axis value.
+ * @param xOffset Constant offset to be applied to output x-axis value.
+ * @param yXMix Influence of input x-axis value on output y-axis value.
+ * @param yScale Influence of input y-axis value on output y-axis value.
+ * @param yOffset Constant offset to be applied to output y-axis value.
+ */
+ public TouchCalibration(float xScale, float xyMix, float xOffset,
+ float yxMix, float yScale, float yOffset) {
+ mXScale = xScale;
+ mXYMix = xyMix;
+ mXOffset = xOffset;
+ mYXMix = yxMix;
+ mYScale = yScale;
+ mYOffset = yOffset;
+ }
+
+ public TouchCalibration(Parcel in) {
+ mXScale = in.readFloat();
+ mXYMix = in.readFloat();
+ mXOffset = in.readFloat();
+ mYXMix = in.readFloat();
+ mYScale = in.readFloat();
+ mYOffset = in.readFloat();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(mXScale);
+ dest.writeFloat(mXYMix);
+ dest.writeFloat(mXOffset);
+ dest.writeFloat(mYXMix);
+ dest.writeFloat(mYScale);
+ dest.writeFloat(mYOffset);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public float[] getAffineTransform() {
+ return new float[] { mXScale, mXYMix, mXOffset, mYXMix, mYScale, mYOffset };
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (obj instanceof TouchCalibration) {
+ TouchCalibration cal = (TouchCalibration)obj;
+
+ return (cal.mXScale == mXScale) &&
+ (cal.mXYMix == mXYMix) &&
+ (cal.mXOffset == mXOffset) &&
+ (cal.mYXMix == mYXMix) &&
+ (cal.mYScale == mYScale) &&
+ (cal.mYOffset == mYOffset);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Float.floatToIntBits(mXScale) ^
+ Float.floatToIntBits(mXYMix) ^
+ Float.floatToIntBits(mXOffset) ^
+ Float.floatToIntBits(mYXMix) ^
+ Float.floatToIntBits(mYScale) ^
+ Float.floatToIntBits(mYOffset);
+ }
+}
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/UsbConfiguration.java b/core/java/android/hardware/usb/UsbConfiguration.java
new file mode 100644
index 0000000..92d6f75
--- /dev/null
+++ b/core/java/android/hardware/usb/UsbConfiguration.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class representing a configuration on a {@link UsbDevice}.
+ * A USB configuration can have one or more interfaces, each one providing a different
+ * piece of functionality, separate from the other interfaces.
+ * An interface will have one or more {@link UsbEndpoint}s, which are the
+ * channels by which the host transfers data with the device.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about communicating with USB hardware, read the
+ * <a href="{@docRoot}guide/topics/usb/index.html">USB</a> developer guide.</p>
+ * </div>
+ */
+public class UsbConfiguration implements Parcelable {
+
+ private final int mId;
+ private final String mName;
+ private final int mAttributes;
+ private final int mMaxPower;
+ private Parcelable[] mInterfaces;
+
+ /**
+ * Mask for "self-powered" bit in the configuration's attributes.
+ * @see #getAttributes
+ */
+ public static final int ATTR_SELF_POWERED_MASK = 1 << 6;
+
+ /**
+ * Mask for "remote wakeup" bit in the configuration's attributes.
+ * @see #getAttributes
+ */
+ public static final int ATTR_REMOTE_WAKEUP_MASK = 1 << 5;
+
+ /**
+ * UsbConfiguration should only be instantiated by UsbService implementation
+ * @hide
+ */
+ public UsbConfiguration(int id, String name, int attributes, int maxPower) {
+ mId = id;
+ mName = name;
+ mAttributes = attributes;
+ mMaxPower = maxPower;
+ }
+
+ /**
+ * Returns the configuration's ID field.
+ * This is an integer that uniquely identifies the configuration on the device.
+ *
+ * @return the configuration's ID
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the configuration's name.
+ *
+ * @return the configuration's name
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the configuration's attributes field.
+ * This field contains a bit field with the following flags:
+ *
+ * Bit 7: always set to 1
+ * Bit 6: self-powered
+ * Bit 5: remote wakeup enabled
+ * Bit 0-4: reserved
+ * @see #ATTR_SELF_POWERED_MASK
+ * @see #ATTR_REMOTE_WAKEUP_MASK
+ * @return the configuration's attributes
+ */
+ public int getAttributes() {
+ return mAttributes;
+ }
+
+ /**
+ * Returns the configuration's max power consumption, in milliamps.
+ *
+ * @return the configuration's max power
+ */
+ public int getMaxPower() {
+ return mMaxPower * 2;
+ }
+
+ /**
+ * Returns the number of {@link UsbInterface}s this configuration contains.
+ *
+ * @return the number of endpoints
+ */
+ public int getInterfaceCount() {
+ return mInterfaces.length;
+ }
+
+ /**
+ * Returns the {@link UsbInterface} at the given index.
+ *
+ * @return the interface
+ */
+ public UsbInterface getInterface(int index) {
+ return (UsbInterface)mInterfaces[index];
+ }
+
+ /**
+ * Only used by UsbService implementation
+ * @hide
+ */
+ public void setInterfaces(Parcelable[] interfaces) {
+ mInterfaces = interfaces;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("UsbConfiguration[mId=" + mId +
+ ",mName=" + mName + ",mAttributes=" + mAttributes +
+ ",mMaxPower=" + mMaxPower + ",mInterfaces=[");
+ for (int i = 0; i < mInterfaces.length; i++) {
+ builder.append("\n");
+ builder.append(mInterfaces[i].toString());
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+
+ public static final Parcelable.Creator<UsbConfiguration> CREATOR =
+ new Parcelable.Creator<UsbConfiguration>() {
+ public UsbConfiguration createFromParcel(Parcel in) {
+ int id = in.readInt();
+ String name = in.readString();
+ int attributes = in.readInt();
+ int maxPower = in.readInt();
+ Parcelable[] interfaces = in.readParcelableArray(UsbInterface.class.getClassLoader());
+ UsbConfiguration configuration = new UsbConfiguration(id, name, attributes, maxPower);
+ configuration.setInterfaces(interfaces);
+ return configuration;
+ }
+
+ public UsbConfiguration[] newArray(int size) {
+ return new UsbConfiguration[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mId);
+ parcel.writeString(mName);
+ parcel.writeInt(mAttributes);
+ parcel.writeInt(mMaxPower);
+ parcel.writeParcelableArray(mInterfaces, 0);
+ }
+}
diff --git a/core/java/android/hardware/usb/UsbDevice.java b/core/java/android/hardware/usb/UsbDevice.java
index d1e63f6..d90e06e 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
@@ -54,7 +50,10 @@ public class UsbDevice implements Parcelable {
private final int mClass;
private final int mSubclass;
private final int mProtocol;
- private final Parcelable[] mInterfaces;
+ private Parcelable[] mConfigurations;
+
+ // list of all interfaces on the device
+ private UsbInterface[] mInterfaces;
/**
* UsbDevice should only be instantiated by UsbService implementation
@@ -62,8 +61,7 @@ public class UsbDevice implements Parcelable {
*/
public UsbDevice(String name, int vendorId, int productId,
int Class, int subClass, int protocol,
- String manufacturerName, String productName, String serialNumber,
- Parcelable[] interfaces) {
+ String manufacturerName, String productName, String serialNumber) {
mName = name;
mVendorId = vendorId;
mProductId = productId;
@@ -73,7 +71,6 @@ public class UsbDevice implements Parcelable {
mManufacturerName = manufacturerName;
mProductName = productName;
mSerialNumber = serialNumber;
- mInterfaces = interfaces;
}
/**
@@ -173,21 +170,74 @@ public class UsbDevice implements Parcelable {
}
/**
+ * Returns the number of {@link UsbConfiguration}s this device contains.
+ *
+ * @return the number of configurations
+ */
+ public int getConfigurationCount() {
+ return mConfigurations.length;
+ }
+
+ /**
+ * Returns the {@link UsbConfiguration} at the given index.
+ *
+ * @return the configuration
+ */
+ public UsbConfiguration getConfiguration(int index) {
+ return (UsbConfiguration)mConfigurations[index];
+ }
+
+ private UsbInterface[] getInterfaceList() {
+ if (mInterfaces == null) {
+ int configurationCount = mConfigurations.length;
+ int interfaceCount = 0;
+ for (int i = 0; i < configurationCount; i++) {
+ UsbConfiguration configuration = (UsbConfiguration)mConfigurations[i];
+ interfaceCount += configuration.getInterfaceCount();
+ }
+
+ mInterfaces = new UsbInterface[interfaceCount];
+ int offset = 0;
+ for (int i = 0; i < configurationCount; i++) {
+ UsbConfiguration configuration = (UsbConfiguration)mConfigurations[i];
+ interfaceCount = configuration.getInterfaceCount();
+ for (int j = 0; j < interfaceCount; j++) {
+ mInterfaces[offset++] = configuration.getInterface(j);
+ }
+ }
+ }
+
+ return mInterfaces;
+ }
+
+ /**
* Returns the number of {@link UsbInterface}s this device contains.
+ * For devices with multiple configurations, you will probably want to use
+ * {@link UsbConfiguration#getInterfaceCount} instead.
*
* @return the number of interfaces
*/
public int getInterfaceCount() {
- return mInterfaces.length;
+ return getInterfaceList().length;
}
/**
* Returns the {@link UsbInterface} at the given index.
+ * For devices with multiple configurations, you will probably want to use
+ * {@link UsbConfiguration#getInterface} instead.
*
* @return the interface
*/
public UsbInterface getInterface(int index) {
- return (UsbInterface)mInterfaces[index];
+ return getInterfaceList()[index];
+ }
+
+ /**
+ * Only used by UsbService implementation
+ * @hide
+ */
+ public void setConfigurations(Parcelable[] configuration) {
+ mConfigurations = configuration;
}
@Override
@@ -208,11 +258,17 @@ public class UsbDevice implements Parcelable {
@Override
public String toString() {
- return "UsbDevice[mName=" + mName + ",mVendorId=" + mVendorId +
- ",mProductId=" + mProductId + ",mClass=" + mClass +
- ",mSubclass=" + mSubclass + ",mProtocol=" + mProtocol +
+ StringBuilder builder = new StringBuilder("UsbDevice[mName=" + mName +
+ ",mVendorId=" + mVendorId + ",mProductId=" + mProductId +
+ ",mClass=" + mClass + ",mSubclass=" + mSubclass + ",mProtocol=" + mProtocol +
",mManufacturerName=" + mManufacturerName + ",mProductName=" + mProductName +
- ",mSerialNumber=" + mSerialNumber + ",mInterfaces=" + mInterfaces + "]";
+ ",mSerialNumber=" + mSerialNumber + ",mConfigurations=[");
+ for (int i = 0; i < mConfigurations.length; i++) {
+ builder.append("\n");
+ builder.append(mConfigurations[i].toString());
+ }
+ builder.append("]");
+ return builder.toString();
}
public static final Parcelable.Creator<UsbDevice> CREATOR =
@@ -227,9 +283,11 @@ public class UsbDevice implements Parcelable {
String manufacturerName = in.readString();
String productName = in.readString();
String serialNumber = in.readString();
- Parcelable[] interfaces = in.readParcelableArray(UsbInterface.class.getClassLoader());
- return new UsbDevice(name, vendorId, productId, clasz, subClass, protocol,
- manufacturerName, productName, serialNumber, interfaces);
+ Parcelable[] configurations = in.readParcelableArray(UsbInterface.class.getClassLoader());
+ UsbDevice device = new UsbDevice(name, vendorId, productId, clasz, subClass, protocol,
+ manufacturerName, productName, serialNumber);
+ device.setConfigurations(configurations);
+ return device;
}
public UsbDevice[] newArray(int size) {
@@ -251,7 +309,7 @@ public class UsbDevice implements Parcelable {
parcel.writeString(mManufacturerName);
parcel.writeString(mProductName);
parcel.writeString(mSerialNumber);
- parcel.writeParcelableArray(mInterfaces, 0);
+ parcel.writeParcelableArray(mConfigurations, 0);
}
public static int getDeviceId(String name) {
diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java
index 389475f..6283951 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -101,6 +101,25 @@ public class UsbDeviceConnection {
}
/**
+ * Sets the current {@link android.hardware.usb.UsbInterface}.
+ * Used to select between two interfaces with the same ID but different alternate setting.
+ *
+ * @return true if the interface was successfully released
+ */
+ public boolean setInterface(UsbInterface intf) {
+ return native_set_interface(intf.getId(), intf.getAlternateSetting());
+ }
+
+ /**
+ * Sets the device's current {@link android.hardware.usb.UsbConfiguration}.
+ *
+ * @return true if the configuration was successfully set
+ */
+ public boolean setConfiguration(UsbConfiguration configuration) {
+ return native_set_configuration(configuration.getId());
+ }
+
+ /**
* Performs a control transaction on endpoint zero for this device.
* The direction of the transfer is determined by the request type.
* If requestType & {@link UsbConstants#USB_ENDPOINT_DIR_MASK} is
@@ -236,6 +255,8 @@ public class UsbDeviceConnection {
private native byte[] native_get_desc();
private native boolean native_claim_interface(int interfaceID, boolean force);
private native boolean native_release_interface(int interfaceID);
+ private native boolean native_set_interface(int interfaceID, int alternateSetting);
+ private native boolean native_set_configuration(int configurationID);
private native int native_control_request(int requestType, int request, int value,
int index, byte[] buffer, int offset, int length, int timeout);
private native int native_bulk_request(int endpoint, byte[] buffer,
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..de01a88 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;
@@ -36,27 +35,31 @@ import android.os.Parcelable;
public class UsbInterface implements Parcelable {
private final int mId;
+ private final int mAlternateSetting;
+ private final String mName;
private final int mClass;
private final int mSubclass;
private final int mProtocol;
- private final Parcelable[] mEndpoints;
+ private Parcelable[] mEndpoints;
/**
* UsbInterface should only be instantiated by UsbService implementation
* @hide
*/
- public UsbInterface(int id, int Class, int subClass, int protocol,
- Parcelable[] endpoints) {
+ public UsbInterface(int id, int alternateSetting, String name,
+ int Class, int subClass, int protocol) {
mId = id;
+ mAlternateSetting = alternateSetting;
+ mName = name;
mClass = Class;
mSubclass = subClass;
mProtocol = protocol;
- mEndpoints = endpoints;
}
/**
- * Returns the interface's ID field.
- * This is an integer that uniquely identifies the interface on the device.
+ * Returns the interface's bInterfaceNumber field.
+ * This is an integer that along with the alternate setting uniquely identifies
+ * the interface on the device.
*
* @return the interface's ID
*/
@@ -65,6 +68,28 @@ public class UsbInterface implements Parcelable {
}
/**
+ * Returns the interface's bAlternateSetting field.
+ * This is an integer that along with the ID uniquely identifies
+ * the interface on the device.
+ * {@link UsbDeviceConnection#setInterface} can be used to switch between
+ * two interfaces with the same ID but different alternate setting.
+ *
+ * @return the interface's alternate setting
+ */
+ public int getAlternateSetting() {
+ return mAlternateSetting;
+ }
+
+ /**
+ * Returns the interface's name.
+ *
+ * @return the interface's name
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
* Returns the interface's class field.
* Some useful constants for USB classes can be found in {@link UsbConstants}
*
@@ -110,22 +135,42 @@ public class UsbInterface implements Parcelable {
return (UsbEndpoint)mEndpoints[index];
}
+ /**
+ * Only used by UsbService implementation
+ * @hide
+ */
+ public void setEndpoints(Parcelable[] endpoints) {
+ mEndpoints = endpoints;
+ }
+
@Override
public String toString() {
- return "UsbInterface[mId=" + mId + ",mClass=" + mClass +
+ StringBuilder builder = new StringBuilder("UsbInterface[mId=" + mId +
+ ",mAlternateSetting=" + mAlternateSetting +
+ ",mName=" + mName + ",mClass=" + mClass +
",mSubclass=" + mSubclass + ",mProtocol=" + mProtocol +
- ",mEndpoints=" + mEndpoints + "]";
+ ",mEndpoints=[");
+ for (int i = 0; i < mEndpoints.length; i++) {
+ builder.append("\n");
+ builder.append(mEndpoints[i].toString());
+ }
+ builder.append("]");
+ return builder.toString();
}
public static final Parcelable.Creator<UsbInterface> CREATOR =
new Parcelable.Creator<UsbInterface>() {
public UsbInterface createFromParcel(Parcel in) {
int id = in.readInt();
+ int alternateSetting = in.readInt();
+ String name = in.readString();
int Class = in.readInt();
int subClass = in.readInt();
int protocol = in.readInt();
Parcelable[] endpoints = in.readParcelableArray(UsbEndpoint.class.getClassLoader());
- return new UsbInterface(id, Class, subClass, protocol, endpoints);
+ UsbInterface intf = new UsbInterface(id, alternateSetting, name, Class, subClass, protocol);
+ intf.setEndpoints(endpoints);
+ return intf;
}
public UsbInterface[] newArray(int size) {
@@ -139,6 +184,8 @@ public class UsbInterface implements Parcelable {
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mId);
+ parcel.writeInt(mAlternateSetting);
+ parcel.writeString(mName);
parcel.writeInt(mClass);
parcel.writeInt(mSubclass);
parcel.writeInt(mProtocol);
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..89c17c7 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");
@@ -453,6 +421,13 @@ public class CaptivePortalTracker extends StateMachine {
case ConnectivityManager.TYPE_WIFI:
WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
if (currentWifiInfo != null) {
+ // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
+ // surrounded by double quotation marks (thus violating the Javadoc), but this
+ // was changed to match the Javadoc in API 17. Since clients may have started
+ // sanitizing the output of this method since API 17 was released, we should
+ // not change it here as it would become impossible to tell whether the SSID is
+ // simply being surrounded by quotes due to the API, or whether those quotes
+ // are actually part of the SSID.
latencyBroadcast.putExtra(EXTRA_SSID, currentWifiInfo.getSSID());
latencyBroadcast.putExtra(EXTRA_BSSID, currentWifiInfo.getBSSID());
} else {
@@ -464,7 +439,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 4eecfa9..5b2a29e 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -23,10 +23,14 @@ import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
+import android.os.IBinder;
+import android.os.INetworkActivityListener;
+import android.os.INetworkManagementService;
import android.os.Messenger;
import android.os.RemoteException;
-import android.os.ResultReceiver;
+import android.os.ServiceManager;
import android.provider.Settings;
+import android.util.ArrayMap;
import java.net.InetAddress;
@@ -77,7 +81,7 @@ public class ConnectivityManager {
/**
* Identical to {@link #CONNECTIVITY_ACTION} broadcast, but sent without any
- * applicable {@link Settings.Secure#CONNECTIVITY_CHANGE_DELAY}.
+ * applicable {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY}.
*
* @hide
*/
@@ -403,6 +407,8 @@ public class ConnectivityManager {
private final String mPackageName;
+ private INetworkManagementService mNMService;
+
/**
* Tests if a given integer represents a valid network type.
* @param networkType the type to be tested
@@ -803,6 +809,8 @@ public class ConnectivityManager {
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface. An attempt to add a route that
* already exists is ignored, but treated as successful.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* @param networkType the type of the network over which traffic to the specified
* host is to be routed
* @param hostAddress the IP address of the host to which the route is desired
@@ -906,6 +914,92 @@ public class ConnectivityManager {
}
/**
+ * Callback for use with {@link ConnectivityManager#registerNetworkActiveListener} to
+ * find out when the current network has gone in to a high power state.
+ */
+ public interface OnNetworkActiveListener {
+ /**
+ * Called on the main thread of the process to report that the current data network
+ * has become active, and it is now a good time to perform any pending network
+ * operations. Note that this listener only tells you when the network becomes
+ * active; if at any other time you want to know whether it is active (and thus okay
+ * to initiate network traffic), you can retrieve its instantaneous state with
+ * {@link ConnectivityManager#isNetworkActive}.
+ */
+ public void onNetworkActive();
+ }
+
+ private INetworkManagementService getNetworkManagementService() {
+ synchronized (this) {
+ if (mNMService != null) {
+ return mNMService;
+ }
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ mNMService = INetworkManagementService.Stub.asInterface(b);
+ return mNMService;
+ }
+ }
+
+ private final ArrayMap<OnNetworkActiveListener, INetworkActivityListener>
+ mNetworkActivityListeners
+ = new ArrayMap<OnNetworkActiveListener, INetworkActivityListener>();
+
+ /**
+ * Start listening to reports when the data network is active, meaning it is
+ * a good time to perform network traffic. Use {@link #isNetworkActive()}
+ * to determine the current state of the network after registering the listener.
+ *
+ * @param l The listener to be told when the network is active.
+ */
+ public void registerNetworkActiveListener(final OnNetworkActiveListener l) {
+ INetworkActivityListener rl = new INetworkActivityListener.Stub() {
+ @Override
+ public void onNetworkActive() throws RemoteException {
+ l.onNetworkActive();
+ }
+ };
+
+ try {
+ getNetworkManagementService().registerNetworkActivityListener(rl);
+ mNetworkActivityListeners.put(l, rl);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Remove network active listener previously registered with
+ * {@link #registerNetworkActiveListener}.
+ *
+ * @param l Previously registered listener.
+ */
+ public void unregisterNetworkActiveListener(OnNetworkActiveListener l) {
+ INetworkActivityListener rl = mNetworkActivityListeners.get(l);
+ if (rl == null) {
+ throw new IllegalArgumentException("Listener not registered: " + l);
+ }
+ try {
+ getNetworkManagementService().unregisterNetworkActivityListener(rl);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Return whether the data network is currently active. An active network means that
+ * it is currently in a high power state for performing data transmission. On some
+ * types of networks, it may be expensive to move and stay in such a state, so it is
+ * more power efficient to batch network traffic together when the radio is already in
+ * this state. This method tells you whether right now is currently a good time to
+ * initiate network traffic, as the network is already active.
+ */
+ public boolean isNetworkActive() {
+ try {
+ return getNetworkManagementService().isNetworkActive();
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
* {@hide}
*/
public ConnectivityManager(IConnectivityManager service, String packageName) {
@@ -1020,7 +1114,7 @@ public class ConnectivityManager {
/**
* Check if the device allows for tethering. It may be disabled via
- * {@code ro.tether.denied} system property, {@link Settings#TETHER_SUPPORTED} or
+ * {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or
* due to device configuration.
*
* @return a boolean - {@code true} indicating Tethering is supported.
@@ -1208,7 +1302,7 @@ public class ConnectivityManager {
* doing something unusual like general internal filtering this may be useful. On
* a private network where the proxy is not accessible, you may break HTTP using this.
*
- * @param proxyProperties The a {@link ProxyProperites} object defining the new global
+ * @param p The a {@link ProxyProperties} object defining the new global
* HTTP proxy. A {@code null} value will clear the global HTTP proxy.
*
* <p>This method requires the call to hold the permission
@@ -1341,24 +1435,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
@@ -1379,7 +1455,7 @@ public class ConnectivityManager {
/**
* Supply the backend messenger for a network tracker
*
- * @param type NetworkType to set
+ * @param networkType NetworkType to set
* @param messenger {@link Messenger}
* {@hide}
*/
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..ec44661 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
}
@@ -423,4 +418,9 @@ public class EthernetDataTracker extends BaseNetworkStateTracker {
public void supplyMessenger(Messenger messenger) {
// not supported on this network
}
+
+ @Override
+ public String getNetworkInterfaceName() {
+ return mIface;
+ }
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 4bca7fe..381a817 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/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..3c3d8ec 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -306,18 +306,18 @@ public class MobileDataStateTracker extends BaseNetworkStateTracker {
if (VDBG) {
Slog.d(TAG, "TelephonyMgr.DataConnectionStateChanged");
if (mNetworkInfo != null) {
- Slog.d(TAG, "NetworkInfo = " + mNetworkInfo.toString());
- Slog.d(TAG, "subType = " + String.valueOf(mNetworkInfo.getSubtype()));
+ Slog.d(TAG, "NetworkInfo = " + mNetworkInfo);
+ Slog.d(TAG, "subType = " + mNetworkInfo.getSubtype());
Slog.d(TAG, "subType = " + mNetworkInfo.getSubtypeName());
}
if (mLinkProperties != null) {
- Slog.d(TAG, "LinkProperties = " + mLinkProperties.toString());
+ Slog.d(TAG, "LinkProperties = " + mLinkProperties);
} else {
Slog.d(TAG, "LinkProperties = " );
}
if (mLinkCapabilities != null) {
- Slog.d(TAG, "LinkCapabilities = " + mLinkCapabilities.toString());
+ Slog.d(TAG, "LinkCapabilities = " + mLinkCapabilities);
} else {
Slog.d(TAG, "LinkCapabilities = " );
}
@@ -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/NetworkStats.java b/core/java/android/net/NetworkStats.java
index a7aae2a..54d43d3 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -44,6 +44,8 @@ public class NetworkStats implements Parcelable {
public static final String IFACE_ALL = null;
/** {@link #uid} value when UID details unavailable. */
public static final int UID_ALL = -1;
+ /** {@link #tag} value matching any tag. */
+ public static final int TAG_ALL = -1;
/** {@link #set} value when all sets combined. */
public static final int SET_ALL = -1;
/** {@link #set} value where background data is accounted. */
@@ -59,8 +61,9 @@ public class NetworkStats implements Parcelable {
* {@link SystemClock#elapsedRealtime()} timestamp when this data was
* generated.
*/
- private final long elapsedRealtime;
+ private long elapsedRealtime;
private int size;
+ private int capacity;
private String[] iface;
private int[] uid;
private int[] set;
@@ -152,20 +155,27 @@ public class NetworkStats implements Parcelable {
public NetworkStats(long elapsedRealtime, int initialSize) {
this.elapsedRealtime = elapsedRealtime;
this.size = 0;
- this.iface = new String[initialSize];
- this.uid = new int[initialSize];
- this.set = new int[initialSize];
- this.tag = new int[initialSize];
- this.rxBytes = new long[initialSize];
- this.rxPackets = new long[initialSize];
- this.txBytes = new long[initialSize];
- this.txPackets = new long[initialSize];
- this.operations = new long[initialSize];
+ if (initialSize >= 0) {
+ this.capacity = initialSize;
+ this.iface = new String[initialSize];
+ this.uid = new int[initialSize];
+ this.set = new int[initialSize];
+ this.tag = new int[initialSize];
+ this.rxBytes = new long[initialSize];
+ this.rxPackets = new long[initialSize];
+ this.txBytes = new long[initialSize];
+ this.txPackets = new long[initialSize];
+ this.operations = new long[initialSize];
+ } else {
+ // Special case for use by NetworkStatsFactory to start out *really* empty.
+ this.capacity = 0;
+ }
}
public NetworkStats(Parcel parcel) {
elapsedRealtime = parcel.readLong();
size = parcel.readInt();
+ capacity = parcel.readInt();
iface = parcel.createStringArray();
uid = parcel.createIntArray();
set = parcel.createIntArray();
@@ -181,6 +191,7 @@ public class NetworkStats implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(elapsedRealtime);
dest.writeInt(size);
+ dest.writeInt(capacity);
dest.writeStringArray(iface);
dest.writeIntArray(uid);
dest.writeIntArray(set);
@@ -222,8 +233,8 @@ public class NetworkStats implements Parcelable {
* object can be recycled across multiple calls.
*/
public NetworkStats addValues(Entry entry) {
- if (size >= this.iface.length) {
- final int newLength = Math.max(iface.length, 10) * 3 / 2;
+ if (size >= capacity) {
+ final int newLength = Math.max(size, 10) * 3 / 2;
iface = Arrays.copyOf(iface, newLength);
uid = Arrays.copyOf(uid, newLength);
set = Arrays.copyOf(set, newLength);
@@ -233,6 +244,7 @@ public class NetworkStats implements Parcelable {
txBytes = Arrays.copyOf(txBytes, newLength);
txPackets = Arrays.copyOf(txPackets, newLength);
operations = Arrays.copyOf(operations, newLength);
+ capacity = newLength;
}
iface[size] = entry.iface;
@@ -270,6 +282,10 @@ public class NetworkStats implements Parcelable {
return elapsedRealtime;
}
+ public void setElapsedRealtime(long time) {
+ elapsedRealtime = time;
+ }
+
/**
* Return age of this {@link NetworkStats} object with respect to
* {@link SystemClock#elapsedRealtime()}.
@@ -284,7 +300,7 @@ public class NetworkStats implements Parcelable {
@VisibleForTesting
public int internalSize() {
- return iface.length;
+ return capacity;
}
@Deprecated
@@ -491,6 +507,17 @@ public class NetworkStats implements Parcelable {
}
/**
+ * Fast path for battery stats.
+ */
+ public long getTotalPackets() {
+ long total = 0;
+ for (int i = size-1; i >= 0; i--) {
+ total += rxPackets[i] + txPackets[i];
+ }
+ return total;
+ }
+
+ /**
* Subtract the given {@link NetworkStats}, effectively leaving the delta
* between two snapshots in time. Assumes that statistics rows collect over
* time, and that none of them have disappeared.
@@ -507,8 +534,25 @@ public class NetworkStats implements Parcelable {
* If counters have rolled backwards, they are clamped to {@code 0} and
* reported to the given {@link NonMonotonicObserver}.
*/
- public static <C> NetworkStats subtract(
- NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie) {
+ public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
+ NonMonotonicObserver<C> observer, C cookie) {
+ return subtract(left, right, observer, cookie, null);
+ }
+
+ /**
+ * Subtract the two given {@link NetworkStats} objects, returning the delta
+ * between two snapshots in time. Assumes that statistics rows collect over
+ * time, and that none of them have disappeared.
+ * <p>
+ * If counters have rolled backwards, they are clamped to {@code 0} and
+ * reported to the given {@link NonMonotonicObserver}.
+ * <p>
+ * If <var>recycle</var> is supplied, this NetworkStats object will be
+ * reused (and returned) as the result if it is large enough to contain
+ * the data.
+ */
+ public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
+ NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) {
long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime;
if (deltaRealtime < 0) {
if (observer != null) {
@@ -519,7 +563,14 @@ public class NetworkStats implements Parcelable {
// result will have our rows, and elapsed time between snapshots
final Entry entry = new Entry();
- final NetworkStats result = new NetworkStats(deltaRealtime, left.size);
+ final NetworkStats result;
+ if (recycle != null && recycle.capacity >= left.size) {
+ result = recycle;
+ result.size = 0;
+ result.elapsedRealtime = deltaRealtime;
+ } else {
+ result = new NetworkStats(deltaRealtime, left.size);
+ }
for (int i = 0; i < left.size; i++) {
entry.iface = left.iface[i];
entry.uid = left.uid[i];
diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java
index c3e1438..033332c 100644
--- a/core/java/android/net/Proxy.java
+++ b/core/java/android/net/Proxy.java
@@ -66,6 +66,19 @@ public final class Proxy {
/** {@hide} **/
public static final String EXTRA_PROXY_INFO = "proxy";
+ /** @hide */
+ public static final int PROXY_VALID = 0;
+ /** @hide */
+ public static final int PROXY_HOSTNAME_EMPTY = 1;
+ /** @hide */
+ public static final int PROXY_HOSTNAME_INVALID = 2;
+ /** @hide */
+ public static final int PROXY_PORT_EMPTY = 3;
+ /** @hide */
+ public static final int PROXY_PORT_INVALID = 4;
+ /** @hide */
+ public static final int PROXY_EXCLLIST_INVALID = 5;
+
private static ConnectivityManager sConnectivityManager = null;
// Hostname / IP REGEX validation
@@ -77,8 +90,10 @@ public final class Proxy {
private static final Pattern HOSTNAME_PATTERN;
- private static final String EXCLLIST_REGEXP = "$|^(.?" + NAME_IP_REGEX
- + ")+(,(.?" + NAME_IP_REGEX + "))*$";
+ private static final String EXCL_REGEX =
+ "[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*(\\.[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*)*";
+
+ private static final String EXCLLIST_REGEXP = "^$|^" + EXCL_REGEX + "(," + EXCL_REGEX + ")*$";
private static final Pattern EXCLLIST_PATTERN;
@@ -236,36 +251,27 @@ public final class Proxy {
* Validate syntax of hostname, port and exclusion list entries
* {@hide}
*/
- public static void validate(String hostname, String port, String exclList) {
+ public static int validate(String hostname, String port, String exclList) {
Matcher match = HOSTNAME_PATTERN.matcher(hostname);
Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList);
- if (!match.matches()) {
- throw new IllegalArgumentException();
- }
+ if (!match.matches()) return PROXY_HOSTNAME_INVALID;
- if (!listMatch.matches()) {
- throw new IllegalArgumentException();
- }
+ if (!listMatch.matches()) return PROXY_EXCLLIST_INVALID;
- if (hostname.length() > 0 && port.length() == 0) {
- throw new IllegalArgumentException();
- }
+ if (hostname.length() > 0 && port.length() == 0) return PROXY_PORT_EMPTY;
if (port.length() > 0) {
- if (hostname.length() == 0) {
- throw new IllegalArgumentException();
- }
+ if (hostname.length() == 0) return PROXY_HOSTNAME_EMPTY;
int portVal = -1;
try {
portVal = Integer.parseInt(port);
} catch (NumberFormatException ex) {
- throw new IllegalArgumentException();
- }
- if (portVal <= 0 || portVal > 0xFFFF) {
- throw new IllegalArgumentException();
+ return PROXY_PORT_INVALID;
}
+ if (portVal <= 0 || portVal > 0xFFFF) return PROXY_PORT_INVALID;
}
+ return PROXY_VALID;
}
static class AndroidProxySelectorRoutePlanner
diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java
index 010e527..50f45e8 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;
/**
@@ -141,13 +140,9 @@ public class ProxyProperties implements Parcelable {
public boolean isValid() {
if (!TextUtils.isEmpty(mPacFileUrl)) return true;
- try {
- Proxy.validate(mHost == null ? "" : mHost, mPort == 0 ? "" : Integer.toString(mPort),
- mExclusionList == null ? "" : mExclusionList);
- } catch (IllegalArgumentException e) {
- return false;
- }
- return true;
+ return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost,
+ mPort == 0 ? "" : Integer.toString(mPort),
+ mExclusionList == null ? "" : mExclusionList);
}
public java.net.Proxy makeProxy() {
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/VpnService.java b/core/java/android/net/VpnService.java
index d7dc7f5..7385dff 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -151,9 +151,10 @@ public class VpnService extends Service {
}
/**
- * Protect a socket from VPN connections. The socket will be bound to the
- * current default network interface, so its traffic will not be forwarded
- * through VPN. This method is useful if some connections need to be kept
+ * Protect a socket from VPN connections. After protecting, data sent
+ * through this socket will go directly to the underlying network,
+ * so its traffic will not be forwarded through the VPN.
+ * This method is useful if some connections need to be kept
* outside of VPN. For example, a VPN tunnel should protect itself if its
* destination is covered by VPN routes. Otherwise its outgoing packets
* will be sent back to the VPN interface and cause an infinite loop. This
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/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java
index cfe5f27..db71279 100644
--- a/core/java/android/net/http/X509TrustManagerExtensions.java
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -63,4 +63,18 @@ public class X509TrustManagerExtensions {
String host) throws CertificateException {
return mDelegate.checkServerTrusted(chain, authType, host);
}
+
+ /**
+ * Checks whether a CA certificate is added by an user.
+ *
+ * <p>Since {@link X509TrustManager#checkServerTrusted} allows its parameter {@code chain} to
+ * chain up to user-added CA certificates, this method can be used to perform additional
+ * policies for user-added CA certificates.
+ *
+ * @return {@code true} to indicate that the certificate was added by the user, {@code false}
+ * otherwise.
+ */
+ public boolean isUserAddedCertificate(X509Certificate cert) {
+ return mDelegate.isUserAddedCertificate(cert);
+ }
}
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index 7b2c623..d8e8e2c 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/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 8414738..635a50f 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -25,6 +25,7 @@ import android.nfc.IAppCallback;
import android.nfc.INfcAdapterExtras;
import android.nfc.INfcTag;
import android.nfc.INfcCardEmulation;
+import android.nfc.INfcUnlockSettings;
import android.os.Bundle;
/**
@@ -35,6 +36,7 @@ interface INfcAdapter
INfcTag getNfcTagInterface();
INfcCardEmulation getNfcCardEmulationInterface();
INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg);
+ INfcUnlockSettings getNfcUnlockSettingsInterface();
int getState();
boolean disable(boolean saveState);
@@ -46,6 +48,7 @@ interface INfcAdapter
void setForegroundDispatch(in PendingIntent intent,
in IntentFilter[] filters, in TechListParcel techLists);
void setAppCallback(in IAppCallback callback);
+ void invokeBeam();
void dispatch(in Tag tag);
diff --git a/core/java/android/nfc/INfcUnlockSettings.aidl b/core/java/android/nfc/INfcUnlockSettings.aidl
new file mode 100644
index 0000000..649eeed
--- /dev/null
+++ b/core/java/android/nfc/INfcUnlockSettings.aidl
@@ -0,0 +1,70 @@
+/*
+ * 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.nfc;
+
+import android.nfc.Tag;
+import java.util.List;
+
+/**
+ * Interface to NFC unlock functionality.
+ *
+ * @hide
+ */
+interface INfcUnlockSettings {
+
+ /**
+ * Checks the validity of the tag and attempts to unlock the screen.
+ *
+ * @return true if the screen was successfuly unlocked.
+ */
+ boolean tryUnlock(int userId, in Tag tag);
+
+ /**
+ * Registers the given tag as an unlock tag. Subsequent calls to {@code tryUnlock}
+ * with the same {@code tag} should succeed.
+ *
+ * @return true if the tag was successfully registered.
+ */
+ boolean registerTag(int userId, in Tag tag);
+
+ /**
+ * Deregisters the tag with the corresponding timestamp.
+ * Subsequent calls to {@code tryUnlock} with the same tag should fail.
+ *
+ * @return true if the tag was successfully deleted.
+ */
+ boolean deregisterTag(int userId, long timestamp);
+
+ /**
+ * Used for user-visible rendering of registered tags.
+ *
+ * @return a list of the times in millis since epoch when the registered tags were paired.
+ */
+ long[] getTagRegistryTimes(int userId);
+
+ /**
+ * Determines the state of the NFC unlock feature.
+ *
+ * @return true if NFC unlock is enabled.
+ */
+ boolean getNfcUnlockEnabled(int userId);
+
+ /**
+ * Sets the state [ON | OFF] of the NFC unlock feature.
+ */
+ void setNfcUnlockEnabled(int userId, boolean enabled);
+}
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
index 2b58818..83d17ba 100644
--- a/core/java/android/nfc/NdefRecord.java
+++ b/core/java/android/nfc/NdefRecord.java
@@ -20,6 +20,7 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@@ -269,6 +270,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
@@ -473,6 +475,45 @@ public final class NdefRecord implements Parcelable {
}
/**
+ * Create a new NDEF record containing UTF-8 text data.<p>
+ *
+ * The caller can either specify the language code for the provided text,
+ * or otherwise the language code corresponding to the current default
+ * locale will be used.
+ *
+ * Reference specification: NFCForum-TS-RTD_Text_1.0
+ * @param languageCode The languageCode for the record. If locale is empty or null,
+ * the language code of the current default locale will be used.
+ * @param text The text to be encoded in the record. Will be represented in UTF-8 format.
+ * @throws IllegalArgumentException if text is null
+ */
+ public static NdefRecord createTextRecord(String languageCode, String text) {
+ if (text == null) throw new NullPointerException("text is null");
+
+ byte[] textBytes = text.getBytes(StandardCharsets.UTF_8);
+
+ byte[] languageCodeBytes = null;
+ if (languageCode != null && !languageCode.isEmpty()) {
+ languageCodeBytes = languageCode.getBytes(StandardCharsets.US_ASCII);
+ } else {
+ languageCodeBytes = Locale.getDefault().getLanguage().
+ getBytes(StandardCharsets.US_ASCII);
+ }
+ // We only have 6 bits to indicate ISO/IANA language code.
+ if (languageCodeBytes.length >= 64) {
+ throw new IllegalArgumentException("language code is too long, must be <64 bytes.");
+ }
+ ByteBuffer buffer = ByteBuffer.allocate(1 + languageCodeBytes.length + textBytes.length);
+
+ byte status = (byte) (languageCodeBytes.length & 0xFF);
+ buffer.put(status);
+ buffer.put(languageCodeBytes);
+ buffer.put(textBytes);
+
+ return new NdefRecord(TNF_WELL_KNOWN, RTD_TEXT, null, buffer.array());
+ }
+
+ /**
* Construct an NDEF Record from its component fields.<p>
* Recommend to use helpers such as {#createUri} or
* {{@link #createExternal} where possible, since they perform
@@ -774,7 +815,7 @@ public final class NdefRecord implements Parcelable {
throw new FormatException("expected TNF_UNCHANGED in non-leading chunk");
} else if (!inChunk && tnf == NdefRecord.TNF_UNCHANGED) {
throw new FormatException("" +
- "unexpected TNF_UNCHANGED in first chunk or unchunked record");
+ "unexpected TNF_UNCHANGED in first chunk or unchunked record");
}
int typeLength = buffer.get() & 0xFF;
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index 77c0234..8643f2e 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -18,6 +18,7 @@ package android.nfc;
import android.app.Activity;
import android.app.Application;
+import android.content.Intent;
import android.net.Uri;
import android.nfc.NfcAdapter.ReaderCallback;
import android.os.Binder;
@@ -327,6 +328,7 @@ public final class NfcActivityManager extends IAppCallback.Stub
NfcAdapter.CreateNdefMessageCallback ndefCallback;
NfcAdapter.CreateBeamUrisCallback urisCallback;
NdefMessage message;
+ Activity activity;
Uri[] uris;
int flags;
synchronized (NfcActivityManager.this) {
@@ -338,6 +340,7 @@ public final class NfcActivityManager extends IAppCallback.Stub
message = state.ndefMessage;
uris = state.uris;
flags = state.flags;
+ activity = state.activity;
}
// Make callbacks without lock
@@ -362,7 +365,13 @@ public final class NfcActivityManager extends IAppCallback.Stub
}
}
}
-
+ if (uris != null && uris.length > 0) {
+ for (Uri uri : uris) {
+ // Grant the NFC process permission to read these URIs
+ activity.grantUriPermission("com.android.nfc", uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
+ }
return new BeamShareData(message, uris, flags);
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 6743c6c..96a3947 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -292,6 +292,7 @@ public final class NfcAdapter {
static INfcAdapter sService;
static INfcTag sTagService;
static INfcCardEmulation sCardEmulationService;
+ static INfcUnlockSettings sNfcUnlockSettingsService;
/**
* The NfcAdapter object for each application context.
@@ -432,6 +433,13 @@ public final class NfcAdapter {
throw new UnsupportedOperationException();
}
+ try {
+ sNfcUnlockSettingsService = sService.getNfcUnlockSettingsInterface();
+ } catch (RemoteException e) {
+ Log.e(TAG, "could not retrieve NFC unlock settings service");
+ sNfcUnlockSettingsService = null;
+ }
+
sIsInitialized = true;
}
if (context == null) {
@@ -549,6 +557,22 @@ public final class NfcAdapter {
}
/**
+ * Returns the binder interface to the NFC unlock service.
+ *
+ * @throws UnsupportedOperationException if the service is not available.
+ * @hide
+ */
+ public INfcUnlockSettings getNfcUnlockSettingsService() throws UnsupportedOperationException {
+ isEnabled();
+
+ if (sNfcUnlockSettingsService == null) {
+ throw new UnsupportedOperationException("NfcUnlockSettingsService not available");
+ }
+
+ return sNfcUnlockSettingsService;
+ }
+
+ /**
* NFC service dead - attempt best effort recovery
* @hide
*/
@@ -1225,6 +1249,45 @@ public final class NfcAdapter {
}
/**
+ * Manually invoke Android Beam to share data.
+ *
+ * <p>The Android Beam animation is normally only shown when two NFC-capable
+ * devices come into range.
+ * By calling this method, an Activity can invoke the Beam animation directly
+ * even if no other NFC device is in range yet. The Beam animation will then
+ * prompt the user to tap another NFC-capable device to complete the data
+ * transfer.
+ *
+ * <p>The main advantage of using this method is that it avoids the need for the
+ * user to tap the screen to complete the transfer, as this method already
+ * establishes the direction of the transfer and the consent of the user to
+ * share data. Callers are responsible for making sure that the user has
+ * consented to sharing data on NFC tap.
+ *
+ * <p>Note that to use this method, the passed in Activity must have already
+ * set data to share over Beam by using method calls such as
+ * {@link #setNdefPushMessageCallback} or
+ * {@link #setBeamPushUrisCallback}.
+ *
+ * @param activity the current foreground Activity that has registered data to share
+ * @return whether the Beam animation was successfully invoked
+ */
+ public boolean invokeBeam(Activity activity) {
+ if (activity == null) {
+ throw new NullPointerException("activity may not be null.");
+ }
+ enforceResumed(activity);
+ try {
+ sService.invokeBeam();
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "invokeBeam: NFC process has died.");
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
+ }
+
+ /**
* Enable NDEF message push over NFC while this Activity is in the foreground.
*
* <p>You must explicitly call this method every time the activity is
diff --git a/core/java/android/nfc/NfcUnlock.java b/core/java/android/nfc/NfcUnlock.java
new file mode 100644
index 0000000..82dcd96
--- /dev/null
+++ b/core/java/android/nfc/NfcUnlock.java
@@ -0,0 +1,255 @@
+/*
+ * 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.nfc;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * Provides an interface to read and update NFC unlock settings.
+ * <p/>
+ * Allows system services (currently exclusively LockSettingsService) to
+ * register NFC tags to be used to unlock the device, as well as the ability
+ * to enable/disable the service entirely.
+ *
+ */
+public class NfcUnlock {
+
+ /**
+ * Action to unlock the device.
+ *
+ * @hide
+ */
+ public static final String ACTION_NFC_UNLOCK = "android.nfc.ACTION_NFC_UNLOCK";
+ /**
+ * Permission to unlock the device.
+ *
+ * @hide
+ */
+ public static final String NFC_UNLOCK_PERMISSION = "android.permission.NFC_UNLOCK";
+
+ /**
+ * Property to enable NFC Unlock
+ *
+ * @hide
+ */
+ public static final String PROPERTY = "ro.com.android.nfc.unlock";
+
+ private static final String TAG = "NfcUnlock";
+ private static HashMap<Context, NfcUnlock> sNfcUnlocks = new HashMap<Context, NfcUnlock>();
+
+ private final Context mContext;
+ private final boolean mEnabled;
+ private INfcUnlockSettings sService;
+
+ private NfcUnlock(Context context, INfcUnlockSettings service) {
+ this.mContext = checkNotNull(context);
+ this.sService = checkNotNull(service);
+ this.mEnabled = getPropertyEnabled();
+ }
+
+ /**
+ * Returns an instance of {@link NfcUnlock}.
+ */
+ public static synchronized NfcUnlock getInstance(NfcAdapter nfcAdapter) {
+ Context context = nfcAdapter.getContext();
+ if (context == null) {
+ Log.e(TAG, "NfcAdapter context is null");
+ throw new UnsupportedOperationException();
+ }
+
+ NfcUnlock manager = sNfcUnlocks.get(context);
+ if (manager == null) {
+ INfcUnlockSettings service = nfcAdapter.getNfcUnlockSettingsService();
+ manager = new NfcUnlock(context, service);
+ sNfcUnlocks.put(context, manager);
+ }
+
+ return manager;
+ }
+
+ /**
+ * Registers the given {@code tag} as an unlock tag.
+ *
+ * @return true if the tag was successfully registered.
+ * @hide
+ */
+ public boolean registerTag(Tag tag) {
+ enforcePropertyEnabled();
+
+ int currentUser = ActivityManager.getCurrentUser();
+
+ try {
+ return sService.registerTag(currentUser, tag);
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NfcUnlockSettingsService");
+ return false;
+ }
+
+ try {
+ return sService.registerTag(currentUser, tag);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach NfcUnlockSettingsService", ee);
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Deregisters the given {@code tag} as an unlock tag.
+ *
+ * @return true if the tag was successfully deregistered.
+ * @hide
+ */
+ public boolean deregisterTag(long timestamp) {
+ enforcePropertyEnabled();
+ int currentUser = ActivityManager.getCurrentUser();
+
+ try {
+ return sService.deregisterTag(currentUser, timestamp);
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NfcUnlockSettingsService");
+ return false;
+ }
+
+ try {
+ return sService.deregisterTag(currentUser, timestamp);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach NfcUnlockSettingsService", ee);
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Determines the enable state of the NFC unlock feature.
+ *
+ * @return true if NFC unlock is enabled.
+ */
+ public boolean getNfcUnlockEnabled() {
+ enforcePropertyEnabled();
+ int currentUser = ActivityManager.getCurrentUser();
+
+ try {
+ return sService.getNfcUnlockEnabled(currentUser);
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NfcUnlockSettingsService");
+ return false;
+ }
+
+ try {
+ return sService.getNfcUnlockEnabled(currentUser);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach NfcUnlockSettingsService", ee);
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Set the enable state of the NFC unlock feature.
+ *
+ * @return true if the setting was successfully persisted.
+ * @hide
+ */
+ public boolean setNfcUnlockEnabled(boolean enabled) {
+ enforcePropertyEnabled();
+ int currentUser = ActivityManager.getCurrentUser();
+
+ try {
+ sService.setNfcUnlockEnabled(currentUser, enabled);
+ return true;
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NfcUnlockSettingsService");
+ return false;
+ }
+
+ try {
+ sService.setNfcUnlockEnabled(currentUser, enabled);
+ return true;
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach NfcUnlockSettingsService", ee);
+ return false;
+ }
+
+ }
+ }
+
+ /**
+ * Returns a list of times (in millis since epoch) corresponding to when
+ * unlock tags were registered.
+ *
+ * @hide
+ */
+ @Nullable
+ public long[] getTagRegistryTimes() {
+ enforcePropertyEnabled();
+ int currentUser = ActivityManager.getCurrentUser();
+
+ try {
+ return sService.getTagRegistryTimes(currentUser);
+ } catch (RemoteException e) {
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NfcUnlockSettingsService");
+ return null;
+ }
+
+ try {
+ return sService.getTagRegistryTimes(currentUser);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach NfcUnlockSettingsService", ee);
+ return null;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean getPropertyEnabled() {
+ return SystemProperties.get(PROPERTY).equals("ON");
+ }
+
+ private void recoverService() {
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+ sService = adapter.getNfcUnlockSettingsService();
+ }
+
+
+ private void enforcePropertyEnabled() {
+ if (!mEnabled) {
+ throw new UnsupportedOperationException("NFC Unlock property is not enabled");
+ }
+ }
+}
diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java
index f2cd232..0d261d1 100644
--- a/core/java/android/nfc/Tag.java
+++ b/core/java/android/nfc/Tag.java
@@ -204,6 +204,14 @@ public final class Tag implements Parcelable {
}
/**
+ * For use by NfcService only.
+ * @hide
+ */
+ public int[] getTechCodeList() {
+ return mTechList;
+ }
+
+ /**
* Get the Tag Identifier (if it has one).
* <p>The tag identifier is a low level serial number, used for anti-collision
* and identification.
diff --git a/core/java/android/nfc/tech/Ndef.java b/core/java/android/nfc/tech/Ndef.java
index f16dc3b..5852ce4 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/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 26e09b6..4f91d19 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -40,7 +40,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* and does not constitute a generic threading framework. AsyncTasks should ideally be
* used for short operations (a few seconds at the most.) If you need to keep threads
* running for long periods of time, it is highly recommended you use the various APIs
- * provided by the <code>java.util.concurrent</code> pacakge such as {@link Executor},
+ * provided by the <code>java.util.concurrent</code> package such as {@link Executor},
* {@link ThreadPoolExecutor} and {@link FutureTask}.</p>
*
* <p>An asynchronous task is defined by a computation that runs on a background thread and
diff --git a/core/java/android/os/BatteryProperties.java b/core/java/android/os/BatteryProperties.java
index 5df5214..8f5cf8b 100644
--- a/core/java/android/os/BatteryProperties.java
+++ b/core/java/android/os/BatteryProperties.java
@@ -15,9 +15,6 @@
package android.os;
-import android.os.Parcel;
-import android.os.Parcelable;
-
/**
* {@hide}
*/
@@ -30,11 +27,25 @@ 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;
+ public BatteryProperties() {
+ }
+
+ public void set(BatteryProperties other) {
+ chargerAcOnline = other.chargerAcOnline;
+ chargerUsbOnline = other.chargerUsbOnline;
+ chargerWirelessOnline = other.chargerWirelessOnline;
+ batteryStatus = other.batteryStatus;
+ batteryHealth = other.batteryHealth;
+ batteryPresent = other.batteryPresent;
+ batteryLevel = other.batteryLevel;
+ batteryVoltage = other.batteryVoltage;
+ batteryTemperature = other.batteryTemperature;
+ batteryTechnology = other.batteryTechnology;
+ }
+
/*
* Parcel read/write code must be kept in sync with
* frameworks/native/services/batteryservice/BatteryProperties.cpp
@@ -49,8 +60,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 +73,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..7db4ac2 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -24,13 +24,15 @@ import java.util.Formatter;
import java.util.List;
import java.util.Map;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.telephony.SignalStrength;
-import android.util.Log;
+import android.text.format.DateFormat;
import android.util.Printer;
-import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
/**
* A class providing access to battery usage statistics, including information on
@@ -160,6 +162,8 @@ public abstract class BatteryStats implements Parcelable {
private static final String BATTERY_LEVEL_DATA = "lv";
private static final String WIFI_DATA = "wfl";
private static final String MISC_DATA = "m";
+ private static final String GLOBAL_NETWORK_DATA = "gn";
+ private static final String HISTORY_STRING_POOL = "hsp";
private static final String HISTORY_DATA = "h";
private static final String SCREEN_BRIGHTNESS_DATA = "br";
private static final String SIGNAL_STRENGTH_TIME_DATA = "sgt";
@@ -167,6 +171,12 @@ public abstract class BatteryStats implements Parcelable {
private static final String SIGNAL_STRENGTH_COUNT_DATA = "sgc";
private static final String DATA_CONNECTION_TIME_DATA = "dct";
private static final String DATA_CONNECTION_COUNT_DATA = "dcc";
+ private static final String WIFI_STATE_TIME_DATA = "wst";
+ private static final String WIFI_STATE_COUNT_DATA = "wsc";
+ private static final String BLUETOOTH_STATE_TIME_DATA = "bst";
+ private static final String BLUETOOTH_STATE_COUNT_DATA = "bsc";
+ private static final String POWER_USE_SUMMARY_DATA = "pws";
+ private static final String POWER_USE_ITEM_DATA = "pwi";
private final StringBuilder mFormatBuilder = new StringBuilder(32);
private final Formatter mFormatter = new Formatter(mFormatBuilder);
@@ -207,11 +217,11 @@ public abstract class BatteryStats implements Parcelable {
* Returns the total time in microseconds associated with this Timer for the
* selected type of statistics.
*
- * @param batteryRealtime system realtime on battery in microseconds
+ * @param elapsedRealtimeUs current elapsed realtime of system in microseconds
* @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT
* @return a time in microseconds
*/
- public abstract long getTotalTimeLocked(long batteryRealtime, int which);
+ public abstract long getTotalTimeLocked(long elapsedRealtimeUs, int which);
/**
* Temporary for debugging.
@@ -269,30 +279,29 @@ public abstract class BatteryStats implements Parcelable {
*/
public abstract int getUid();
- public abstract void noteWifiRunningLocked();
- public abstract void noteWifiStoppedLocked();
- public abstract void noteFullWifiLockAcquiredLocked();
- public abstract void noteFullWifiLockReleasedLocked();
- public abstract void noteWifiScanStartedLocked();
- public abstract void noteWifiScanStoppedLocked();
- public abstract void noteWifiBatchedScanStartedLocked(int csph);
- public abstract void noteWifiBatchedScanStoppedLocked();
- public abstract void noteWifiMulticastEnabledLocked();
- public abstract void noteWifiMulticastDisabledLocked();
- public abstract void noteAudioTurnedOnLocked();
- public abstract void noteAudioTurnedOffLocked();
- public abstract void noteVideoTurnedOnLocked();
- public abstract void noteVideoTurnedOffLocked();
- public abstract void noteActivityResumedLocked();
- public abstract void noteActivityPausedLocked();
- public abstract long getWifiRunningTime(long batteryRealtime, int which);
- public abstract long getFullWifiLockTime(long batteryRealtime, int which);
- public abstract long getWifiScanTime(long batteryRealtime, int which);
- public abstract long getWifiBatchedScanTime(int csphBin, long batteryRealtime, int which);
- public abstract long getWifiMulticastTime(long batteryRealtime,
- int which);
- public abstract long getAudioTurnedOnTime(long batteryRealtime, int which);
- public abstract long getVideoTurnedOnTime(long batteryRealtime, int which);
+ public abstract void noteWifiRunningLocked(long elapsedRealtime);
+ public abstract void noteWifiStoppedLocked(long elapsedRealtime);
+ public abstract void noteFullWifiLockAcquiredLocked(long elapsedRealtime);
+ public abstract void noteFullWifiLockReleasedLocked(long elapsedRealtime);
+ public abstract void noteWifiScanStartedLocked(long elapsedRealtime);
+ public abstract void noteWifiScanStoppedLocked(long elapsedRealtime);
+ public abstract void noteWifiBatchedScanStartedLocked(int csph, long elapsedRealtime);
+ public abstract void noteWifiBatchedScanStoppedLocked(long elapsedRealtime);
+ public abstract void noteWifiMulticastEnabledLocked(long elapsedRealtime);
+ public abstract void noteWifiMulticastDisabledLocked(long elapsedRealtime);
+ public abstract void noteAudioTurnedOnLocked(long elapsedRealtime);
+ public abstract void noteAudioTurnedOffLocked(long elapsedRealtime);
+ public abstract void noteVideoTurnedOnLocked(long elapsedRealtime);
+ public abstract void noteVideoTurnedOffLocked(long elapsedRealtime);
+ public abstract void noteActivityResumedLocked(long elapsedRealtime);
+ public abstract void noteActivityPausedLocked(long elapsedRealtime);
+ public abstract long getWifiRunningTime(long elapsedRealtimeUs, int which);
+ public abstract long getFullWifiLockTime(long elapsedRealtimeUs, int which);
+ public abstract long getWifiScanTime(long elapsedRealtimeUs, int which);
+ public abstract long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which);
+ public abstract long getWifiMulticastTime(long elapsedRealtimeUs, int which);
+ public abstract long getAudioTurnedOnTime(long elapsedRealtimeUs, int which);
+ public abstract long getVideoTurnedOnTime(long elapsedRealtimeUs, int which);
public abstract Timer getForegroundActivityTimer();
public abstract Timer getVibratorOnTimer();
@@ -314,7 +323,10 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getUserActivityCount(int type, int which);
public abstract boolean hasNetworkActivity();
- public abstract long getNetworkActivityCount(int type, int which);
+ public abstract long getNetworkActivityBytes(int type, int which);
+ public abstract long getNetworkActivityPackets(int type, int which);
+ public abstract long getMobileRadioActiveTime(int which);
+ public abstract int getMobileRadioActiveCount(int which);
public static abstract class Sensor {
/*
@@ -331,8 +343,9 @@ public abstract class BatteryStats implements Parcelable {
}
public class Pid {
- public long mWakeSum;
- public long mWakeStart;
+ public int mWakeNesting;
+ public long mWakeSumMs;
+ public long mWakeStartMs;
}
/**
@@ -350,6 +363,11 @@ public abstract class BatteryStats implements Parcelable {
}
/**
+ * Returns true if this process is still active in the battery stats.
+ */
+ public abstract boolean isActive();
+
+ /**
* Returns the total time (in 1/100 sec) spent executing in user code.
*
* @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
@@ -440,21 +458,76 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ public final static class HistoryTag {
+ public String string;
+ public int uid;
+
+ public int poolIdx;
+
+ public void setTo(HistoryTag o) {
+ string = o.string;
+ uid = o.uid;
+ poolIdx = o.poolIdx;
+ }
+
+ public void setTo(String _string, int _uid) {
+ string = _string;
+ uid = _uid;
+ poolIdx = -1;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(string);
+ dest.writeInt(uid);
+ }
+
+ public void readFromParcel(Parcel src) {
+ string = src.readString();
+ uid = src.readInt();
+ poolIdx = -1;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ HistoryTag that = (HistoryTag) o;
+
+ if (uid != that.uid) return false;
+ if (!string.equals(that.string)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = string.hashCode();
+ result = 31 * result + uid;
+ return result;
+ }
+ }
+
public final static class HistoryItem implements Parcelable {
- static final String TAG = "HistoryItem";
- static final boolean DEBUG = false;
-
public HistoryItem next;
public long time;
-
- public static final byte CMD_NULL = 0;
- public static final byte CMD_UPDATE = 1;
- public static final byte CMD_START = 2;
- public static final byte CMD_OVERFLOW = 3;
-
+
+ public static final byte CMD_UPDATE = 0; // These can be written as deltas
+ public static final byte CMD_NULL = -1;
+ public static final byte CMD_START = 4;
+ public static final byte CMD_CURRENT_TIME = 5;
+ public static final byte CMD_OVERFLOW = 6;
+
public byte cmd = CMD_NULL;
+ /**
+ * Return whether the command code is a delta data update.
+ */
+ public boolean isDeltaData() {
+ return cmd == CMD_UPDATE;
+ }
+
public byte batteryLevel;
public byte batteryStatus;
public byte batteryHealth;
@@ -464,31 +537,32 @@ public abstract class BatteryStats implements Parcelable {
public char batteryVoltage;
// Constants from SCREEN_BRIGHTNESS_*
- public static final int STATE_BRIGHTNESS_MASK = 0x0000000f;
public static final int STATE_BRIGHTNESS_SHIFT = 0;
+ public static final int STATE_BRIGHTNESS_MASK = 0x7;
// Constants from SIGNAL_STRENGTH_*
- public static final int STATE_SIGNAL_STRENGTH_MASK = 0x000000f0;
- public static final int STATE_SIGNAL_STRENGTH_SHIFT = 4;
+ public static final int STATE_SIGNAL_STRENGTH_SHIFT = 3;
+ public static final int STATE_SIGNAL_STRENGTH_MASK = 0x7 << STATE_SIGNAL_STRENGTH_SHIFT;
// Constants from ServiceState.STATE_*
- public static final int STATE_PHONE_STATE_MASK = 0x00000f00;
- public static final int STATE_PHONE_STATE_SHIFT = 8;
+ public static final int STATE_PHONE_STATE_SHIFT = 6;
+ public static final int STATE_PHONE_STATE_MASK = 0x7 << STATE_PHONE_STATE_SHIFT;
// Constants from DATA_CONNECTION_*
- public static final int STATE_DATA_CONNECTION_MASK = 0x0000f000;
- public static final int STATE_DATA_CONNECTION_SHIFT = 12;
-
+ public static final int STATE_DATA_CONNECTION_SHIFT = 9;
+ public static final int STATE_DATA_CONNECTION_MASK = 0x1f << STATE_DATA_CONNECTION_SHIFT;
+
// These states always appear directly in the first int token
// of a delta change; they should be ones that change relatively
// frequently.
- public static final int STATE_WAKE_LOCK_FLAG = 1<<30;
- public static final int STATE_SENSOR_ON_FLAG = 1<<29;
- public static final int STATE_GPS_ON_FLAG = 1<<28;
- public static final int STATE_PHONE_SCANNING_FLAG = 1<<27;
- public static final int STATE_WIFI_RUNNING_FLAG = 1<<26;
- public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<25;
- public static final int STATE_WIFI_SCAN_FLAG = 1<<24;
- public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<23;
+ public static final int STATE_WAKE_LOCK_FLAG = 1<<31;
+ public static final int STATE_SENSOR_ON_FLAG = 1<<30;
+ public static final int STATE_GPS_ON_FLAG = 1<<29;
+ public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<28;
+ public static final int STATE_WIFI_SCAN_FLAG = 1<<27;
+ public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<26;
+ public static final int STATE_MOBILE_RADIO_ACTIVE_FLAG = 1<<25;
+ public static final int STATE_WIFI_RUNNING_FLAG = 1<<24;
// These are on the lower bits used for the command; if they change
// we need to write another int of data.
+ public static final int STATE_PHONE_SCANNING_FLAG = 1<<23;
public static final int STATE_AUDIO_ON_FLAG = 1<<22;
public static final int STATE_VIDEO_ON_FLAG = 1<<21;
public static final int STATE_SCREEN_ON_FLAG = 1<<20;
@@ -503,11 +577,58 @@ public abstract class BatteryStats implements Parcelable {
public int states;
+ // The wake lock that was acquired at this point.
+ public HistoryTag wakelockTag;
+
+ // Kernel wakeup reason at this point.
+ public HistoryTag wakeReasonTag;
+
+ public static final int EVENT_FLAG_START = 0x8000;
+ public static final int EVENT_FLAG_FINISH = 0x4000;
+
+ // No event in this item.
+ public static final int EVENT_NONE = 0x0000;
+ // Event is about a process that is running.
+ public static final int EVENT_PROC = 0x0001;
+ // Event is about an application package that is in the foreground.
+ public static final int EVENT_FOREGROUND = 0x0002;
+ // Event is about an application package that is at the top of the screen.
+ public static final int EVENT_TOP = 0x0003;
+ // Event is about an application package that is at the top of the screen.
+ public static final int EVENT_SYNC = 0x0004;
+ // Number of event types.
+ public static final int EVENT_COUNT = 0x0005;
+
+ public static final int EVENT_PROC_START = EVENT_PROC | EVENT_FLAG_START;
+ public static final int EVENT_PROC_FINISH = EVENT_PROC | EVENT_FLAG_FINISH;
+ public static final int EVENT_FOREGROUND_START = EVENT_FOREGROUND | EVENT_FLAG_START;
+ public static final int EVENT_FOREGROUND_FINISH = EVENT_FOREGROUND | EVENT_FLAG_FINISH;
+ public static final int EVENT_TOP_START = EVENT_TOP | EVENT_FLAG_START;
+ public static final int EVENT_TOP_FINISH = EVENT_TOP | EVENT_FLAG_FINISH;
+ public static final int EVENT_SYNC_START = EVENT_SYNC | EVENT_FLAG_START;
+ public static final int EVENT_SYNC_FINISH = EVENT_SYNC | EVENT_FLAG_FINISH;
+
+ // For CMD_EVENT.
+ public int eventCode;
+ public HistoryTag eventTag;
+
+ // Only set for CMD_CURRENT_TIME.
+ public long currentTime;
+
+ // Meta-data when reading.
+ public int numReadInts;
+
+ // Pre-allocated objects.
+ public final HistoryTag localWakelockTag = new HistoryTag();
+ public final HistoryTag localWakeReasonTag = new HistoryTag();
+ public final HistoryTag localEventTag = new HistoryTag();
+
public HistoryItem() {
}
public HistoryItem(long time, Parcel src) {
this.time = time;
+ numReadInts = 2;
readFromParcel(src);
}
@@ -521,168 +642,68 @@ public abstract class BatteryStats implements Parcelable {
| ((((int)batteryLevel)<<8)&0xff00)
| ((((int)batteryStatus)<<16)&0xf0000)
| ((((int)batteryHealth)<<20)&0xf00000)
- | ((((int)batteryPlugType)<<24)&0xf000000);
+ | ((((int)batteryPlugType)<<24)&0xf000000)
+ | (wakelockTag != null ? 0x10000000 : 0)
+ | (wakeReasonTag != null ? 0x20000000 : 0)
+ | (eventCode != EVENT_NONE ? 0x40000000 : 0);
dest.writeInt(bat);
bat = (((int)batteryTemperature)&0xffff)
| ((((int)batteryVoltage)<<16)&0xffff0000);
dest.writeInt(bat);
dest.writeInt(states);
+ if (wakelockTag != null) {
+ wakelockTag.writeToParcel(dest, flags);
+ }
+ if (wakeReasonTag != null) {
+ wakeReasonTag.writeToParcel(dest, flags);
+ }
+ if (eventCode != EVENT_NONE) {
+ dest.writeInt(eventCode);
+ eventTag.writeToParcel(dest, flags);
+ }
+ if (cmd == CMD_CURRENT_TIME) {
+ dest.writeLong(currentTime);
+ }
}
- private void readFromParcel(Parcel src) {
+ public void readFromParcel(Parcel src) {
+ int start = src.dataPosition();
int bat = src.readInt();
cmd = (byte)(bat&0xff);
batteryLevel = (byte)((bat>>8)&0xff);
batteryStatus = (byte)((bat>>16)&0xf);
batteryHealth = (byte)((bat>>20)&0xf);
batteryPlugType = (byte)((bat>>24)&0xf);
- bat = src.readInt();
- batteryTemperature = (short)(bat&0xffff);
- batteryVoltage = (char)((bat>>16)&0xffff);
+ int bat2 = src.readInt();
+ batteryTemperature = (short)(bat2&0xffff);
+ batteryVoltage = (char)((bat2>>16)&0xffff);
states = src.readInt();
- }
-
- // Part of initial delta int that specifies the time delta.
- static final int DELTA_TIME_MASK = 0x3ffff;
- static final int DELTA_TIME_ABS = 0x3fffd; // Following is an entire abs update.
- static final int DELTA_TIME_INT = 0x3fffe; // The delta is a following int
- static final int DELTA_TIME_LONG = 0x3ffff; // The delta is a following long
- // Part of initial delta int holding the command code.
- static final int DELTA_CMD_MASK = 0x3;
- static final int DELTA_CMD_SHIFT = 18;
- // Flag in delta int: a new battery level int follows.
- static final int DELTA_BATTERY_LEVEL_FLAG = 1<<20;
- // Flag in delta int: a new full state and battery status int follows.
- static final int DELTA_STATE_FLAG = 1<<21;
- static final int DELTA_STATE_MASK = 0xffc00000;
-
- public void writeDelta(Parcel dest, HistoryItem last) {
- if (last == null || last.cmd != CMD_UPDATE) {
- dest.writeInt(DELTA_TIME_ABS);
- writeToParcel(dest, 0);
- return;
- }
-
- final long deltaTime = time - last.time;
- final int lastBatteryLevelInt = last.buildBatteryLevelInt();
- final int lastStateInt = last.buildStateInt();
-
- int deltaTimeToken;
- if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
- deltaTimeToken = DELTA_TIME_LONG;
- } else if (deltaTime >= DELTA_TIME_ABS) {
- deltaTimeToken = DELTA_TIME_INT;
+ if ((bat&0x10000000) != 0) {
+ wakelockTag = localWakelockTag;
+ wakelockTag.readFromParcel(src);
} else {
- deltaTimeToken = (int)deltaTime;
- }
- int firstToken = deltaTimeToken
- | (cmd<<DELTA_CMD_SHIFT)
- | (states&DELTA_STATE_MASK);
- final int batteryLevelInt = buildBatteryLevelInt();
- final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
- if (batteryLevelIntChanged) {
- firstToken |= DELTA_BATTERY_LEVEL_FLAG;
- }
- final int stateInt = buildStateInt();
- final boolean stateIntChanged = stateInt != lastStateInt;
- if (stateIntChanged) {
- firstToken |= DELTA_STATE_FLAG;
- }
- dest.writeInt(firstToken);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
- + " deltaTime=" + deltaTime);
-
- if (deltaTimeToken >= DELTA_TIME_INT) {
- if (deltaTimeToken == DELTA_TIME_INT) {
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int)deltaTime);
- dest.writeInt((int)deltaTime);
- } else {
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
- dest.writeLong(deltaTime);
- }
- }
- if (batteryLevelIntChanged) {
- dest.writeInt(batteryLevelInt);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
- + Integer.toHexString(batteryLevelInt)
- + " batteryLevel=" + batteryLevel
- + " batteryTemp=" + batteryTemperature
- + " batteryVolt=" + (int)batteryVoltage);
+ wakelockTag = null;
}
- if (stateIntChanged) {
- dest.writeInt(stateInt);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: stateToken=0x"
- + Integer.toHexString(stateInt)
- + " batteryStatus=" + batteryStatus
- + " batteryHealth=" + batteryHealth
- + " batteryPlugType=" + batteryPlugType
- + " states=0x" + Integer.toHexString(states));
- }
- }
-
- private int buildBatteryLevelInt() {
- return ((((int)batteryLevel)<<25)&0xfe000000)
- | ((((int)batteryTemperature)<<14)&0x01ffc000)
- | (((int)batteryVoltage)&0x00003fff);
- }
-
- private int buildStateInt() {
- return ((((int)batteryStatus)<<28)&0xf0000000)
- | ((((int)batteryHealth)<<24)&0x0f000000)
- | ((((int)batteryPlugType)<<22)&0x00c00000)
- | (states&(~DELTA_STATE_MASK));
- }
-
- public void readDelta(Parcel src) {
- int firstToken = src.readInt();
- int deltaTimeToken = firstToken&DELTA_TIME_MASK;
- cmd = (byte)((firstToken>>DELTA_CMD_SHIFT)&DELTA_CMD_MASK);
- if (DEBUG) Slog.i(TAG, "READ DELTA: firstToken=0x" + Integer.toHexString(firstToken)
- + " deltaTimeToken=" + deltaTimeToken);
-
- if (deltaTimeToken < DELTA_TIME_ABS) {
- time += deltaTimeToken;
- } else if (deltaTimeToken == DELTA_TIME_ABS) {
- time = src.readLong();
- readFromParcel(src);
- return;
- } else if (deltaTimeToken == DELTA_TIME_INT) {
- int delta = src.readInt();
- time += delta;
- if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + time);
+ if ((bat&0x20000000) != 0) {
+ wakeReasonTag = localWakeReasonTag;
+ wakeReasonTag.readFromParcel(src);
} else {
- long delta = src.readLong();
- if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + time);
- time += delta;
+ wakeReasonTag = null;
}
-
- if ((firstToken&DELTA_BATTERY_LEVEL_FLAG) != 0) {
- int batteryLevelInt = src.readInt();
- batteryLevel = (byte)((batteryLevelInt>>25)&0x7f);
- batteryTemperature = (short)((batteryLevelInt<<7)>>21);
- batteryVoltage = (char)(batteryLevelInt&0x3fff);
- if (DEBUG) Slog.i(TAG, "READ DELTA: batteryToken=0x"
- + Integer.toHexString(batteryLevelInt)
- + " batteryLevel=" + batteryLevel
- + " batteryTemp=" + batteryTemperature
- + " batteryVolt=" + (int)batteryVoltage);
+ if ((bat&0x40000000) != 0) {
+ eventCode = src.readInt();
+ eventTag = localEventTag;
+ eventTag.readFromParcel(src);
+ } else {
+ eventCode = EVENT_NONE;
+ eventTag = null;
}
-
- if ((firstToken&DELTA_STATE_FLAG) != 0) {
- int stateInt = src.readInt();
- states = (firstToken&DELTA_STATE_MASK) | (stateInt&(~DELTA_STATE_MASK));
- batteryStatus = (byte)((stateInt>>28)&0xf);
- batteryHealth = (byte)((stateInt>>24)&0xf);
- batteryPlugType = (byte)((stateInt>>22)&0x3);
- if (DEBUG) Slog.i(TAG, "READ DELTA: stateToken=0x"
- + Integer.toHexString(stateInt)
- + " batteryStatus=" + batteryStatus
- + " batteryHealth=" + batteryHealth
- + " batteryPlugType=" + batteryPlugType
- + " states=0x" + Integer.toHexString(states));
+ if (cmd == CMD_CURRENT_TIME) {
+ currentTime = src.readLong();
} else {
- states = (firstToken&DELTA_STATE_MASK) | (states&(~DELTA_STATE_MASK));
+ currentTime = 0;
}
+ numReadInts += (src.dataPosition()-start)/4;
}
public void clear() {
@@ -695,23 +716,25 @@ public abstract class BatteryStats implements Parcelable {
batteryTemperature = 0;
batteryVoltage = 0;
states = 0;
+ wakelockTag = null;
+ wakeReasonTag = null;
+ eventCode = EVENT_NONE;
+ eventTag = null;
}
public void setTo(HistoryItem o) {
time = o.time;
cmd = o.cmd;
- batteryLevel = o.batteryLevel;
- batteryStatus = o.batteryStatus;
- batteryHealth = o.batteryHealth;
- batteryPlugType = o.batteryPlugType;
- batteryTemperature = o.batteryTemperature;
- batteryVoltage = o.batteryVoltage;
- states = o.states;
+ setToCommon(o);
}
public void setTo(long time, byte cmd, HistoryItem o) {
this.time = time;
this.cmd = cmd;
+ setToCommon(o);
+ }
+
+ private void setToCommon(HistoryItem o) {
batteryLevel = o.batteryLevel;
batteryStatus = o.batteryStatus;
batteryHealth = o.batteryHealth;
@@ -719,16 +742,68 @@ public abstract class BatteryStats implements Parcelable {
batteryTemperature = o.batteryTemperature;
batteryVoltage = o.batteryVoltage;
states = o.states;
+ if (o.wakelockTag != null) {
+ wakelockTag = localWakelockTag;
+ wakelockTag.setTo(o.wakelockTag);
+ } else {
+ wakelockTag = null;
+ }
+ if (o.wakeReasonTag != null) {
+ wakeReasonTag = localWakeReasonTag;
+ wakeReasonTag.setTo(o.wakeReasonTag);
+ } else {
+ wakeReasonTag = null;
+ }
+ eventCode = o.eventCode;
+ if (o.eventTag != null) {
+ eventTag = localEventTag;
+ eventTag.setTo(o.eventTag);
+ } else {
+ eventTag = null;
+ }
+ currentTime = o.currentTime;
}
- public boolean same(HistoryItem o) {
+ public boolean sameNonEvent(HistoryItem o) {
return batteryLevel == o.batteryLevel
&& batteryStatus == o.batteryStatus
&& batteryHealth == o.batteryHealth
&& batteryPlugType == o.batteryPlugType
&& batteryTemperature == o.batteryTemperature
&& batteryVoltage == o.batteryVoltage
- && states == o.states;
+ && states == o.states
+ && currentTime == o.currentTime;
+ }
+
+ public boolean same(HistoryItem o) {
+ if (!sameNonEvent(o) || eventCode != o.eventCode) {
+ return false;
+ }
+ if (wakelockTag != o.wakelockTag) {
+ if (wakelockTag == null || o.wakelockTag == null) {
+ return false;
+ }
+ if (!wakelockTag.equals(o.wakelockTag)) {
+ return false;
+ }
+ }
+ if (wakeReasonTag != o.wakeReasonTag) {
+ if (wakeReasonTag == null || o.wakeReasonTag == null) {
+ return false;
+ }
+ if (!wakeReasonTag.equals(o.wakeReasonTag)) {
+ return false;
+ }
+ }
+ if (eventTag != o.eventTag) {
+ if (eventTag == null || o.eventTag == null) {
+ return false;
+ }
+ if (!eventTag.equals(o.eventTag)) {
+ return false;
+ }
+ }
+ return true;
}
}
@@ -736,25 +811,44 @@ public abstract class BatteryStats implements Parcelable {
public final int mask;
public final int shift;
public final String name;
+ public final String shortName;
public final String[] values;
+ public final String[] shortValues;
- public BitDescription(int mask, String name) {
+ public BitDescription(int mask, String name, String shortName) {
this.mask = mask;
this.shift = -1;
this.name = name;
+ this.shortName = shortName;
this.values = null;
+ this.shortValues = null;
}
- public BitDescription(int mask, int shift, String name, String[] values) {
+ public BitDescription(int mask, int shift, String name, String shortName,
+ String[] values, String[] shortValues) {
this.mask = mask;
this.shift = shift;
this.name = name;
+ this.shortName = shortName;
this.values = values;
+ this.shortValues = shortValues;
}
}
+ public abstract int getHistoryTotalSize();
+
+ public abstract int getHistoryUsedSize();
+
public abstract boolean startIteratingHistoryLocked();
+ public abstract int getHistoryStringPoolSize();
+
+ public abstract int getHistoryStringPoolBytes();
+
+ public abstract String getHistoryTagPoolString(int index);
+
+ public abstract int getHistoryTagPoolUid(int index);
+
public abstract boolean getNextHistoryLocked(HistoryItem out);
public abstract void finishIteratingHistoryLocked();
@@ -781,8 +875,15 @@ public abstract class BatteryStats implements Parcelable {
*
* {@hide}
*/
- public abstract long getScreenOnTime(long batteryRealtime, int which);
+ public abstract long getScreenOnTime(long elapsedRealtimeUs, int which);
+ /**
+ * Returns the number of times the screen was turned on.
+ *
+ * {@hide}
+ */
+ public abstract int getScreenOnCount(int which);
+
public static final int SCREEN_BRIGHTNESS_DARK = 0;
public static final int SCREEN_BRIGHTNESS_DIM = 1;
public static final int SCREEN_BRIGHTNESS_MEDIUM = 2;
@@ -793,6 +894,10 @@ public abstract class BatteryStats implements Parcelable {
"dark", "dim", "medium", "light", "bright"
};
+ static final String[] SCREEN_BRIGHTNESS_SHORT_NAMES = {
+ "0", "1", "2", "3", "4"
+ };
+
public static final int NUM_SCREEN_BRIGHTNESS_BINS = 5;
/**
@@ -802,7 +907,7 @@ public abstract class BatteryStats implements Parcelable {
* {@hide}
*/
public abstract long getScreenBrightnessTime(int brightnessBin,
- long batteryRealtime, int which);
+ long elapsedRealtimeUs, int which);
public abstract int getInputEventCount(int which);
@@ -812,16 +917,23 @@ public abstract class BatteryStats implements Parcelable {
*
* {@hide}
*/
- public abstract long getPhoneOnTime(long batteryRealtime, int which);
+ public abstract long getPhoneOnTime(long elapsedRealtimeUs, int which);
/**
+ * Returns the number of times a phone call was activated.
+ *
+ * {@hide}
+ */
+ public abstract int getPhoneOnCount(int which);
+
+ /**
* Returns the time in microseconds that the phone has been running with
* the given signal strength.
*
* {@hide}
*/
public abstract long getPhoneSignalStrengthTime(int strengthBin,
- long batteryRealtime, int which);
+ long elapsedRealtimeUs, int which);
/**
* Returns the time in microseconds that the phone has been trying to
@@ -830,7 +942,7 @@ public abstract class BatteryStats implements Parcelable {
* {@hide}
*/
public abstract long getPhoneSignalScanningTime(
- long batteryRealtime, int which);
+ long elapsedRealtimeUs, int which);
/**
* Returns the number of times the phone has entered the given signal strength.
@@ -839,6 +951,37 @@ public abstract class BatteryStats implements Parcelable {
*/
public abstract int getPhoneSignalStrengthCount(int strengthBin, int which);
+ /**
+ * Returns the time in microseconds that the mobile network has been active
+ * (in a high power state).
+ *
+ * {@hide}
+ */
+ public abstract long getMobileRadioActiveTime(long elapsedRealtimeUs, int which);
+
+ /**
+ * Returns the number of times that the mobile network has transitioned to the
+ * active state.
+ *
+ * {@hide}
+ */
+ public abstract int getMobileRadioActiveCount(int which);
+
+ /**
+ * Returns the time in microseconds that the mobile network has been active
+ * (in a high power state) but not being able to blame on an app.
+ *
+ * {@hide}
+ */
+ public abstract long getMobileRadioActiveUnknownTime(int which);
+
+ /**
+ * Return count of number of times radio was up that could not be blamed on apps.
+ *
+ * {@hide}
+ */
+ public abstract int getMobileRadioActiveUnknownCount(int which);
+
public static final int DATA_CONNECTION_NONE = 0;
public static final int DATA_CONNECTION_GPRS = 1;
public static final int DATA_CONNECTION_EDGE = 2;
@@ -872,7 +1015,7 @@ public abstract class BatteryStats implements Parcelable {
* {@hide}
*/
public abstract long getPhoneDataConnectionTime(int dataType,
- long batteryRealtime, int which);
+ long elapsedRealtimeUs, int which);
/**
* Returns the number of times the phone has entered the given data
@@ -884,33 +1027,45 @@ public abstract class BatteryStats implements Parcelable {
public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS
= new BitDescription[] {
- new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged"),
- new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen"),
- new BitDescription(HistoryItem.STATE_GPS_ON_FLAG, "gps"),
- new BitDescription(HistoryItem.STATE_PHONE_IN_CALL_FLAG, "phone_in_call"),
- new BitDescription(HistoryItem.STATE_PHONE_SCANNING_FLAG, "phone_scanning"),
- new BitDescription(HistoryItem.STATE_WIFI_ON_FLAG, "wifi"),
- new BitDescription(HistoryItem.STATE_WIFI_RUNNING_FLAG, "wifi_running"),
- new BitDescription(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG, "wifi_full_lock"),
- new BitDescription(HistoryItem.STATE_WIFI_SCAN_FLAG, "wifi_scan"),
- new BitDescription(HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG, "wifi_multicast"),
- new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth"),
- new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio"),
- new BitDescription(HistoryItem.STATE_VIDEO_ON_FLAG, "video"),
- new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock"),
- new BitDescription(HistoryItem.STATE_SENSOR_ON_FLAG, "sensor"),
- new BitDescription(HistoryItem.STATE_BRIGHTNESS_MASK,
- HistoryItem.STATE_BRIGHTNESS_SHIFT, "brightness",
- SCREEN_BRIGHTNESS_NAMES),
- new BitDescription(HistoryItem.STATE_SIGNAL_STRENGTH_MASK,
- HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT, "signal_strength",
- SignalStrength.SIGNAL_STRENGTH_NAMES),
- new BitDescription(HistoryItem.STATE_PHONE_STATE_MASK,
- HistoryItem.STATE_PHONE_STATE_SHIFT, "phone_state",
- new String[] {"in", "out", "emergency", "off"}),
+ new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock", "w"),
+ new BitDescription(HistoryItem.STATE_SENSOR_ON_FLAG, "sensor", "s"),
+ new BitDescription(HistoryItem.STATE_GPS_ON_FLAG, "gps", "g"),
+ new BitDescription(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG, "wifi_full_lock", "Wl"),
+ new BitDescription(HistoryItem.STATE_WIFI_SCAN_FLAG, "wifi_scan", "Ws"),
+ new BitDescription(HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG, "wifi_multicast", "Wm"),
+ new BitDescription(HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG, "mobile_radio", "Pr"),
+ new BitDescription(HistoryItem.STATE_WIFI_RUNNING_FLAG, "wifi_running", "Wr"),
+ new BitDescription(HistoryItem.STATE_PHONE_SCANNING_FLAG, "phone_scanning", "Psc"),
+ new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio", "a"),
+ new BitDescription(HistoryItem.STATE_VIDEO_ON_FLAG, "video", "v"),
+ new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen", "S"),
+ new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged", "BP"),
+ new BitDescription(HistoryItem.STATE_PHONE_IN_CALL_FLAG, "phone_in_call", "Pcl"),
+ new BitDescription(HistoryItem.STATE_WIFI_ON_FLAG, "wifi", "W"),
+ new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth", "b"),
new BitDescription(HistoryItem.STATE_DATA_CONNECTION_MASK,
- HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn",
- DATA_CONNECTION_NAMES),
+ HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn", "Pcn",
+ DATA_CONNECTION_NAMES, DATA_CONNECTION_NAMES),
+ new BitDescription(HistoryItem.STATE_PHONE_STATE_MASK,
+ HistoryItem.STATE_PHONE_STATE_SHIFT, "phone_state", "Pst",
+ new String[] {"in", "out", "emergency", "off"},
+ new String[] {"in", "out", "em", "off"}),
+ new BitDescription(HistoryItem.STATE_SIGNAL_STRENGTH_MASK,
+ HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT, "signal_strength", "Pss",
+ SignalStrength.SIGNAL_STRENGTH_NAMES, new String[] {
+ "0", "1", "2", "3", "4"
+ }),
+ new BitDescription(HistoryItem.STATE_BRIGHTNESS_MASK,
+ HistoryItem.STATE_BRIGHTNESS_SHIFT, "brightness", "Sb",
+ SCREEN_BRIGHTNESS_NAMES, SCREEN_BRIGHTNESS_SHORT_NAMES),
+ };
+
+ public static final String[] HISTORY_EVENT_NAMES = new String[] {
+ "null", "proc", "fg", "top", "sync"
+ };
+
+ public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] {
+ "Enl", "Epr", "Efg", "Etp", "Esy"
};
/**
@@ -919,7 +1074,7 @@ public abstract class BatteryStats implements Parcelable {
*
* {@hide}
*/
- public abstract long getWifiOnTime(long batteryRealtime, int which);
+ public abstract long getWifiOnTime(long elapsedRealtimeUs, int which);
/**
* Returns the time in microseconds that wifi has been on and the driver has
@@ -927,7 +1082,38 @@ public abstract class BatteryStats implements Parcelable {
*
* {@hide}
*/
- public abstract long getGlobalWifiRunningTime(long batteryRealtime, int which);
+ public abstract long getGlobalWifiRunningTime(long elapsedRealtimeUs, int which);
+
+ public static final int WIFI_STATE_OFF = 0;
+ public static final int WIFI_STATE_OFF_SCANNING = 1;
+ public static final int WIFI_STATE_ON_NO_NETWORKS = 2;
+ public static final int WIFI_STATE_ON_DISCONNECTED = 3;
+ public static final int WIFI_STATE_ON_CONNECTED_STA = 4;
+ public static final int WIFI_STATE_ON_CONNECTED_P2P = 5;
+ public static final int WIFI_STATE_ON_CONNECTED_STA_P2P = 6;
+ public static final int WIFI_STATE_SOFT_AP = 7;
+
+ static final String[] WIFI_STATE_NAMES = {
+ "off", "scanning", "no_net", "disconn",
+ "sta", "p2p", "sta_p2p", "soft_ap"
+ };
+
+ public static final int NUM_WIFI_STATES = WIFI_STATE_SOFT_AP+1;
+
+ /**
+ * Returns the time in microseconds that WiFi has been running in the given state.
+ *
+ * {@hide}
+ */
+ public abstract long getWifiStateTime(int wifiState,
+ long elapsedRealtimeUs, int which);
+
+ /**
+ * Returns the number of times that WiFi has entered the given state.
+ *
+ * {@hide}
+ */
+ public abstract int getWifiStateCount(int wifiState, int which);
/**
* Returns the time in microseconds that bluetooth has been on while the device was
@@ -935,16 +1121,51 @@ public abstract class BatteryStats implements Parcelable {
*
* {@hide}
*/
- public abstract long getBluetoothOnTime(long batteryRealtime, int which);
+ public abstract long getBluetoothOnTime(long elapsedRealtimeUs, int which);
- public static final int NETWORK_MOBILE_RX_BYTES = 0;
- public static final int NETWORK_MOBILE_TX_BYTES = 1;
- public static final int NETWORK_WIFI_RX_BYTES = 2;
- public static final int NETWORK_WIFI_TX_BYTES = 3;
+ public abstract int getBluetoothPingCount();
+
+ public static final int BLUETOOTH_STATE_INACTIVE = 0;
+ public static final int BLUETOOTH_STATE_LOW = 1;
+ public static final int BLUETOOTH_STATE_MEDIUM = 2;
+ public static final int BLUETOOTH_STATE_HIGH = 3;
+
+ static final String[] BLUETOOTH_STATE_NAMES = {
+ "inactive", "low", "med", "high"
+ };
- public static final int NUM_NETWORK_ACTIVITY_TYPES = NETWORK_WIFI_TX_BYTES + 1;
+ public static final int NUM_BLUETOOTH_STATES = BLUETOOTH_STATE_HIGH +1;
- public abstract long getNetworkActivityCount(int type, int which);
+ /**
+ * Returns the time in microseconds that Bluetooth has been running in the
+ * given active state.
+ *
+ * {@hide}
+ */
+ public abstract long getBluetoothStateTime(int bluetoothState,
+ long elapsedRealtimeUs, int which);
+
+ /**
+ * Returns the number of times that Bluetooth has entered the given active state.
+ *
+ * {@hide}
+ */
+ public abstract int getBluetoothStateCount(int bluetoothState, int which);
+
+ public static final int NETWORK_MOBILE_RX_DATA = 0;
+ public static final int NETWORK_MOBILE_TX_DATA = 1;
+ public static final int NETWORK_WIFI_RX_DATA = 2;
+ public static final int NETWORK_WIFI_TX_DATA = 3;
+
+ public static final int NUM_NETWORK_ACTIVITY_TYPES = NETWORK_WIFI_TX_DATA + 1;
+
+ public abstract long getNetworkActivityBytes(int type, int which);
+ public abstract long getNetworkActivityPackets(int type, int which);
+
+ /**
+ * Return the wall clock time when battery stats data collection started.
+ */
+ public abstract long getStartClockTime();
/**
* Return whether we are currently running on battery.
@@ -964,19 +1185,6 @@ public abstract class BatteryStats implements Parcelable {
public abstract long getBatteryUptime(long curTime);
/**
- * @deprecated use getRadioDataUptime
- */
- public long getRadioDataUptimeMs() {
- return getRadioDataUptime() / 1000;
- }
-
- /**
- * Returns the time that the radio was on for data transfers.
- * @return the uptime in microseconds while unplugged
- */
- public abstract long getRadioDataUptime();
-
- /**
* Returns the current battery realtime in microseconds.
*
* @param curTime the amount of elapsed realtime in microseconds.
@@ -1048,6 +1256,22 @@ public abstract class BatteryStats implements Parcelable {
public abstract long computeBatteryRealtime(long curTime, int which);
/**
+ * Returns the total, last, or current battery screen off uptime in microseconds.
+ *
+ * @param curTime the elapsed realtime in microseconds.
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ */
+ public abstract long computeBatteryScreenOffUptime(long curTime, int which);
+
+ /**
+ * Returns the total, last, or current battery screen off realtime in microseconds.
+ *
+ * @param curTime the current elapsed realtime in microseconds.
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
+ */
+ public abstract long computeBatteryScreenOffRealtime(long curTime, int which);
+
+ /**
* Returns the total, last, or current uptime in microseconds.
*
* @param curTime the current elapsed realtime in microseconds.
@@ -1068,6 +1292,8 @@ public abstract class BatteryStats implements Parcelable {
/** Returns the number of different speeds that the CPU can run at */
public abstract int getCpuSpeedSteps();
+ public abstract void writeToParcelWithoutUids(Parcel out, int flags);
+
private final static void formatTimeRaw(StringBuilder out, long seconds) {
long days = seconds / (60 * 60 * 24);
if (days != 0) {
@@ -1096,23 +1322,30 @@ public abstract class BatteryStats implements Parcelable {
}
}
- private final static void formatTime(StringBuilder sb, long time) {
+ public final static void formatTime(StringBuilder sb, long time) {
long sec = time / 100;
formatTimeRaw(sb, sec);
sb.append((time - (sec * 100)) * 10);
sb.append("ms ");
}
- private final static void formatTimeMs(StringBuilder sb, long time) {
+ public final static void formatTimeMs(StringBuilder sb, long time) {
long sec = time / 1000;
formatTimeRaw(sb, sec);
sb.append(time - (sec * 1000));
sb.append("ms ");
}
- private final String formatRatioLocked(long num, long den) {
+ public final static void formatTimeMsNoSpace(StringBuilder sb, long time) {
+ long sec = time / 1000;
+ formatTimeRaw(sb, sec);
+ sb.append(time - (sec * 1000));
+ sb.append("ms");
+ }
+
+ public final String formatRatioLocked(long num, long den) {
if (den == 0L) {
- return "---%";
+ return "--%";
}
float perc = ((float)num) / ((float)den) * 100;
mFormatBuilder.setLength(0);
@@ -1120,7 +1353,7 @@ public abstract class BatteryStats implements Parcelable {
return mFormatBuilder.toString();
}
- private final String formatBytesLocked(long bytes) {
+ final String formatBytesLocked(long bytes) {
mFormatBuilder.setLength(0);
if (bytes < BYTES_PER_KB) {
@@ -1137,10 +1370,10 @@ public abstract class BatteryStats implements Parcelable {
}
}
- private static long computeWakeLock(Timer timer, long batteryRealtime, int which) {
+ private static long computeWakeLock(Timer timer, long elapsedRealtimeUs, int which) {
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
- long totalTimeMicros = timer.getTotalTimeLocked(batteryRealtime, which);
+ long totalTimeMicros = timer.getTotalTimeLocked(elapsedRealtimeUs, which);
long totalTimeMillis = (totalTimeMicros + 500) / 1000;
return totalTimeMillis;
}
@@ -1151,17 +1384,17 @@ public abstract class BatteryStats implements Parcelable {
*
* @param sb a StringBuilder object.
* @param timer a Timer object contining the wakelock times.
- * @param batteryRealtime the current on-battery time in microseconds.
+ * @param elapsedRealtimeUs the current on-battery time in microseconds.
* @param name the name of the wakelock.
* @param which which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
* @param linePrefix a String to be prepended to each line of output.
* @return the line prefix
*/
private static final String printWakeLock(StringBuilder sb, Timer timer,
- long batteryRealtime, String name, int which, String linePrefix) {
+ long elapsedRealtimeUs, String name, int which, String linePrefix) {
if (timer != null) {
- long totalTimeMillis = computeWakeLock(timer, batteryRealtime, which);
+ long totalTimeMillis = computeWakeLock(timer, elapsedRealtimeUs, which);
int count = timer.getCountLocked(which);
if (totalTimeMillis != 0) {
@@ -1185,18 +1418,18 @@ public abstract class BatteryStats implements Parcelable {
*
* @param sb a StringBuilder object.
* @param timer a Timer object contining the wakelock times.
- * @param now the current time in microseconds.
+ * @param elapsedRealtimeUs the current time in microseconds.
* @param name the name of the wakelock.
* @param which which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
* @param linePrefix a String to be prepended to each line of output.
* @return the line prefix
*/
- private static final String printWakeLockCheckin(StringBuilder sb, Timer timer, long now,
- String name, int which, String linePrefix) {
+ private static final String printWakeLockCheckin(StringBuilder sb, Timer timer,
+ long elapsedRealtimeUs, String name, int which, String linePrefix) {
long totalTimeMicros = 0;
int count = 0;
if (timer != null) {
- totalTimeMicros = timer.getTotalTimeLocked(now, which);
+ totalTimeMicros = timer.getTotalTimeLocked(elapsedRealtimeUs, which);
count = timer.getCountLocked(which);
}
sb.append(linePrefix);
@@ -1234,20 +1467,22 @@ public abstract class BatteryStats implements Parcelable {
*
* NOTE: all times are expressed in 'ms'.
*/
- public final void dumpCheckinLocked(PrintWriter pw, int which, int reqUid) {
+ public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid) {
final long rawUptime = SystemClock.uptimeMillis() * 1000;
final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
final long batteryUptime = getBatteryUptime(rawUptime);
- final long batteryRealtime = getBatteryRealtime(rawRealtime);
final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
+ final long whichBatteryScreenOffUptime = computeBatteryScreenOffUptime(rawUptime, which);
+ final long whichBatteryScreenOffRealtime = computeBatteryScreenOffRealtime(rawRealtime,
+ which);
final long totalRealtime = computeRealtime(rawRealtime, which);
final long totalUptime = computeUptime(rawUptime, which);
- final long screenOnTime = getScreenOnTime(batteryRealtime, which);
- final long phoneOnTime = getPhoneOnTime(batteryRealtime, which);
- final long wifiOnTime = getWifiOnTime(batteryRealtime, which);
- final long wifiRunningTime = getGlobalWifiRunningTime(batteryRealtime, which);
- final long bluetoothOnTime = getBluetoothOnTime(batteryRealtime, which);
+ final long screenOnTime = getScreenOnTime(rawRealtime, which);
+ final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
+ final long wifiOnTime = getWifiOnTime(rawRealtime, which);
+ final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which);
+ final long bluetoothOnTime = getBluetoothOnTime(rawRealtime, which);
StringBuilder sb = new StringBuilder(128);
@@ -1260,22 +1495,16 @@ public abstract class BatteryStats implements Parcelable {
dumpLine(pw, 0 /* uid */, category, BATTERY_DATA,
which == STATS_SINCE_CHARGED ? getStartCount() : "N/A",
whichBatteryRealtime / 1000, whichBatteryUptime / 1000,
- totalRealtime / 1000, totalUptime / 1000);
+ totalRealtime / 1000, totalUptime / 1000,
+ getStartClockTime(),
+ whichBatteryScreenOffRealtime / 1000, whichBatteryScreenOffUptime / 1000);
- // Calculate total network and wakelock times across all uids.
- long mobileRxTotal = 0;
- long mobileTxTotal = 0;
- long wifiRxTotal = 0;
- long wifiTxTotal = 0;
+ // Calculate wakelock times across all uids.
long fullWakeLockTimeTotal = 0;
long partialWakeLockTimeTotal = 0;
for (int iu = 0; iu < NU; iu++) {
Uid u = uidStats.valueAt(iu);
- mobileRxTotal += u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, which);
- mobileTxTotal += u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, which);
- wifiRxTotal += u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, which);
- wifiTxTotal += u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, which);
Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats();
if (wakelocks.size() > 0) {
@@ -1285,59 +1514,96 @@ public abstract class BatteryStats implements Parcelable {
Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL);
if (fullWakeTimer != null) {
- fullWakeLockTimeTotal += fullWakeTimer.getTotalTimeLocked(batteryRealtime, which);
+ fullWakeLockTimeTotal += fullWakeTimer.getTotalTimeLocked(rawRealtime,
+ which);
}
Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
if (partialWakeTimer != null) {
partialWakeLockTimeTotal += partialWakeTimer.getTotalTimeLocked(
- batteryRealtime, which);
+ rawRealtime, which);
}
}
}
}
+ long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which);
+ long mobileTxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which);
+ long wifiRxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which);
+ long wifiTxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which);
+ long mobileRxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which);
+ long mobileTxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which);
+ long wifiRxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which);
+ long wifiTxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which);
+
+ // Dump network stats
+ dumpLine(pw, 0 /* uid */, category, GLOBAL_NETWORK_DATA,
+ mobileRxTotalBytes, mobileTxTotalBytes, wifiRxTotalBytes, wifiTxTotalBytes,
+ mobileRxTotalPackets, mobileTxTotalPackets, wifiRxTotalPackets, wifiTxTotalPackets);
+
// Dump misc stats
dumpLine(pw, 0 /* uid */, category, MISC_DATA,
screenOnTime / 1000, phoneOnTime / 1000, wifiOnTime / 1000,
wifiRunningTime / 1000, bluetoothOnTime / 1000,
- mobileRxTotal, mobileTxTotal, wifiRxTotal, wifiTxTotal,
+ mobileRxTotalBytes, mobileTxTotalBytes, wifiRxTotalBytes, wifiTxTotalBytes,
fullWakeLockTimeTotal, partialWakeLockTimeTotal,
- getInputEventCount(which));
+ getInputEventCount(which), getMobileRadioActiveTime(rawRealtime, which));
// Dump screen brightness stats
Object[] args = new Object[NUM_SCREEN_BRIGHTNESS_BINS];
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- args[i] = getScreenBrightnessTime(i, batteryRealtime, which) / 1000;
+ args[i] = getScreenBrightnessTime(i, rawRealtime, which) / 1000;
}
dumpLine(pw, 0 /* uid */, category, SCREEN_BRIGHTNESS_DATA, args);
// Dump signal strength stats
args = new Object[SignalStrength.NUM_SIGNAL_STRENGTH_BINS];
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
- args[i] = getPhoneSignalStrengthTime(i, batteryRealtime, which) / 1000;
+ args[i] = getPhoneSignalStrengthTime(i, rawRealtime, which) / 1000;
}
dumpLine(pw, 0 /* uid */, category, SIGNAL_STRENGTH_TIME_DATA, args);
dumpLine(pw, 0 /* uid */, category, SIGNAL_SCANNING_TIME_DATA,
- getPhoneSignalScanningTime(batteryRealtime, which) / 1000);
+ getPhoneSignalScanningTime(rawRealtime, which) / 1000);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
args[i] = getPhoneSignalStrengthCount(i, which);
}
dumpLine(pw, 0 /* uid */, category, SIGNAL_STRENGTH_COUNT_DATA, args);
-
+
// Dump network type stats
args = new Object[NUM_DATA_CONNECTION_TYPES];
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- args[i] = getPhoneDataConnectionTime(i, batteryRealtime, which) / 1000;
+ args[i] = getPhoneDataConnectionTime(i, rawRealtime, which) / 1000;
}
dumpLine(pw, 0 /* uid */, category, DATA_CONNECTION_TIME_DATA, args);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
args[i] = getPhoneDataConnectionCount(i, which);
}
dumpLine(pw, 0 /* uid */, category, DATA_CONNECTION_COUNT_DATA, args);
-
+
+ // Dump wifi state stats
+ args = new Object[NUM_WIFI_STATES];
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ args[i] = getWifiStateTime(i, rawRealtime, which) / 1000;
+ }
+ dumpLine(pw, 0 /* uid */, category, WIFI_STATE_TIME_DATA, args);
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ args[i] = getWifiStateCount(i, which);
+ }
+ dumpLine(pw, 0 /* uid */, category, WIFI_STATE_COUNT_DATA, args);
+
+ // Dump bluetooth state stats
+ args = new Object[NUM_BLUETOOTH_STATES];
+ for (int i=0; i<NUM_BLUETOOTH_STATES; i++) {
+ args[i] = getBluetoothStateTime(i, rawRealtime, which) / 1000;
+ }
+ dumpLine(pw, 0 /* uid */, category, BLUETOOTH_STATE_TIME_DATA, args);
+ for (int i=0; i<NUM_BLUETOOTH_STATES; i++) {
+ args[i] = getBluetoothStateCount(i, which);
+ }
+ dumpLine(pw, 0 /* uid */, category, BLUETOOTH_STATE_COUNT_DATA, args);
+
if (which == STATS_SINCE_UNPLUGGED) {
- dumpLine(pw, 0 /* uid */, category, BATTERY_LEVEL_DATA, getDischargeStartLevel(),
+ dumpLine(pw, 0 /* uid */, category, BATTERY_LEVEL_DATA, getDischargeStartLevel(),
getDischargeCurrentLevel());
}
@@ -1357,7 +1623,7 @@ public abstract class BatteryStats implements Parcelable {
if (kernelWakelocks.size() > 0) {
for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) {
sb.setLength(0);
- printWakeLockCheckin(sb, ent.getValue(), batteryRealtime, null, which, "");
+ printWakeLockCheckin(sb, ent.getValue(), rawRealtime, null, which, "");
dumpLine(pw, 0 /* uid */, category, KERNEL_WAKELOCK_DATA, ent.getKey(),
sb.toString());
@@ -1365,6 +1631,61 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ BatteryStatsHelper helper = new BatteryStatsHelper(context);
+ helper.create(this);
+ helper.refreshStats(which, UserHandle.USER_ALL);
+ List<BatterySipper> sippers = helper.getUsageList();
+ if (sippers != null && sippers.size() > 0) {
+ dumpLine(pw, 0 /* uid */, category, POWER_USE_SUMMARY_DATA,
+ BatteryStatsHelper.makemAh(helper.getPowerProfile().getBatteryCapacity()),
+ BatteryStatsHelper.makemAh(helper.getComputedPower()),
+ BatteryStatsHelper.makemAh(helper.getMinDrainedPower()),
+ BatteryStatsHelper.makemAh(helper.getMaxDrainedPower()));
+ for (int i=0; i<sippers.size(); i++) {
+ BatterySipper bs = sippers.get(i);
+ int uid = 0;
+ String label;
+ switch (bs.drainType) {
+ case IDLE:
+ label="idle";
+ break;
+ case CELL:
+ label="cell";
+ break;
+ case PHONE:
+ label="phone";
+ break;
+ case WIFI:
+ label="wifi";
+ break;
+ case BLUETOOTH:
+ label="blue";
+ break;
+ case SCREEN:
+ label="scrn";
+ break;
+ case APP:
+ uid = bs.uidObj.getUid();
+ label = "uid";
+ break;
+ case USER:
+ uid = UserHandle.getUid(bs.userId, 0);
+ label = "user";
+ break;
+ case UNACCOUNTED:
+ label = "unacc";
+ break;
+ case OVERCOUNTED:
+ label = "over";
+ break;
+ default:
+ label = "???";
+ }
+ dumpLine(pw, uid, category, POWER_USE_ITEM_DATA, label,
+ BatteryStatsHelper.makemAh(bs.value));
+ }
+ }
+
for (int iu = 0; iu < NU; iu++) {
final int uid = uidStats.keyAt(iu);
if (reqUid >= 0 && uid != reqUid) {
@@ -1372,16 +1693,28 @@ public abstract class BatteryStats implements Parcelable {
}
Uid u = uidStats.valueAt(iu);
// Dump Network stats per uid, if any
- long mobileRx = u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, which);
- long mobileTx = u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, which);
- long wifiRx = u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, which);
- long wifiTx = u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, which);
- long fullWifiLockOnTime = u.getFullWifiLockTime(batteryRealtime, which);
- long wifiScanTime = u.getWifiScanTime(batteryRealtime, which);
- long uidWifiRunningTime = u.getWifiRunningTime(batteryRealtime, which);
-
- if (mobileRx > 0 || mobileTx > 0 || wifiRx > 0 || wifiTx > 0) {
- dumpLine(pw, uid, category, NETWORK_DATA, mobileRx, mobileTx, wifiRx, wifiTx);
+ long mobileBytesRx = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which);
+ long mobileBytesTx = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which);
+ long wifiBytesRx = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which);
+ long wifiBytesTx = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which);
+ long mobilePacketsRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which);
+ long mobilePacketsTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which);
+ long mobileActiveTime = u.getMobileRadioActiveTime(which);
+ int mobileActiveCount = u.getMobileRadioActiveCount(which);
+ long wifiPacketsRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which);
+ long wifiPacketsTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which);
+ long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
+ long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
+ long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
+
+ if (mobileBytesRx > 0 || mobileBytesTx > 0 || wifiBytesRx > 0 || wifiBytesTx > 0
+ || mobilePacketsRx > 0 || mobilePacketsTx > 0 || wifiPacketsRx > 0
+ || wifiPacketsTx > 0 || mobileActiveTime > 0 || mobileActiveCount > 0) {
+ dumpLine(pw, uid, category, NETWORK_DATA, mobileBytesRx, mobileBytesTx,
+ wifiBytesRx, wifiBytesTx,
+ mobilePacketsRx, mobilePacketsTx,
+ wifiPacketsRx, wifiPacketsTx,
+ mobileActiveTime, mobileActiveCount);
}
if (fullWifiLockOnTime != 0 || wifiScanTime != 0
@@ -1411,11 +1744,11 @@ public abstract class BatteryStats implements Parcelable {
String linePrefix = "";
sb.setLength(0);
linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_FULL),
- batteryRealtime, "f", which, linePrefix);
+ rawRealtime, "f", which, linePrefix);
linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL),
- batteryRealtime, "p", which, linePrefix);
+ rawRealtime, "p", which, linePrefix);
linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_WINDOW),
- batteryRealtime, "w", which, linePrefix);
+ rawRealtime, "w", which, linePrefix);
// Only log if we had at lease one wakelock...
if (sb.length() > 0) {
@@ -1437,7 +1770,7 @@ public abstract class BatteryStats implements Parcelable {
Timer timer = se.getSensorTime();
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
- long totalTime = (timer.getTotalTimeLocked(batteryRealtime, which) + 500) / 1000;
+ long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
int count = timer.getCountLocked(which);
if (totalTime != 0) {
dumpLine(pw, uid, category, SENSOR_DATA, sensorNumber, totalTime, count);
@@ -1449,7 +1782,7 @@ public abstract class BatteryStats implements Parcelable {
Timer vibTimer = u.getVibratorOnTimer();
if (vibTimer != null) {
// Convert from microseconds to milliseconds with rounding
- long totalTime = (vibTimer.getTotalTimeLocked(batteryRealtime, which) + 500) / 1000;
+ long totalTime = (vibTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
int count = vibTimer.getCountLocked(which);
if (totalTime != 0) {
dumpLine(pw, uid, category, VIBRATOR_DATA, totalTime, count);
@@ -1459,7 +1792,7 @@ public abstract class BatteryStats implements Parcelable {
Timer fgTimer = u.getForegroundActivityTimer();
if (fgTimer != null) {
// Convert from microseconds to milliseconds with rounding
- long totalTime = (fgTimer.getTotalTimeLocked(batteryRealtime, which) + 500) / 1000;
+ long totalTime = (fgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
int count = fgTimer.getCountLocked(which);
if (totalTime != 0) {
dumpLine(pw, uid, category, FOREGROUND_DATA, totalTime, count);
@@ -1527,18 +1860,25 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ private void printmAh(PrintWriter printer, double power) {
+ printer.print(BatteryStatsHelper.makemAh(power));
+ }
+
@SuppressWarnings("unused")
- public final void dumpLocked(PrintWriter pw, String prefix, final int which, int reqUid) {
+ public final void dumpLocked(Context context, PrintWriter pw, String prefix, final int which,
+ int reqUid) {
final long rawUptime = SystemClock.uptimeMillis() * 1000;
final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
final long batteryUptime = getBatteryUptime(rawUptime);
- final long batteryRealtime = getBatteryRealtime(rawRealtime);
final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
final long totalRealtime = computeRealtime(rawRealtime, which);
final long totalUptime = computeUptime(rawUptime, which);
-
+ final long whichBatteryScreenOffUptime = computeBatteryScreenOffUptime(rawUptime, which);
+ final long whichBatteryScreenOffRealtime = computeBatteryScreenOffRealtime(rawRealtime,
+ which);
+
StringBuilder sb = new StringBuilder(128);
SparseArray<? extends Uid> uidStats = getUidStats();
@@ -1556,37 +1896,56 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
+ sb.append(" Time on battery screen off: ");
+ formatTimeMs(sb, whichBatteryScreenOffRealtime / 1000); sb.append("(");
+ sb.append(formatRatioLocked(whichBatteryScreenOffRealtime, totalRealtime));
+ sb.append(") realtime, ");
+ formatTimeMs(sb, whichBatteryScreenOffUptime / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(whichBatteryScreenOffUptime, totalRealtime));
+ sb.append(") uptime");
+ pw.println(sb.toString());
+ sb.setLength(0);
+ sb.append(prefix);
sb.append(" Total run time: ");
formatTimeMs(sb, totalRealtime / 1000);
sb.append("realtime, ");
formatTimeMs(sb, totalUptime / 1000);
- sb.append("uptime, ");
+ sb.append("uptime");
pw.println(sb.toString());
-
- final long screenOnTime = getScreenOnTime(batteryRealtime, which);
- final long phoneOnTime = getPhoneOnTime(batteryRealtime, which);
- final long wifiRunningTime = getGlobalWifiRunningTime(batteryRealtime, which);
- final long wifiOnTime = getWifiOnTime(batteryRealtime, which);
- final long bluetoothOnTime = getBluetoothOnTime(batteryRealtime, which);
+ pw.print(" Start clock time: ");
+ pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", getStartClockTime()).toString());
+
+ final long screenOnTime = getScreenOnTime(rawRealtime, which);
+ final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
+ final long wifiRunningTime = getGlobalWifiRunningTime(rawRealtime, which);
+ final long wifiOnTime = getWifiOnTime(rawRealtime, which);
+ final long bluetoothOnTime = getBluetoothOnTime(rawRealtime, which);
sb.setLength(0);
sb.append(prefix);
sb.append(" Screen on: "); formatTimeMs(sb, screenOnTime / 1000);
sb.append("("); sb.append(formatRatioLocked(screenOnTime, whichBatteryRealtime));
- sb.append("), Input events: "); sb.append(getInputEventCount(which));
- sb.append(", Active phone call: "); formatTimeMs(sb, phoneOnTime / 1000);
- sb.append("("); sb.append(formatRatioLocked(phoneOnTime, whichBatteryRealtime));
- sb.append(")");
+ sb.append(") "); sb.append(getScreenOnCount(which));
+ sb.append("x, Input events: "); sb.append(getInputEventCount(which));
pw.println(sb.toString());
+ if (phoneOnTime != 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Active phone call: "); formatTimeMs(sb, phoneOnTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(phoneOnTime, whichBatteryRealtime));
+ sb.append(") "); sb.append(getPhoneOnCount(which));
+ }
sb.setLength(0);
sb.append(prefix);
- sb.append(" Screen brightnesses: ");
+ sb.append(" Screen brightnesses:");
boolean didOne = false;
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- final long time = getScreenBrightnessTime(i, batteryRealtime, which);
+ final long time = getScreenBrightnessTime(i, rawRealtime, which);
if (time == 0) {
continue;
}
- if (didOne) sb.append(", ");
+ sb.append("\n ");
+ sb.append(prefix);
didOne = true;
sb.append(SCREEN_BRIGHTNESS_NAMES[i]);
sb.append(" ");
@@ -1595,70 +1954,17 @@ public abstract class BatteryStats implements Parcelable {
sb.append(formatRatioLocked(time, screenOnTime));
sb.append(")");
}
- if (!didOne) sb.append("No activity");
+ if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
- // Calculate total network and wakelock times across all uids.
- long mobileRxTotal = 0;
- long mobileTxTotal = 0;
- long wifiRxTotal = 0;
- long wifiTxTotal = 0;
+ // Calculate wakelock times across all uids.
long fullWakeLockTimeTotalMicros = 0;
long partialWakeLockTimeTotalMicros = 0;
- final Comparator<TimerEntry> timerComparator = new Comparator<TimerEntry>() {
- @Override
- public int compare(TimerEntry lhs, TimerEntry rhs) {
- long lhsTime = lhs.mTime;
- long rhsTime = rhs.mTime;
- if (lhsTime < rhsTime) {
- return 1;
- }
- if (lhsTime > rhsTime) {
- return -1;
- }
- return 0;
- }
- };
-
- if (reqUid < 0) {
- Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats();
- if (kernelWakelocks.size() > 0) {
- final ArrayList<TimerEntry> timers = new ArrayList<TimerEntry>();
- for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) {
- BatteryStats.Timer timer = ent.getValue();
- long totalTimeMillis = computeWakeLock(timer, batteryRealtime, which);
- if (totalTimeMillis > 0) {
- timers.add(new TimerEntry(ent.getKey(), 0, timer, totalTimeMillis));
- }
- }
- Collections.sort(timers, timerComparator);
- for (int i=0; i<timers.size(); i++) {
- TimerEntry timer = timers.get(i);
- String linePrefix = ": ";
- sb.setLength(0);
- sb.append(prefix);
- sb.append(" Kernel Wake lock ");
- sb.append(timer.mName);
- linePrefix = printWakeLock(sb, timer.mTimer, batteryRealtime, null,
- which, linePrefix);
- if (!linePrefix.equals(": ")) {
- sb.append(" realtime");
- // Only print out wake locks that were held
- pw.println(sb.toString());
- }
- }
- }
- }
-
final ArrayList<TimerEntry> timers = new ArrayList<TimerEntry>();
for (int iu = 0; iu < NU; iu++) {
Uid u = uidStats.valueAt(iu);
- mobileRxTotal += u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, which);
- mobileTxTotal += u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, which);
- wifiRxTotal += u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, which);
- wifiTxTotal += u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, which);
Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats();
if (wakelocks.size() > 0) {
@@ -1669,13 +1975,13 @@ public abstract class BatteryStats implements Parcelable {
Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL);
if (fullWakeTimer != null) {
fullWakeLockTimeTotalMicros += fullWakeTimer.getTotalTimeLocked(
- batteryRealtime, which);
+ rawRealtime, which);
}
Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
if (partialWakeTimer != null) {
long totalTimeMicros = partialWakeTimer.getTotalTimeLocked(
- batteryRealtime, which);
+ rawRealtime, which);
if (totalTimeMicros > 0) {
if (reqUid < 0) {
// Only show the ordered list of all wake
@@ -1691,30 +1997,47 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which);
+ long mobileTxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which);
+ long wifiRxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which);
+ long wifiTxTotalBytes = getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which);
+ long mobileRxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which);
+ long mobileTxTotalPackets = getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which);
+ long wifiRxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which);
+ long wifiTxTotalPackets = getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which);
+
+ if (fullWakeLockTimeTotalMicros != 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Total full wakelock time: "); formatTimeMsNoSpace(sb,
+ (fullWakeLockTimeTotalMicros + 500) / 1000);
+ pw.println(sb.toString());
+ }
+
+ if (partialWakeLockTimeTotalMicros != 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Total partial wakelock time: "); formatTimeMsNoSpace(sb,
+ (partialWakeLockTimeTotalMicros + 500) / 1000);
+ pw.println(sb.toString());
+ }
+
pw.print(prefix);
- pw.print(" Mobile total received: "); pw.print(formatBytesLocked(mobileRxTotal));
- pw.print(", Total sent: "); pw.println(formatBytesLocked(mobileTxTotal));
- pw.print(prefix);
- pw.print(" Wi-Fi total received: "); pw.print(formatBytesLocked(wifiRxTotal));
- pw.print(", Total sent: "); pw.println(formatBytesLocked(wifiTxTotal));
- sb.setLength(0);
- sb.append(prefix);
- sb.append(" Total full wakelock time: "); formatTimeMs(sb,
- (fullWakeLockTimeTotalMicros + 500) / 1000);
- sb.append(", Total partial wakelock time: "); formatTimeMs(sb,
- (partialWakeLockTimeTotalMicros + 500) / 1000);
- pw.println(sb.toString());
-
+ pw.print(" Mobile total received: "); pw.print(formatBytesLocked(mobileRxTotalBytes));
+ pw.print(", sent: "); pw.print(formatBytesLocked(mobileTxTotalBytes));
+ pw.print(" (packets received "); pw.print(mobileRxTotalPackets);
+ pw.print(", sent "); pw.print(mobileTxTotalPackets); pw.println(")");
sb.setLength(0);
sb.append(prefix);
- sb.append(" Signal levels: ");
+ sb.append(" Signal levels:");
didOne = false;
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
- final long time = getPhoneSignalStrengthTime(i, batteryRealtime, which);
+ final long time = getPhoneSignalStrengthTime(i, rawRealtime, which);
if (time == 0) {
continue;
}
- if (didOne) sb.append(", ");
+ sb.append("\n ");
+ sb.append(prefix);
didOne = true;
sb.append(SignalStrength.SIGNAL_STRENGTH_NAMES[i]);
sb.append(" ");
@@ -1725,25 +2048,26 @@ public abstract class BatteryStats implements Parcelable {
sb.append(getPhoneSignalStrengthCount(i, which));
sb.append("x");
}
- if (!didOne) sb.append("No activity");
+ if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
sb.append(" Signal scanning time: ");
- formatTimeMs(sb, getPhoneSignalScanningTime(batteryRealtime, which) / 1000);
+ formatTimeMsNoSpace(sb, getPhoneSignalScanningTime(rawRealtime, which) / 1000);
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" Radio types: ");
+ sb.append(" Radio types:");
didOne = false;
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- final long time = getPhoneDataConnectionTime(i, batteryRealtime, which);
+ final long time = getPhoneDataConnectionTime(i, rawRealtime, which);
if (time == 0) {
continue;
}
- if (didOne) sb.append(", ");
+ sb.append("\n ");
+ sb.append(prefix);
didOne = true;
sb.append(DATA_CONNECTION_NAMES[i]);
sb.append(" ");
@@ -1754,28 +2078,100 @@ public abstract class BatteryStats implements Parcelable {
sb.append(getPhoneDataConnectionCount(i, which));
sb.append("x");
}
- if (!didOne) sb.append("No activity");
+ if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
sb.setLength(0);
sb.append(prefix);
- sb.append(" Radio data uptime when unplugged: ");
- sb.append(getRadioDataUptime() / 1000);
- sb.append(" ms");
+ sb.append(" Mobile radio active time: ");
+ final long mobileActiveTime = getMobileRadioActiveTime(rawRealtime, which);
+ formatTimeMs(sb, mobileActiveTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(mobileActiveTime, whichBatteryRealtime));
+ sb.append(") "); sb.append(getMobileRadioActiveCount(which));
+ sb.append("x");
pw.println(sb.toString());
+ final long mobileActiveUnknownTime = getMobileRadioActiveUnknownTime(which);
+ if (mobileActiveUnknownTime != 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Mobile radio active unknown time: ");
+ formatTimeMs(sb, mobileActiveUnknownTime / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(mobileActiveUnknownTime, whichBatteryRealtime));
+ sb.append(") "); sb.append(getMobileRadioActiveUnknownCount(which));
+ sb.append("x");
+ pw.println(sb.toString());
+ }
+
+ pw.print(prefix);
+ pw.print(" Wi-Fi total received: "); pw.print(formatBytesLocked(wifiRxTotalBytes));
+ pw.print(", sent: "); pw.print(formatBytesLocked(wifiTxTotalBytes));
+ pw.print(" (packets received "); pw.print(wifiRxTotalPackets);
+ pw.print(", sent "); pw.print(wifiTxTotalPackets); pw.println(")");
sb.setLength(0);
sb.append(prefix);
sb.append(" Wifi on: "); formatTimeMs(sb, wifiOnTime / 1000);
sb.append("("); sb.append(formatRatioLocked(wifiOnTime, whichBatteryRealtime));
sb.append("), Wifi running: "); formatTimeMs(sb, wifiRunningTime / 1000);
sb.append("("); sb.append(formatRatioLocked(wifiRunningTime, whichBatteryRealtime));
- sb.append("), Bluetooth on: "); formatTimeMs(sb, bluetoothOnTime / 1000);
+ sb.append(")");
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Wifi states:");
+ didOne = false;
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ final long time = getWifiStateTime(i, rawRealtime, which);
+ if (time == 0) {
+ continue;
+ }
+ sb.append("\n ");
+ didOne = true;
+ sb.append(WIFI_STATE_NAMES[i]);
+ sb.append(" ");
+ formatTimeMs(sb, time/1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(time, whichBatteryRealtime));
+ sb.append(") ");
+ sb.append(getPhoneDataConnectionCount(i, which));
+ sb.append("x");
+ }
+ if (!didOne) sb.append(" (no activity)");
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Bluetooth on: "); formatTimeMs(sb, bluetoothOnTime / 1000);
sb.append("("); sb.append(formatRatioLocked(bluetoothOnTime, whichBatteryRealtime));
sb.append(")");
pw.println(sb.toString());
-
- pw.println(" ");
+
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Bluetooth states:");
+ didOne = false;
+ for (int i=0; i<NUM_BLUETOOTH_STATES; i++) {
+ final long time = getBluetoothStateTime(i, rawRealtime, which);
+ if (time == 0) {
+ continue;
+ }
+ sb.append("\n ");
+ didOne = true;
+ sb.append(BLUETOOTH_STATE_NAMES[i]);
+ sb.append(" ");
+ formatTimeMs(sb, time/1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(time, whichBatteryRealtime));
+ sb.append(") ");
+ sb.append(getPhoneDataConnectionCount(i, which));
+ sb.append("x");
+ }
+ if (!didOne) sb.append(" (no activity)");
+ pw.println(sb.toString());
+
+ pw.println();
if (which == STATS_SINCE_UNPLUGGED) {
if (getIsOnBattery()) {
@@ -1809,6 +2205,142 @@ public abstract class BatteryStats implements Parcelable {
pw.println();
}
+ BatteryStatsHelper helper = new BatteryStatsHelper(context);
+ helper.create(this);
+ helper.refreshStats(which, UserHandle.USER_ALL);
+ List<BatterySipper> sippers = helper.getUsageList();
+ if (sippers != null && sippers.size() > 0) {
+ pw.print(prefix); pw.println(" Estimated power use (mAh):");
+ pw.print(prefix); pw.print(" Capacity: ");
+ printmAh(pw, helper.getPowerProfile().getBatteryCapacity());
+ pw.print(", Computed drain: "); printmAh(pw, helper.getComputedPower());
+ pw.print(", Min drain: "); printmAh(pw, helper.getMinDrainedPower());
+ pw.print(", Max drain: "); printmAh(pw, helper.getMaxDrainedPower());
+ pw.println();
+ for (int i=0; i<sippers.size(); i++) {
+ BatterySipper bs = sippers.get(i);
+ switch (bs.drainType) {
+ case IDLE:
+ pw.print(prefix); pw.print(" Idle: "); printmAh(pw, bs.value);
+ pw.println();
+ break;
+ case CELL:
+ pw.print(prefix); pw.print(" Cell standby: "); printmAh(pw, bs.value);
+ pw.println();
+ break;
+ case PHONE:
+ pw.print(prefix); pw.print(" Phone calls: "); printmAh(pw, bs.value);
+ pw.println();
+ break;
+ case WIFI:
+ pw.print(prefix); pw.print(" Wifi: "); printmAh(pw, bs.value);
+ pw.println();
+ break;
+ case BLUETOOTH:
+ pw.print(prefix); pw.print(" Bluetooth: "); printmAh(pw, bs.value);
+ pw.println();
+ break;
+ case SCREEN:
+ pw.print(prefix); pw.print(" Screen: "); printmAh(pw, bs.value);
+ pw.println();
+ break;
+ case APP:
+ pw.print(prefix); pw.print(" Uid ");
+ UserHandle.formatUid(pw, bs.uidObj.getUid());
+ pw.print(": "); printmAh(pw, bs.value); pw.println();
+ break;
+ case USER:
+ pw.print(prefix); pw.print(" User "); pw.print(bs.userId);
+ pw.print(": "); printmAh(pw, bs.value); pw.println();
+ break;
+ case UNACCOUNTED:
+ pw.print(prefix); pw.print(" Unaccounted: "); printmAh(pw, bs.value);
+ pw.println();
+ break;
+ case OVERCOUNTED:
+ pw.print(prefix); pw.print(" Over-counted: "); printmAh(pw, bs.value);
+ pw.println();
+ break;
+ }
+ }
+ pw.println();
+ }
+
+ sippers = helper.getMobilemsppList();
+ if (sippers != null && sippers.size() > 0) {
+ pw.print(prefix); pw.println(" Per-app mobile ms per packet:");
+ long totalTime = 0;
+ for (int i=0; i<sippers.size(); i++) {
+ BatterySipper bs = sippers.get(i);
+ sb.setLength(0);
+ sb.append(prefix); sb.append(" Uid ");
+ UserHandle.formatUid(sb, bs.uidObj.getUid());
+ sb.append(": "); sb.append(BatteryStatsHelper.makemAh(bs.mobilemspp));
+ sb.append(" ("); sb.append(bs.mobileRxPackets+bs.mobileTxPackets);
+ sb.append(" packets over "); formatTimeMsNoSpace(sb, bs.mobileActive);
+ sb.append(") "); sb.append(bs.mobileActiveCount); sb.append("x");
+ pw.println(sb.toString());
+ totalTime += bs.mobileActive;
+ }
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" TOTAL TIME: ");
+ formatTimeMs(sb, totalTime);
+ sb.append("("); sb.append(formatRatioLocked(totalTime, whichBatteryRealtime));
+ sb.append(")");
+ pw.println(sb.toString());
+ pw.println();
+ }
+
+ final Comparator<TimerEntry> timerComparator = new Comparator<TimerEntry>() {
+ @Override
+ public int compare(TimerEntry lhs, TimerEntry rhs) {
+ long lhsTime = lhs.mTime;
+ long rhsTime = rhs.mTime;
+ if (lhsTime < rhsTime) {
+ return 1;
+ }
+ if (lhsTime > rhsTime) {
+ return -1;
+ }
+ return 0;
+ }
+ };
+
+ if (reqUid < 0) {
+ Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats();
+ if (kernelWakelocks.size() > 0) {
+ final ArrayList<TimerEntry> ktimers = new ArrayList<TimerEntry>();
+ for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) {
+ BatteryStats.Timer timer = ent.getValue();
+ long totalTimeMillis = computeWakeLock(timer, rawRealtime, which);
+ if (totalTimeMillis > 0) {
+ ktimers.add(new TimerEntry(ent.getKey(), 0, timer, totalTimeMillis));
+ }
+ }
+ if (ktimers.size() > 0) {
+ Collections.sort(ktimers, timerComparator);
+ pw.print(prefix); pw.println(" All kernel wake locks:");
+ for (int i=0; i<ktimers.size(); i++) {
+ TimerEntry timer = ktimers.get(i);
+ String linePrefix = ": ";
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Kernel Wake lock ");
+ sb.append(timer.mName);
+ linePrefix = printWakeLock(sb, timer.mTimer, rawRealtime, null,
+ which, linePrefix);
+ if (!linePrefix.equals(": ")) {
+ sb.append(" realtime");
+ // Only print out wake locks that were held
+ pw.println(sb.toString());
+ }
+ }
+ pw.println();
+ }
+ }
+ }
+
if (timers.size() > 0) {
Collections.sort(timers, timerComparator);
pw.print(prefix); pw.println(" All partial wake locks:");
@@ -1819,7 +2351,7 @@ public abstract class BatteryStats implements Parcelable {
UserHandle.formatUid(sb, timer.mId);
sb.append(" ");
sb.append(timer.mName);
- printWakeLock(sb, timer.mTimer, batteryRealtime, null, which, ": ");
+ printWakeLock(sb, timer.mTimer, rawRealtime, null, which, ": ");
sb.append(" realtime");
pw.println(sb.toString());
}
@@ -1840,24 +2372,70 @@ public abstract class BatteryStats implements Parcelable {
UserHandle.formatUid(pw, uid);
pw.println(":");
boolean uidActivity = false;
-
- long mobileRxBytes = u.getNetworkActivityCount(NETWORK_MOBILE_RX_BYTES, which);
- long mobileTxBytes = u.getNetworkActivityCount(NETWORK_MOBILE_TX_BYTES, which);
- long wifiRxBytes = u.getNetworkActivityCount(NETWORK_WIFI_RX_BYTES, which);
- long wifiTxBytes = u.getNetworkActivityCount(NETWORK_WIFI_TX_BYTES, which);
- long fullWifiLockOnTime = u.getFullWifiLockTime(batteryRealtime, which);
- long wifiScanTime = u.getWifiScanTime(batteryRealtime, which);
- long uidWifiRunningTime = u.getWifiRunningTime(batteryRealtime, which);
-
- if (mobileRxBytes > 0 || mobileTxBytes > 0) {
+
+ long mobileRxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which);
+ long mobileTxBytes = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which);
+ long wifiRxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which);
+ long wifiTxBytes = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which);
+ long mobileRxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which);
+ long mobileTxPackets = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which);
+ long uidMobileActiveTime = u.getMobileRadioActiveTime(which);
+ int uidMobileActiveCount = u.getMobileRadioActiveCount(which);
+ long wifiRxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which);
+ long wifiTxPackets = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which);
+ long fullWifiLockOnTime = u.getFullWifiLockTime(rawRealtime, which);
+ long wifiScanTime = u.getWifiScanTime(rawRealtime, which);
+ long uidWifiRunningTime = u.getWifiRunningTime(rawRealtime, which);
+
+ if (mobileRxBytes > 0 || mobileTxBytes > 0
+ || mobileRxPackets > 0 || mobileTxPackets > 0) {
pw.print(prefix); pw.print(" Mobile network: ");
pw.print(formatBytesLocked(mobileRxBytes)); pw.print(" received, ");
- pw.print(formatBytesLocked(mobileTxBytes)); pw.println(" sent");
+ pw.print(formatBytesLocked(mobileTxBytes));
+ pw.print(" sent (packets "); pw.print(mobileRxPackets);
+ pw.print(" received, "); pw.print(mobileTxPackets); pw.println(" sent)");
}
- if (wifiRxBytes > 0 || wifiTxBytes > 0) {
+ if (uidMobileActiveTime > 0 || uidMobileActiveCount > 0) {
+ sb.setLength(0);
+ sb.append(prefix); sb.append(" Mobile radio active: ");
+ formatTimeMs(sb, uidMobileActiveTime / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(uidMobileActiveTime, mobileActiveTime));
+ sb.append(") "); sb.append(uidMobileActiveCount); sb.append("x");
+ long packets = mobileRxPackets + mobileTxPackets;
+ if (packets == 0) {
+ packets = 1;
+ }
+ sb.append(" @ ");
+ sb.append(BatteryStatsHelper.makemAh(uidMobileActiveTime / 1000 / (double)packets));
+ sb.append(" mspp");
+ pw.println(sb.toString());
+ }
+
+ if (wifiRxBytes > 0 || wifiTxBytes > 0 || wifiRxPackets > 0 || wifiTxPackets > 0) {
pw.print(prefix); pw.print(" Wi-Fi network: ");
pw.print(formatBytesLocked(wifiRxBytes)); pw.print(" received, ");
- pw.print(formatBytesLocked(wifiTxBytes)); pw.println(" sent");
+ pw.print(formatBytesLocked(wifiTxBytes));
+ pw.print(" sent (packets "); pw.print(wifiRxPackets);
+ pw.print(" received, "); pw.print(wifiTxPackets); pw.println(" sent)");
+ }
+
+ if (fullWifiLockOnTime != 0 || wifiScanTime != 0
+ || uidWifiRunningTime != 0) {
+ sb.setLength(0);
+ sb.append(prefix); sb.append(" Wifi Running: ");
+ formatTimeMs(sb, uidWifiRunningTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(uidWifiRunningTime,
+ whichBatteryRealtime)); sb.append(")\n");
+ sb.append(prefix); sb.append(" Full Wifi Lock: ");
+ formatTimeMs(sb, fullWifiLockOnTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(fullWifiLockOnTime,
+ whichBatteryRealtime)); sb.append(")\n");
+ sb.append(prefix); sb.append(" Wifi Scan: ");
+ formatTimeMs(sb, wifiScanTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(wifiScanTime,
+ whichBatteryRealtime)); sb.append(")");
+ pw.println(sb.toString());
}
if (u.hasUserActivity()) {
@@ -1881,24 +2459,6 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
}
-
- if (fullWifiLockOnTime != 0 || wifiScanTime != 0
- || uidWifiRunningTime != 0) {
- sb.setLength(0);
- sb.append(prefix); sb.append(" Wifi Running: ");
- formatTimeMs(sb, uidWifiRunningTime / 1000);
- sb.append("("); sb.append(formatRatioLocked(uidWifiRunningTime,
- whichBatteryRealtime)); sb.append(")\n");
- sb.append(prefix); sb.append(" Full Wifi Lock: ");
- formatTimeMs(sb, fullWifiLockOnTime / 1000);
- sb.append("("); sb.append(formatRatioLocked(fullWifiLockOnTime,
- whichBatteryRealtime)); sb.append(")\n");
- sb.append(prefix); sb.append(" Wifi Scan: ");
- formatTimeMs(sb, wifiScanTime / 1000);
- sb.append("("); sb.append(formatRatioLocked(wifiScanTime,
- whichBatteryRealtime)); sb.append(")");
- pw.println(sb.toString());
- }
Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats();
if (wakelocks.size() > 0) {
@@ -1912,11 +2472,11 @@ public abstract class BatteryStats implements Parcelable {
sb.append(prefix);
sb.append(" Wake lock ");
sb.append(ent.getKey());
- linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_FULL), batteryRealtime,
+ linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_FULL), rawRealtime,
"full", which, linePrefix);
- linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), batteryRealtime,
+ linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_PARTIAL), rawRealtime,
"partial", which, linePrefix);
- linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), batteryRealtime,
+ linePrefix = printWakeLock(sb, wl.getWakeTime(WAKE_TYPE_WINDOW), rawRealtime,
"window", which, linePrefix);
if (!linePrefix.equals(": ")) {
sb.append(" realtime");
@@ -1926,11 +2486,11 @@ public abstract class BatteryStats implements Parcelable {
count++;
}
totalFull += computeWakeLock(wl.getWakeTime(WAKE_TYPE_FULL),
- batteryRealtime, which);
+ rawRealtime, which);
totalPartial += computeWakeLock(wl.getWakeTime(WAKE_TYPE_PARTIAL),
- batteryRealtime, which);
+ rawRealtime, which);
totalWindow += computeWakeLock(wl.getWakeTime(WAKE_TYPE_WINDOW),
- batteryRealtime, which);
+ rawRealtime, which);
}
if (count > 1) {
if (totalFull != 0 || totalPartial != 0 || totalWindow != 0) {
@@ -1986,7 +2546,7 @@ public abstract class BatteryStats implements Parcelable {
if (timer != null) {
// Convert from microseconds to milliseconds with rounding
long totalTime = (timer.getTotalTimeLocked(
- batteryRealtime, which) + 500) / 1000;
+ rawRealtime, which) + 500) / 1000;
int count = timer.getCountLocked(which);
//timer.logState();
if (totalTime != 0) {
@@ -2010,7 +2570,7 @@ public abstract class BatteryStats implements Parcelable {
if (vibTimer != null) {
// Convert from microseconds to milliseconds with rounding
long totalTime = (vibTimer.getTotalTimeLocked(
- batteryRealtime, which) + 500) / 1000;
+ rawRealtime, which) + 500) / 1000;
int count = vibTimer.getCountLocked(which);
//timer.logState();
if (totalTime != 0) {
@@ -2029,7 +2589,7 @@ public abstract class BatteryStats implements Parcelable {
Timer fgTimer = u.getForegroundActivityTimer();
if (fgTimer != null) {
// Convert from microseconds to milliseconds with rounding
- long totalTime = (fgTimer.getTotalTimeLocked(batteryRealtime, which) + 500) / 1000;
+ long totalTime = (fgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
int count = fgTimer.getCountLocked(which);
if (totalTime != 0) {
sb.setLength(0);
@@ -2151,28 +2711,53 @@ public abstract class BatteryStats implements Parcelable {
}
}
- static void printBitDescriptions(PrintWriter pw, int oldval, int newval, BitDescription[] descriptions) {
+ static void printBitDescriptions(PrintWriter pw, int oldval, int newval, HistoryTag wakelockTag,
+ BitDescription[] descriptions, boolean longNames) {
int diff = oldval ^ newval;
if (diff == 0) return;
+ boolean didWake = false;
for (int i=0; i<descriptions.length; i++) {
BitDescription bd = descriptions[i];
if ((diff&bd.mask) != 0) {
+ pw.print(longNames ? " " : ",");
if (bd.shift < 0) {
- pw.print((newval&bd.mask) != 0 ? " +" : " -");
- pw.print(bd.name);
+ pw.print((newval&bd.mask) != 0 ? "+" : "-");
+ pw.print(longNames ? bd.name : bd.shortName);
+ if (bd.mask == HistoryItem.STATE_WAKE_LOCK_FLAG && wakelockTag != null) {
+ didWake = true;
+ pw.print("=");
+ if (longNames) {
+ UserHandle.formatUid(pw, wakelockTag.uid);
+ pw.print(":\"");
+ pw.print(wakelockTag.string);
+ pw.print("\"");
+ } else {
+ pw.print(wakelockTag.poolIdx);
+ }
+ }
} else {
- pw.print(" ");
- pw.print(bd.name);
+ pw.print(longNames ? bd.name : bd.shortName);
pw.print("=");
int val = (newval&bd.mask)>>bd.shift;
if (bd.values != null && val >= 0 && val < bd.values.length) {
- pw.print(bd.values[val]);
+ pw.print(longNames? bd.values[val] : bd.shortValues[val]);
} else {
pw.print(val);
}
}
}
}
+ if (!didWake && wakelockTag != null) {
+ pw.print(longNames ? "wake_lock=" : "w=");
+ if (longNames) {
+ UserHandle.formatUid(pw, wakelockTag.uid);
+ pw.print(":\"");
+ pw.print(wakelockTag.string);
+ pw.print("\"");
+ } else {
+ pw.print(wakelockTag.poolIdx);
+ }
+ }
}
public void prepareForDumpLocked() {
@@ -2180,51 +2765,99 @@ public abstract class BatteryStats implements Parcelable {
public static class HistoryPrinter {
int oldState = 0;
+ int oldLevel = -1;
int oldStatus = -1;
int oldHealth = -1;
int oldPlug = -1;
int oldTemp = -1;
int oldVolt = -1;
+ long lastTime = -1;
- public void printNextItem(PrintWriter pw, HistoryItem rec, long now) {
- pw.print(" ");
- TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
- pw.print(" ");
+ public void printNextItem(PrintWriter pw, HistoryItem rec, long now, boolean checkin) {
+ if (!checkin) {
+ pw.print(" ");
+ if (now >= 0) {
+ TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
+ } else {
+ TimeUtils.formatDuration(rec.time, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
+ }
+ pw.print(" (");
+ pw.print(rec.numReadInts);
+ pw.print(") ");
+ } else {
+ if (lastTime < 0) {
+ if (now >= 0) {
+ pw.print("@");
+ pw.print(rec.time-now);
+ } else {
+ pw.print(rec.time);
+ }
+ } else {
+ pw.print(rec.time-lastTime);
+ }
+ lastTime = rec.time;
+ }
if (rec.cmd == HistoryItem.CMD_START) {
- pw.println(" START");
+ if (checkin) {
+ pw.print(":");
+ }
+ pw.println("START");
+ } else if (rec.cmd == HistoryItem.CMD_CURRENT_TIME) {
+ if (checkin) {
+ pw.print(":");
+ }
+ pw.print("TIME:");
+ if (checkin) {
+ pw.println(rec.currentTime);
+ } else {
+ pw.print(" ");
+ pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
+ rec.currentTime).toString());
+ }
} else if (rec.cmd == HistoryItem.CMD_OVERFLOW) {
- pw.println(" *OVERFLOW*");
+ if (checkin) {
+ pw.print(":");
+ }
+ pw.println("*OVERFLOW*");
} else {
- if (rec.batteryLevel < 10) pw.print("00");
- else if (rec.batteryLevel < 100) pw.print("0");
- pw.print(rec.batteryLevel);
- pw.print(" ");
- if (rec.states < 0x10) pw.print("0000000");
- else if (rec.states < 0x100) pw.print("000000");
- else if (rec.states < 0x1000) pw.print("00000");
- else if (rec.states < 0x10000) pw.print("0000");
- else if (rec.states < 0x100000) pw.print("000");
- else if (rec.states < 0x1000000) pw.print("00");
- else if (rec.states < 0x10000000) pw.print("0");
- pw.print(Integer.toHexString(rec.states));
+ if (!checkin) {
+ if (rec.batteryLevel < 10) pw.print("00");
+ else if (rec.batteryLevel < 100) pw.print("0");
+ pw.print(rec.batteryLevel);
+ pw.print(" ");
+ if (rec.states < 0) ;
+ else if (rec.states < 0x10) pw.print("0000000");
+ else if (rec.states < 0x100) pw.print("000000");
+ else if (rec.states < 0x1000) pw.print("00000");
+ else if (rec.states < 0x10000) pw.print("0000");
+ else if (rec.states < 0x100000) pw.print("000");
+ else if (rec.states < 0x1000000) pw.print("00");
+ else if (rec.states < 0x10000000) pw.print("0");
+ pw.print(Integer.toHexString(rec.states));
+ } else {
+ if (oldLevel != rec.batteryLevel) {
+ oldLevel = rec.batteryLevel;
+ pw.print(",Bl="); pw.print(rec.batteryLevel);
+ }
+ }
if (oldStatus != rec.batteryStatus) {
oldStatus = rec.batteryStatus;
- pw.print(" status=");
+ pw.print(checkin ? ",Bs=" : " status=");
switch (oldStatus) {
case BatteryManager.BATTERY_STATUS_UNKNOWN:
- pw.print("unknown");
+ pw.print(checkin ? "?" : "unknown");
break;
case BatteryManager.BATTERY_STATUS_CHARGING:
- pw.print("charging");
+ pw.print(checkin ? "c" : "charging");
break;
case BatteryManager.BATTERY_STATUS_DISCHARGING:
- pw.print("discharging");
+ pw.print(checkin ? "d" : "discharging");
break;
case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
- pw.print("not-charging");
+ pw.print(checkin ? "n" : "not-charging");
break;
case BatteryManager.BATTERY_STATUS_FULL:
- pw.print("full");
+ pw.print(checkin ? "f" : "full");
break;
default:
pw.print(oldStatus);
@@ -2233,25 +2866,28 @@ public abstract class BatteryStats implements Parcelable {
}
if (oldHealth != rec.batteryHealth) {
oldHealth = rec.batteryHealth;
- pw.print(" health=");
+ pw.print(checkin ? ",Bh=" : " health=");
switch (oldHealth) {
case BatteryManager.BATTERY_HEALTH_UNKNOWN:
- pw.print("unknown");
+ pw.print(checkin ? "?" : "unknown");
break;
case BatteryManager.BATTERY_HEALTH_GOOD:
- pw.print("good");
+ pw.print(checkin ? "g" : "good");
break;
case BatteryManager.BATTERY_HEALTH_OVERHEAT:
- pw.print("overheat");
+ pw.print(checkin ? "h" : "overheat");
break;
case BatteryManager.BATTERY_HEALTH_DEAD:
- pw.print("dead");
+ pw.print(checkin ? "d" : "dead");
break;
case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
- pw.print("over-voltage");
+ pw.print(checkin ? "v" : "over-voltage");
break;
case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:
- pw.print("failure");
+ pw.print(checkin ? "f" : "failure");
+ break;
+ case BatteryManager.BATTERY_HEALTH_COLD:
+ pw.print(checkin ? "c" : "cold");
break;
default:
pw.print(oldHealth);
@@ -2260,19 +2896,19 @@ public abstract class BatteryStats implements Parcelable {
}
if (oldPlug != rec.batteryPlugType) {
oldPlug = rec.batteryPlugType;
- pw.print(" plug=");
+ pw.print(checkin ? ",Bp=" : " plug=");
switch (oldPlug) {
case 0:
- pw.print("none");
+ pw.print(checkin ? "n" : "none");
break;
case BatteryManager.BATTERY_PLUGGED_AC:
- pw.print("ac");
+ pw.print(checkin ? "a" : "ac");
break;
case BatteryManager.BATTERY_PLUGGED_USB:
- pw.print("usb");
+ pw.print(checkin ? "u" : "usb");
break;
case BatteryManager.BATTERY_PLUGGED_WIRELESS:
- pw.print("wireless");
+ pw.print(checkin ? "w" : "wireless");
break;
default:
pw.print(oldPlug);
@@ -2281,139 +2917,248 @@ public abstract class BatteryStats implements Parcelable {
}
if (oldTemp != rec.batteryTemperature) {
oldTemp = rec.batteryTemperature;
- pw.print(" temp=");
+ pw.print(checkin ? ",Bt=" : " temp=");
pw.print(oldTemp);
}
if (oldVolt != rec.batteryVoltage) {
oldVolt = rec.batteryVoltage;
- pw.print(" volt=");
+ pw.print(checkin ? ",Bv=" : " volt=");
pw.print(oldVolt);
}
- printBitDescriptions(pw, oldState, rec.states,
- HISTORY_STATE_DESCRIPTIONS);
+ printBitDescriptions(pw, oldState, rec.states, rec.wakelockTag,
+ HISTORY_STATE_DESCRIPTIONS, !checkin);
+ if (rec.wakeReasonTag != null) {
+ if (checkin) {
+ pw.print(",Wr=");
+ pw.print(rec.wakeReasonTag.poolIdx);
+ } else {
+ pw.print(" wake_reason=");
+ pw.print(rec.wakeReasonTag.uid);
+ pw.print(":\"");
+ pw.print(rec.wakeReasonTag.string);
+ pw.print("\"");
+ }
+ }
+ if (rec.eventCode != HistoryItem.EVENT_NONE) {
+ pw.print(checkin ? "," : " ");
+ if ((rec.eventCode&HistoryItem.EVENT_FLAG_START) != 0) {
+ pw.print("+");
+ } else if ((rec.eventCode&HistoryItem.EVENT_FLAG_FINISH) != 0) {
+ pw.print("-");
+ }
+ String[] eventNames = checkin ? HISTORY_EVENT_CHECKIN_NAMES
+ : HISTORY_EVENT_NAMES;
+ int idx = rec.eventCode & ~(HistoryItem.EVENT_FLAG_START
+ | HistoryItem.EVENT_FLAG_FINISH);
+ if (idx >= 0 && idx < eventNames.length) {
+ pw.print(eventNames[idx]);
+ } else {
+ pw.print(checkin ? "Ev" : "event");
+ pw.print(idx);
+ }
+ pw.print("=");
+ if (checkin) {
+ pw.print(rec.eventTag.poolIdx);
+ } else {
+ UserHandle.formatUid(pw, rec.eventTag.uid);
+ pw.print(":\"");
+ pw.print(rec.eventTag.string);
+ pw.print("\"");
+ }
+ }
pw.println();
+ oldState = rec.states;
}
- oldState = rec.states;
}
+ }
- public void printNextItemCheckin(PrintWriter pw, HistoryItem rec, long now) {
- pw.print(rec.time-now);
- pw.print(",");
- if (rec.cmd == HistoryItem.CMD_START) {
- pw.print("start");
- } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) {
- pw.print("overflow");
- } else {
- pw.print(rec.batteryLevel);
- pw.print(",");
- pw.print(rec.states);
- pw.print(",");
- pw.print(rec.batteryStatus);
- pw.print(",");
- pw.print(rec.batteryHealth);
- pw.print(",");
- pw.print(rec.batteryPlugType);
- pw.print(",");
- pw.print((int)rec.batteryTemperature);
- pw.print(",");
- pw.print((int)rec.batteryVoltage);
- }
+ private void printSizeValue(PrintWriter pw, long size) {
+ float result = size;
+ String suffix = "";
+ if (result >= 10*1024) {
+ suffix = "KB";
+ result = result / 1024;
+ }
+ if (result >= 10*1024) {
+ suffix = "MB";
+ result = result / 1024;
}
+ if (result >= 10*1024) {
+ suffix = "GB";
+ result = result / 1024;
+ }
+ if (result >= 10*1024) {
+ suffix = "TB";
+ result = result / 1024;
+ }
+ if (result >= 10*1024) {
+ suffix = "PB";
+ result = result / 1024;
+ }
+ pw.print((int)result);
+ pw.print(suffix);
}
+ public static final int DUMP_UNPLUGGED_ONLY = 1<<0;
+ public static final int DUMP_CHARGED_ONLY = 1<<1;
+ public static final int DUMP_HISTORY_ONLY = 1<<2;
+ public static final int DUMP_INCLUDE_HISTORY = 1<<3;
+
/**
* Dumps a human-readable summary of the battery statistics to the given PrintWriter.
*
* @param pw a Printer to receive the dump output.
*/
@SuppressWarnings("unused")
- public void dumpLocked(PrintWriter pw, boolean isUnpluggedOnly, int reqUid) {
+ public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) {
prepareForDumpLocked();
- long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+ final boolean filtering =
+ (flags&(DUMP_HISTORY_ONLY|DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY)) != 0;
+
+ if ((flags&DUMP_HISTORY_ONLY) != 0 || !filtering) {
+ long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+
+ final HistoryItem rec = new HistoryItem();
+ final long historyTotalSize = getHistoryTotalSize();
+ final long historyUsedSize = getHistoryUsedSize();
+ if (startIteratingHistoryLocked()) {
+ try {
+ pw.print("Battery History (");
+ pw.print((100*historyUsedSize)/historyTotalSize);
+ pw.print("% used, ");
+ printSizeValue(pw, historyUsedSize);
+ pw.print(" used of ");
+ printSizeValue(pw, historyTotalSize);
+ pw.print(", ");
+ pw.print(getHistoryStringPoolSize());
+ pw.print(" strings using ");
+ printSizeValue(pw, getHistoryStringPoolBytes());
+ pw.println("):");
+ HistoryPrinter hprinter = new HistoryPrinter();
+ long lastTime = -1;
+ while (getNextHistoryLocked(rec)) {
+ lastTime = rec.time;
+ if (rec.time >= histStart) {
+ hprinter.printNextItem(pw, rec, histStart >= 0 ? -1 : now, false);
+ }
+ }
+ if (histStart >= 0) {
+ pw.print(" NEXT: "); pw.println(lastTime+1);
+ }
+ pw.println();
+ } finally {
+ finishIteratingHistoryLocked();
+ }
+ }
- final HistoryItem rec = new HistoryItem();
- if (startIteratingHistoryLocked()) {
- pw.println("Battery History:");
- HistoryPrinter hprinter = new HistoryPrinter();
- while (getNextHistoryLocked(rec)) {
- hprinter.printNextItem(pw, rec, now);
+ if (startIteratingOldHistoryLocked()) {
+ try {
+ pw.println("Old battery History:");
+ HistoryPrinter hprinter = new HistoryPrinter();
+ while (getNextOldHistoryLocked(rec)) {
+ hprinter.printNextItem(pw, rec, now, false);
+ }
+ pw.println();
+ } finally {
+ finishIteratingOldHistoryLocked();
+ }
}
- finishIteratingHistoryLocked();
- pw.println("");
}
- if (startIteratingOldHistoryLocked()) {
- pw.println("Old battery History:");
- HistoryPrinter hprinter = new HistoryPrinter();
- while (getNextOldHistoryLocked(rec)) {
- hprinter.printNextItem(pw, rec, now);
- }
- finishIteratingOldHistoryLocked();
- pw.println("");
+ if (filtering && (flags&(DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY)) == 0) {
+ return;
}
-
- SparseArray<? extends Uid> uidStats = getUidStats();
- final int NU = uidStats.size();
- boolean didPid = false;
- long nowRealtime = SystemClock.elapsedRealtime();
- for (int i=0; i<NU; i++) {
- Uid uid = uidStats.valueAt(i);
- SparseArray<? extends Uid.Pid> pids = uid.getPidStats();
- if (pids != null) {
- for (int j=0; j<pids.size(); j++) {
- Uid.Pid pid = pids.valueAt(j);
- if (!didPid) {
- pw.println("Per-PID Stats:");
- didPid = true;
+
+ if (!filtering) {
+ SparseArray<? extends Uid> uidStats = getUidStats();
+ final int NU = uidStats.size();
+ boolean didPid = false;
+ long nowRealtime = SystemClock.elapsedRealtime();
+ for (int i=0; i<NU; i++) {
+ Uid uid = uidStats.valueAt(i);
+ SparseArray<? extends Uid.Pid> pids = uid.getPidStats();
+ if (pids != null) {
+ for (int j=0; j<pids.size(); j++) {
+ Uid.Pid pid = pids.valueAt(j);
+ if (!didPid) {
+ pw.println("Per-PID Stats:");
+ didPid = true;
+ }
+ long time = pid.mWakeSumMs + (pid.mWakeNesting > 0
+ ? (nowRealtime - pid.mWakeStartMs) : 0);
+ pw.print(" PID "); pw.print(pids.keyAt(j));
+ pw.print(" wake time: ");
+ TimeUtils.formatDuration(time, pw);
+ pw.println("");
}
- long time = pid.mWakeSum + (pid.mWakeStart != 0
- ? (nowRealtime - pid.mWakeStart) : 0);
- pw.print(" PID "); pw.print(pids.keyAt(j));
- pw.print(" wake time: ");
- TimeUtils.formatDuration(time, pw);
- pw.println("");
}
}
- }
- if (didPid) {
- pw.println("");
+ if (didPid) {
+ pw.println("");
+ }
}
- if (!isUnpluggedOnly) {
+ if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
pw.println("Statistics since last charge:");
pw.println(" System starts: " + getStartCount()
+ ", currently on battery: " + getIsOnBattery());
- dumpLocked(pw, "", STATS_SINCE_CHARGED, reqUid);
+ dumpLocked(context, pw, "", STATS_SINCE_CHARGED, reqUid);
pw.println("");
}
- pw.println("Statistics since last unplugged:");
- dumpLocked(pw, "", STATS_SINCE_UNPLUGGED, reqUid);
+ if (!filtering || (flags&DUMP_UNPLUGGED_ONLY) != 0) {
+ pw.println("Statistics since last unplugged:");
+ dumpLocked(context, pw, "", STATS_SINCE_UNPLUGGED, reqUid);
+ }
}
@SuppressWarnings("unused")
- public void dumpCheckinLocked(
- PrintWriter pw, List<ApplicationInfo> apps, boolean isUnpluggedOnly,
- boolean includeHistory) {
+ public void dumpCheckinLocked(Context context, PrintWriter pw,
+ List<ApplicationInfo> apps, int flags, long histStart) {
prepareForDumpLocked();
long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
- if (includeHistory) {
+ final boolean filtering =
+ (flags&(DUMP_HISTORY_ONLY|DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY)) != 0;
+
+ if ((flags&DUMP_INCLUDE_HISTORY) != 0 || (flags&DUMP_HISTORY_ONLY) != 0) {
final HistoryItem rec = new HistoryItem();
if (startIteratingHistoryLocked()) {
- HistoryPrinter hprinter = new HistoryPrinter();
- while (getNextHistoryLocked(rec)) {
- pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
- pw.print(0); pw.print(',');
- pw.print(HISTORY_DATA); pw.print(',');
- hprinter.printNextItemCheckin(pw, rec, now);
- pw.println();
+ try {
+ for (int i=0; i<getHistoryStringPoolSize(); i++) {
+ pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
+ pw.print(HISTORY_STRING_POOL); pw.print(',');
+ pw.print(i);
+ pw.print(',');
+ pw.print(getHistoryTagPoolString(i));
+ pw.print(',');
+ pw.print(getHistoryTagPoolUid(i));
+ pw.println();
+ }
+ HistoryPrinter hprinter = new HistoryPrinter();
+ long lastTime = -1;
+ while (getNextHistoryLocked(rec)) {
+ lastTime = rec.time;
+ if (rec.time >= histStart) {
+ pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
+ pw.print(HISTORY_DATA); pw.print(',');
+ hprinter.printNextItem(pw, rec, histStart >= 0 ? -1 : now, true);
+ }
+ }
+ if (histStart >= 0) {
+ pw.print("NEXT: "); pw.println(lastTime+1);
+ }
+ } finally {
+ finishIteratingHistoryLocked();
}
- finishIteratingHistoryLocked();
}
}
+ if (filtering && (flags&(DUMP_UNPLUGGED_ONLY|DUMP_CHARGED_ONLY)) == 0) {
+ return;
+ }
+
if (apps != null) {
SparseArray<ArrayList<String>> uids = new SparseArray<ArrayList<String>>();
for (int i=0; i<apps.size(); i++) {
@@ -2441,12 +3186,11 @@ public abstract class BatteryStats implements Parcelable {
}
}
}
- if (isUnpluggedOnly) {
- dumpCheckinLocked(pw, STATS_SINCE_UNPLUGGED, -1);
+ if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
+ dumpCheckinLocked(context, pw, STATS_SINCE_CHARGED, -1);
}
- else {
- dumpCheckinLocked(pw, STATS_SINCE_CHARGED, -1);
- dumpCheckinLocked(pw, STATS_SINCE_UNPLUGGED, -1);
+ if (!filtering || (flags&DUMP_UNPLUGGED_ONLY) != 0) {
+ dumpCheckinLocked(context, pw, STATS_SINCE_UNPLUGGED, -1);
}
}
}
diff --git a/core/java/android/os/Broadcaster.java b/core/java/android/os/Broadcaster.java
index 96dc61a..70dcdd8 100644
--- a/core/java/android/os/Broadcaster.java
+++ b/core/java/android/os/Broadcaster.java
@@ -171,10 +171,10 @@ public class Broadcaster
public void broadcast(Message msg)
{
synchronized (this) {
- if (mReg == null) {
- return;
- }
-
+ if (mReg == null) {
+ return;
+ }
+
int senderWhat = msg.what;
Registration start = mReg;
Registration r = start;
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/Debug.java b/core/java/android/os/Debug.java
index 2de1204..18730b6 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/Environment.java b/core/java/android/os/Environment.java
index b5413db..54e2c0b 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -16,14 +16,13 @@
package android.os;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.os.storage.IMountService;
-import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.text.TextUtils;
import android.util.Log;
-import com.android.internal.annotations.GuardedBy;
import com.google.android.collect.Lists;
import java.io.File;
@@ -66,33 +65,6 @@ public class Environment {
private static UserEnvironment sCurrentUser;
private static boolean sUserRequired;
- private static final Object sLock = new Object();
-
- @GuardedBy("sLock")
- private static volatile StorageVolume sPrimaryVolume;
-
- private static StorageVolume getPrimaryVolume() {
- if (SystemProperties.getBoolean("config.disable_storage", false)) {
- return null;
- }
-
- if (sPrimaryVolume == null) {
- synchronized (sLock) {
- if (sPrimaryVolume == null) {
- try {
- IMountService mountService = IMountService.Stub.asInterface(ServiceManager
- .getService("mount"));
- final StorageVolume[] volumes = mountService.getVolumeList();
- sPrimaryVolume = StorageManager.getPrimaryVolume(volumes);
- } catch (Exception e) {
- Log.e(TAG, "couldn't talk to MountService", e);
- }
- }
- }
- }
- return sPrimaryVolume;
- }
-
static {
initForCurrentUser();
}
@@ -101,10 +73,6 @@ public class Environment {
public static void initForCurrentUser() {
final int userId = UserHandle.myUserId();
sCurrentUser = new UserEnvironment(userId);
-
- synchronized (sLock) {
- sPrimaryVolume = null;
- }
}
/** {@hide} */
@@ -603,28 +571,28 @@ public class Environment {
* Unknown storage state, such as when a path isn't backed by known storage
* media.
*
- * @see #getStorageState(File)
+ * @see #getExternalStorageState(File)
*/
public static final String MEDIA_UNKNOWN = "unknown";
/**
* Storage state if the media is not present.
*
- * @see #getStorageState(File)
+ * @see #getExternalStorageState(File)
*/
public static final String MEDIA_REMOVED = "removed";
/**
* Storage state if the media is present but not mounted.
*
- * @see #getStorageState(File)
+ * @see #getExternalStorageState(File)
*/
public static final String MEDIA_UNMOUNTED = "unmounted";
/**
* Storage state if the media is present and being disk-checked.
*
- * @see #getStorageState(File)
+ * @see #getExternalStorageState(File)
*/
public static final String MEDIA_CHECKING = "checking";
@@ -632,7 +600,7 @@ public class Environment {
* Storage state if the media is present but is blank or is using an
* unsupported filesystem.
*
- * @see #getStorageState(File)
+ * @see #getExternalStorageState(File)
*/
public static final String MEDIA_NOFS = "nofs";
@@ -640,7 +608,7 @@ public class Environment {
* Storage state if the media is present and mounted at its mount point with
* read/write access.
*
- * @see #getStorageState(File)
+ * @see #getExternalStorageState(File)
*/
public static final String MEDIA_MOUNTED = "mounted";
@@ -648,7 +616,7 @@ public class Environment {
* Storage state if the media is present and mounted at its mount point with
* read-only access.
*
- * @see #getStorageState(File)
+ * @see #getExternalStorageState(File)
*/
public static final String MEDIA_MOUNTED_READ_ONLY = "mounted_ro";
@@ -656,14 +624,14 @@ public class Environment {
* Storage state if the media is present not mounted, and shared via USB
* mass storage.
*
- * @see #getStorageState(File)
+ * @see #getExternalStorageState(File)
*/
public static final String MEDIA_SHARED = "shared";
/**
* Storage state if the media was removed before it was unmounted.
*
- * @see #getStorageState(File)
+ * @see #getExternalStorageState(File)
*/
public static final String MEDIA_BAD_REMOVAL = "bad_removal";
@@ -671,7 +639,7 @@ public class Environment {
* Storage state if the media is present but cannot be mounted. Typically
* this happens if the file system on the media is corrupted.
*
- * @see #getStorageState(File)
+ * @see #getExternalStorageState(File)
*/
public static final String MEDIA_UNMOUNTABLE = "unmountable";
@@ -687,7 +655,15 @@ public class Environment {
*/
public static String getExternalStorageState() {
final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
- return getStorageState(externalDir);
+ return getExternalStorageState(externalDir);
+ }
+
+ /**
+ * @deprecated use {@link #getExternalStorageState(File)}
+ */
+ @Deprecated
+ public static String getStorageState(File path) {
+ return getExternalStorageState(path);
}
/**
@@ -700,59 +676,81 @@ public class Environment {
* {@link #MEDIA_MOUNTED_READ_ONLY}, {@link #MEDIA_SHARED},
* {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
*/
- public static String getStorageState(File path) {
- final String rawPath;
- try {
- rawPath = path.getCanonicalPath();
- } catch (IOException e) {
- Log.w(TAG, "Failed to resolve target path: " + e);
- return Environment.MEDIA_UNKNOWN;
- }
-
- try {
+ public static String getExternalStorageState(File path) {
+ final StorageVolume volume = getStorageVolume(path);
+ if (volume != null) {
final IMountService mountService = IMountService.Stub.asInterface(
ServiceManager.getService("mount"));
- final StorageVolume[] volumes = mountService.getVolumeList();
- for (StorageVolume volume : volumes) {
- if (rawPath.startsWith(volume.getPath())) {
- return mountService.getVolumeState(volume.getPath());
- }
+ try {
+ return mountService.getVolumeState(volume.getPath());
+ } catch (RemoteException e) {
}
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to find external storage state: " + e);
}
+
return Environment.MEDIA_UNKNOWN;
}
/**
* Returns whether the primary "external" storage device is removable.
- * If true is returned, this device is for example an SD card that the
- * user can remove. If false is returned, the storage is built into
- * the device and can not be physically removed.
*
- * <p>See {@link #getExternalStorageDirectory()} for more information.
+ * @return true if the storage device can be removed (such as an SD card),
+ * or false if the storage device is built in and cannot be
+ * physically removed.
*/
public static boolean isExternalStorageRemovable() {
- final StorageVolume primary = getPrimaryVolume();
- return (primary != null && primary.isRemovable());
+ if (isStorageDisabled()) return false;
+ final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+ return isExternalStorageRemovable(externalDir);
}
/**
- * Returns whether the device has an external storage device which is
- * emulated. If true, the device does not have real external storage, and the directory
- * returned by {@link #getExternalStorageDirectory()} will be allocated using a portion of
- * the internal storage system.
+ * Returns whether the storage device that provides the given path is
+ * removable.
*
- * <p>Certain system services, such as the package manager, use this
- * to determine where to install an application.
+ * @return true if the storage device can be removed (such as an SD card),
+ * or false if the storage device is built in and cannot be
+ * physically removed.
+ * @throws IllegalArgumentException if the path is not a valid storage
+ * device.
+ */
+ public static boolean isExternalStorageRemovable(File path) {
+ final StorageVolume volume = getStorageVolume(path);
+ if (volume != null) {
+ return volume.isRemovable();
+ } else {
+ throw new IllegalArgumentException("Failed to find storage device at " + path);
+ }
+ }
+
+ /**
+ * Returns whether the primary "external" storage device is emulated. If
+ * true, data stored on this device will be stored on a portion of the
+ * internal storage system.
*
- * <p>Emulated external storage may also be encrypted - see
- * {@link android.app.admin.DevicePolicyManager#setStorageEncryption(
- * android.content.ComponentName, boolean)} for additional details.
+ * @see DevicePolicyManager#setStorageEncryption(android.content.ComponentName,
+ * boolean)
*/
public static boolean isExternalStorageEmulated() {
- final StorageVolume primary = getPrimaryVolume();
- return (primary != null && primary.isEmulated());
+ if (isStorageDisabled()) return false;
+ final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+ return isExternalStorageEmulated(externalDir);
+ }
+
+ /**
+ * Returns whether the storage device that provides the given path is
+ * emulated. If true, data stored on this device will be stored on a portion
+ * of the internal storage system.
+ *
+ * @throws IllegalArgumentException if the path is not a valid storage
+ * device.
+ */
+ public static boolean isExternalStorageEmulated(File path) {
+ final StorageVolume volume = getStorageVolume(path);
+ if (volume != null) {
+ return volume.isEmulated();
+ } else {
+ throw new IllegalArgumentException("Failed to find storage device at " + path);
+ }
}
static File getDirectory(String variableName, String defaultPath) {
@@ -815,6 +813,32 @@ public class Environment {
return cur;
}
+ private static boolean isStorageDisabled() {
+ return SystemProperties.getBoolean("config.disable_storage", false);
+ }
+
+ private static StorageVolume getStorageVolume(File path) {
+ try {
+ path = path.getCanonicalFile();
+ } catch (IOException e) {
+ return null;
+ }
+
+ try {
+ final IMountService mountService = IMountService.Stub.asInterface(
+ ServiceManager.getService("mount"));
+ final StorageVolume[] volumes = mountService.getVolumeList();
+ for (StorageVolume volume : volumes) {
+ if (FileUtils.contains(volume.getPathFile(), path)) {
+ return volume;
+ }
+ }
+ } catch (RemoteException e) {
+ }
+
+ return null;
+ }
+
/**
* If the given path exists on emulated external storage, return the
* translated backing path hosted on internal storage. This bypasses any
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..dc18dee 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;
@@ -328,14 +326,15 @@ public class FileUtils {
*
* @param minCount Always keep at least this many files.
* @param minAge Always keep files younger than this age.
+ * @return if any files were deleted.
*/
- public static void deleteOlderFiles(File dir, int minCount, long minAge) {
+ public static boolean deleteOlderFiles(File dir, int minCount, long minAge) {
if (minCount < 0 || minAge < 0) {
throw new IllegalArgumentException("Constraints must be positive or 0");
}
final File[] files = dir.listFiles();
- if (files == null) return;
+ if (files == null) return false;
// Sort with newest files first
Arrays.sort(files, new Comparator<File>() {
@@ -346,15 +345,41 @@ public class FileUtils {
});
// Keep at least minCount files
+ boolean deleted = false;
for (int i = minCount; i < files.length; i++) {
final File file = files[i];
// Keep files newer than minAge
final long age = System.currentTimeMillis() - file.lastModified();
if (age > minAge) {
- Log.d(TAG, "Deleting old file " + file);
- file.delete();
+ if (file.delete()) {
+ Log.d(TAG, "Deleted old file " + file);
+ deleted = true;
+ }
}
}
+ return deleted;
+ }
+
+ /**
+ * Test if a file lives under the given directory, either as a direct child
+ * or a distant grandchild.
+ * <p>
+ * Both files <em>must</em> have been resolved using
+ * {@link File#getCanonicalFile()} to avoid symlink or path traversal
+ * attacks.
+ */
+ public static boolean contains(File dir, File file) {
+ String dirPath = dir.getPath();
+ String filePath = file.getPath();
+
+ if (dirPath.equals(filePath)) {
+ return true;
+ }
+
+ if (!dirPath.endsWith("/")) {
+ dirPath += "/";
+ }
+ return filePath.startsWith(dirPath);
}
}
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index e6886c4..44367f3 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -330,6 +330,7 @@ public class Handler {
* Causes the Runnable r to be added to the message queue, to be run
* at a specific time given by <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
+ * Time spent in deep sleep will add an additional delay to execution.
* The runnable will be run on the thread to which this handler is attached.
*
* @param r The Runnable that will be executed.
@@ -352,6 +353,7 @@ public class Handler {
* Causes the Runnable r to be added to the message queue, to be run
* at a specific time given by <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
+ * Time spent in deep sleep will add an additional delay to execution.
* The runnable will be run on the thread to which this handler is attached.
*
* @param r The Runnable that will be executed.
@@ -377,6 +379,8 @@ public class Handler {
* after the specified amount of time elapses.
* The runnable will be run on the thread to which this handler
* is attached.
+ * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
+ * Time spent in deep sleep will add an additional delay to execution.
*
* @param r The Runnable that will be executed.
* @param delayMillis The delay (in milliseconds) until the Runnable
@@ -570,6 +574,7 @@ public class Handler {
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
+ * Time spent in deep sleep will add an additional delay to execution.
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
*
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/INetworkActivityListener.aidl b/core/java/android/os/INetworkActivityListener.aidl
new file mode 100644
index 0000000..24e6e55
--- /dev/null
+++ b/core/java/android/os/INetworkActivityListener.aidl
@@ -0,0 +1,24 @@
+/* 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;
+
+/**
+ * @hide
+ */
+oneway interface INetworkActivityListener
+{
+ void onNetworkActive();
+}
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 21b8ae5..e6a4c5a 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -23,6 +23,7 @@ import android.net.LinkAddress;
import android.net.NetworkStats;
import android.net.RouteInfo;
import android.net.wifi.WifiConfiguration;
+import android.os.INetworkActivityListener;
/**
* @hide
@@ -306,14 +307,12 @@ interface INetworkManagementService
* reference-counting if an idletimer already exists for given
* {@code iface}.
*
- * {@code label} usually represents the network type of {@code iface}.
- * Caller should ensure that {@code label} for an {@code iface} remains the
- * same for all calls to addIdleTimer.
+ * {@code type} is the type of the interface, such as TYPE_MOBILE.
*
* Every {@code addIdleTimer} should be paired with a
* {@link removeIdleTimer} to cleanup when the network disconnects.
*/
- void addIdleTimer(String iface, int timeout, String label);
+ void addIdleTimer(String iface, int timeout, int type);
/**
* Removes idletimer for an interface.
@@ -441,4 +440,19 @@ interface INetworkManagementService
* Determine whether the clatd (464xlat) service has been started
*/
boolean isClatdStarted();
+
+ /**
+ * Start listening for mobile activity state changes.
+ */
+ void registerNetworkActivityListener(INetworkActivityListener listener);
+
+ /**
+ * Stop listening for mobile activity state changes.
+ */
+ void unregisterNetworkActivityListener(INetworkActivityListener listener);
+
+ /**
+ * Check whether the mobile radio is currently active.
+ */
+ boolean isNetworkActive();
}
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 56176a4..069285a 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -25,8 +25,10 @@ interface IPowerManager
{
// WARNING: The first four methods must remain the first three methods because their
// transaction numbers must not change unless IPowerManager.cpp is also updated.
- void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, in WorkSource ws);
- void acquireWakeLockWithUid(IBinder lock, int flags, String tag, String packageName, int uidtoblame);
+ void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, in WorkSource ws,
+ String historyTag);
+ void acquireWakeLockWithUid(IBinder lock, int flags, String tag, String packageName,
+ int uidtoblame);
void releaseWakeLock(IBinder lock, int flags);
void updateWakeLockUids(IBinder lock, in int[] uids);
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 3c9d0d9..6e6c06d 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -28,11 +28,13 @@ import android.graphics.Bitmap;
*/
interface IUserManager {
UserInfo createUser(in String name, int flags);
+ UserInfo createRelatedUser(in String name, int flags, int relatedUserId);
boolean removeUser(int userHandle);
void setUserName(int userHandle, String name);
void setUserIcon(int userHandle, in Bitmap icon);
Bitmap getUserIcon(int userHandle);
List<UserInfo> getUsers(boolean excludeDying);
+ List<UserInfo> getRelatedUsers(int userHandle);
UserInfo getUserInfo(int userHandle);
boolean isRestricted();
void setGuestEnabled(boolean enable);
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 21e9f6b..6d7c9cf 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
@@ -150,7 +149,7 @@ public final class Looper {
+ msg.callback + " what=" + msg.what);
}
- msg.recycle();
+ msg.recycleUnchecked();
}
}
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 51203a4..d30bbc1 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -71,7 +71,14 @@ public final class Message implements Parcelable {
*/
public Messenger replyTo;
- /** If set message is in use */
+ /** If set message is in use.
+ * This flag is set when the message is enqueued and remains set while it
+ * is delivered and afterwards when it is recycled. The flag is only cleared
+ * when a new message is created or obtained since that is the only time that
+ * applications are allowed to modify the contents of the message.
+ *
+ * It is an error to attempt to enqueue or recycle a message that is already in use.
+ */
/*package*/ static final int FLAG_IN_USE = 1 << 0;
/** If set message is asynchronous */
@@ -86,9 +93,9 @@ public final class Message implements Parcelable {
/*package*/ Bundle data;
- /*package*/ Handler target;
+ /*package*/ Handler target;
- /*package*/ Runnable callback;
+ /*package*/ Runnable callback;
// sometimes we store linked lists of these things
/*package*/ Message next;
@@ -109,6 +116,7 @@ public final class Message implements Parcelable {
Message m = sPool;
sPool = m.next;
m.next = null;
+ m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
@@ -241,12 +249,38 @@ public final class Message implements Parcelable {
}
/**
- * Return a Message instance to the global pool. You MUST NOT touch
- * the Message after calling this function -- it has effectively been
- * freed.
+ * Return a Message instance to the global pool.
+ * <p>
+ * You MUST NOT touch the Message after calling this function because it has
+ * effectively been freed. It is an error to recycle a message that is currently
+ * enqueued or that is in the process of being delivered to a Handler.
+ * </p>
*/
public void recycle() {
- clearForRecycle();
+ if (isInUse()) {
+ throw new IllegalStateException("This message cannot be recycled because it "
+ + "is still in use.");
+ }
+ recycleUnchecked();
+ }
+
+ /**
+ * Recycles a Message that may be in-use.
+ * Used internally by the MessageQueue and Looper when disposing of queued Messages.
+ */
+ void recycleUnchecked() {
+ // Mark the message as in use while it remains in the recycled object pool.
+ // Clear out all other details.
+ flags = FLAG_IN_USE;
+ what = 0;
+ arg1 = 0;
+ arg2 = 0;
+ obj = null;
+ replyTo = null;
+ when = 0;
+ target = null;
+ callback = null;
+ data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
@@ -402,19 +436,6 @@ public final class Message implements Parcelable {
}
}
- /*package*/ void clearForRecycle() {
- flags = 0;
- what = 0;
- arg1 = 0;
- arg2 = 0;
- obj = null;
- replyTo = null;
- when = 0;
- target = null;
- callback = null;
- data = null;
- }
-
/*package*/ boolean isInUse() {
return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
}
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 75f9813..01a23ce 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -16,7 +16,6 @@
package android.os;
-import android.util.AndroidRuntimeException;
import android.util.Log;
import android.util.Printer;
@@ -126,6 +125,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 long ptr = mPtr;
+ if (ptr == 0) {
+ return null;
+ }
+
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
@@ -133,9 +140,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.
@@ -163,7 +168,6 @@ public final class MessageQueue {
}
msg.next = null;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
- msg.markInUse();
return msg;
}
} else {
@@ -227,7 +231,7 @@ public final class MessageQueue {
void quit(boolean safe) {
if (!mQuitAllowed) {
- throw new RuntimeException("Main thread not allowed to quit.");
+ throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
@@ -253,6 +257,7 @@ public final class MessageQueue {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
+ msg.markInUse();
msg.when = when;
msg.arg1 = token;
@@ -297,7 +302,7 @@ public final class MessageQueue {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
- p.recycle();
+ p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
@@ -308,21 +313,23 @@ public final class MessageQueue {
}
boolean enqueueMessage(Message msg, long when) {
- if (msg.isInUse()) {
- throw new AndroidRuntimeException(msg + " This message is already in use.");
- }
if (msg.target == null) {
- throw new AndroidRuntimeException("Message must have a target.");
+ throw new IllegalArgumentException("Message must have a target.");
+ }
+ if (msg.isInUse()) {
+ throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
- RuntimeException e = new RuntimeException(
+ IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
+ msg.recycle();
return false;
}
+ msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
@@ -418,7 +425,7 @@ public final class MessageQueue {
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
- p.recycle();
+ p.recycleUnchecked();
p = n;
}
@@ -429,7 +436,7 @@ public final class MessageQueue {
if (n.target == h && n.what == what
&& (object == null || n.obj == object)) {
Message nn = n.next;
- n.recycle();
+ n.recycleUnchecked();
p.next = nn;
continue;
}
@@ -452,7 +459,7 @@ public final class MessageQueue {
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
- p.recycle();
+ p.recycleUnchecked();
p = n;
}
@@ -463,7 +470,7 @@ public final class MessageQueue {
if (n.target == h && n.callback == r
&& (object == null || n.obj == object)) {
Message nn = n.next;
- n.recycle();
+ n.recycleUnchecked();
p.next = nn;
continue;
}
@@ -486,7 +493,7 @@ public final class MessageQueue {
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
- p.recycle();
+ p.recycleUnchecked();
p = n;
}
@@ -496,7 +503,7 @@ public final class MessageQueue {
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
- n.recycle();
+ n.recycleUnchecked();
p.next = nn;
continue;
}
@@ -510,7 +517,7 @@ public final class MessageQueue {
Message p = mMessages;
while (p != null) {
Message n = p.next;
- p.recycle();
+ p.recycleUnchecked();
p = n;
}
mMessages = null;
@@ -538,7 +545,7 @@ public final class MessageQueue {
do {
p = n;
n = p.next;
- p.recycle();
+ p.recycleUnchecked();
} while (n != null);
}
}
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/Parcel.java b/core/java/android/os/Parcel.java
index 7425f67..8e0ff08 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -29,6 +29,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -2067,7 +2068,7 @@ public final class Parcel {
return readByte();
case VAL_SERIALIZABLE:
- return readSerializable();
+ return readSerializable(loader);
case VAL_PARCELABLEARRAY:
return readParcelableArray(loader);
@@ -2204,6 +2205,10 @@ public final class Parcel {
* wasn't found in the parcel.
*/
public final Serializable readSerializable() {
+ return readSerializable(null);
+ }
+
+ private final Serializable readSerializable(final ClassLoader loader) {
String name = readString();
if (name == null) {
// For some reason we were unable to read the name of the Serializable (either there
@@ -2215,14 +2220,27 @@ public final class Parcel {
byte[] serializedData = createByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(serializedData);
try {
- ObjectInputStream ois = new ObjectInputStream(bais);
+ ObjectInputStream ois = new ObjectInputStream(bais) {
+ @Override
+ protected Class<?> resolveClass(ObjectStreamClass osClass)
+ throws IOException, ClassNotFoundException {
+ // try the custom classloader if provided
+ if (loader != null) {
+ Class<?> c = Class.forName(osClass.getName(), false, loader);
+ if (c != null) {
+ return c;
+ }
+ }
+ return super.resolveClass(osClass);
+ }
+ };
return (Serializable) ois.readObject();
} catch (IOException ioe) {
throw new RuntimeException("Parcelable encountered " +
"IOException reading a Serializable object (name = " + name +
")", ioe);
} catch (ClassNotFoundException cnfe) {
- throw new RuntimeException("Parcelable encountered" +
+ throw new RuntimeException("Parcelable encountered " +
"ClassNotFoundException reading a Serializable object (name = "
+ name + ")", cnfe);
}
@@ -2234,6 +2252,7 @@ public final class Parcel {
private static final HashMap<ClassLoader,HashMap<String,Parcelable.Creator>>
mCreators = new HashMap<ClassLoader,HashMap<String,Parcelable.Creator>>();
+ /** @hide for internal use only. */
static protected final Parcel obtain(int obj) {
throw new UnsupportedOperationException();
}
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/PowerManager.java b/core/java/android/os/PowerManager.java
index 86ef479..74ca3bb 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -235,6 +235,13 @@ public final class PowerManager {
public static final int ON_AFTER_RELEASE = 0x20000000;
/**
+ * Wake lock flag: This wake lock is not important for logging events. If a later
+ * wake lock is acquired that is important, it will be considered the one to log.
+ * @hide
+ */
+ public static final int UNIMPORTANT_FOR_LOGGING = 0x40000000;
+
+ /**
* Flag for {@link WakeLock#release release(int)} to defer releasing a
* {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} wake lock until the proximity sensor returns
* a negative value.
@@ -302,6 +309,18 @@ public final class PowerManager {
*/
public static final int GO_TO_SLEEP_REASON_TIMEOUT = 2;
+ /**
+ * The value to pass as the 'reason' argument to reboot() to
+ * reboot into recovery mode (for applying system updates, doing
+ * factory resets, etc.).
+ * <p>
+ * Requires the {@link android.Manifest.permission#RECOVERY}
+ * permission (in addition to
+ * {@link android.Manifest.permission#REBOOT}).
+ * </p>
+ */
+ public static final String REBOOT_RECOVERY = "recovery";
+
final Context mContext;
final IPowerManager mService;
final Handler mHandler;
@@ -635,14 +654,15 @@ public final class PowerManager {
* </p>
*/
public final class WakeLock {
- private final int mFlags;
- private final String mTag;
+ private int mFlags;
+ private String mTag;
private final String mPackageName;
private final IBinder mToken;
private int mCount;
private boolean mRefCounted = true;
private boolean mHeld;
private WorkSource mWorkSource;
+ private String mHistoryTag;
private final Runnable mReleaser = new Runnable() {
public void run() {
@@ -729,7 +749,8 @@ public final class PowerManager {
// been explicitly released by the keyguard.
mHandler.removeCallbacks(mReleaser);
try {
- mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource);
+ mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
+ mHistoryTag);
} catch (RemoteException e) {
}
mHeld = true;
@@ -830,6 +851,22 @@ public final class PowerManager {
}
}
+ /** @hide */
+ public void setTag(String tag) {
+ mTag = tag;
+ }
+
+ /** @hide */
+ public void setHistoryTag(String tag) {
+ mHistoryTag = tag;
+ }
+
+ /** @hide */
+ public void setUnimportantForLogging(boolean state) {
+ if (state) mFlags |= UNIMPORTANT_FOR_LOGGING;
+ else mFlags &= ~UNIMPORTANT_FOR_LOGGING;
+ }
+
@Override
public String toString() {
synchronized (mToken) {
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/StrictMode.java b/core/java/android/os/StrictMode.java
index d794ca6..ea71ad8 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -1449,7 +1449,11 @@ public final class StrictMode {
if (policy.classInstanceLimit.size() == 0) {
return;
}
- Runtime.getRuntime().gc();
+
+ System.gc();
+ System.runFinalization();
+ System.gc();
+
// Note: classInstanceLimit is immutable, so this is lock-free
for (Map.Entry<Class, Integer> entry : policy.classInstanceLimit.entrySet()) {
Class klass = entry.getKey();
@@ -2005,7 +2009,10 @@ public final class StrictMode {
// noticeably less responsive during orientation changes when activities are
// being restarted. Granted, it is only a problem when StrictMode is enabled
// but it is annoying.
- Runtime.getRuntime().gc();
+
+ System.gc();
+ System.runFinalization();
+ System.gc();
long instances = VMDebug.countInstancesOfClass(klass, false);
if (instances > limit) {
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..1ec5cd5 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;
@@ -165,11 +164,13 @@ public class UserManager {
/**
* Returns whether the system supports multiple users.
- * @return true if multiple users can be created, false if it is a single user device.
+ * @return true if multiple users can be created by user, false if it is a single user device.
* @hide
*/
public static boolean supportsMultipleUsers() {
- return getMaxSupportedUsers() > 1;
+ return getMaxSupportedUsers() > 1
+ && SystemProperties.getBoolean("fw.show_multiuserui",
+ Resources.getSystem().getBoolean(R.bool.config_enableMultiUserUI));
}
/**
@@ -409,6 +410,27 @@ public class UserManager {
}
/**
+ * Creates a user with the specified name and options.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ *
+ * @param name the user's name
+ * @param flags flags that identify the type of user and other properties.
+ * @see UserInfo
+ * @param relatedUserId new user will be related to this user id.
+ *
+ * @return the UserInfo object for the created user, or null if the user could not be created.
+ * @hide
+ */
+ public UserInfo createRelatedUser(String name, int flags, int relatedUserId) {
+ try {
+ return mService.createRelatedUser(name, flags, relatedUserId);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not create a user", re);
+ return null;
+ }
+ }
+
+ /**
* Return the number of users currently created on the device.
*/
public int getUserCount() {
@@ -432,6 +454,22 @@ public class UserManager {
}
/**
+ * Returns information for all users related to userId
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * @param userHandle users related to this user id will be returned.
+ * @return the list of related users.
+ * @hide
+ */
+ public List<UserInfo> getRelatedUsers(int userHandle) {
+ try {
+ return mService.getRelatedUsers(userHandle);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get user list", re);
+ return null;
+ }
+ }
+
+ /**
* Returns information for all users on this device.
* Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
* @param excludeDying specify if the list should exclude users being removed.
@@ -565,6 +603,26 @@ public class UserManager {
}
/**
+ * Returns true if the user switcher should be shown, this will be if there
+ * are multiple users that aren't managed profiles.
+ * @hide
+ * @return true if user switcher should be shown.
+ */
+ public boolean isUserSwitcherEnabled() {
+ List<UserInfo> users = getUsers(true);
+ if (users == null) {
+ return false;
+ }
+ int switchableUserCount = 0;
+ for (UserInfo user : users) {
+ if (user.supportsSwitchTo()) {
+ ++switchableUserCount;
+ }
+ }
+ return switchableUserCount > 1;
+ }
+
+ /**
* Returns a serial number on this device for a given userHandle. User handles can be recycled
* when deleting and creating users, but serial numbers are not reused until the device is wiped.
* @param userHandle
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 51ba2f6..b97734e 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -642,12 +642,13 @@ public interface IMountService extends IInterface {
return _result;
}
- public int changeEncryptionPassword(String password) throws RemoteException {
+ public int changeEncryptionPassword(int type, String password) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeInt(type);
_data.writeString(password);
mRemote.transact(Stub.TRANSACTION_changeEncryptionPassword, _data, _reply, 0);
_reply.readException();
@@ -677,6 +678,22 @@ public interface IMountService extends IInterface {
return _result;
}
+ public int getPasswordType() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_getPasswordType, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
public StorageVolume[] getVolumeList() throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
@@ -829,6 +846,8 @@ public interface IMountService extends IInterface {
static final int TRANSACTION_mkdirs = IBinder.FIRST_CALL_TRANSACTION + 34;
+ static final int TRANSACTION_getPasswordType = IBinder.FIRST_CALL_TRANSACTION + 36;
+
/**
* Cast an IBinder object into an IMountService interface, generating a
* proxy if needed.
@@ -1130,8 +1149,9 @@ public interface IMountService extends IInterface {
}
case TRANSACTION_changeEncryptionPassword: {
data.enforceInterface(DESCRIPTOR);
+ int type = data.readInt();
String password = data.readString();
- int result = changeEncryptionPassword(password);
+ int result = changeEncryptionPassword(type, password);
reply.writeNoException();
reply.writeInt(result);
return true;
@@ -1181,6 +1201,13 @@ public interface IMountService extends IInterface {
reply.writeInt(result);
return true;
}
+ case TRANSACTION_getPasswordType: {
+ data.enforceInterface(DESCRIPTOR);
+ int result = getPasswordType();
+ reply.writeNoException();
+ reply.writeInt(result);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
}
@@ -1375,7 +1402,8 @@ public interface IMountService extends IInterface {
/**
* Changes the encryption password.
*/
- public int changeEncryptionPassword(String password) throws RemoteException;
+ public int changeEncryptionPassword(int type, String password)
+ throws RemoteException;
/**
* Verify the encryption password against the stored volume. This method
@@ -1412,4 +1440,10 @@ public interface IMountService extends IInterface {
* external storage data or OBB directory belonging to calling app.
*/
public int mkdirs(String callingPkg, String path) throws RemoteException;
+
+ /**
+ * Determines the type of the encryption password
+ * @return PasswordType
+ */
+ public int getPasswordType() throws RemoteException;
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index f5e728d..4963991 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -58,6 +58,24 @@ import java.util.concurrent.atomic.AtomicInteger;
* argument of {@link android.content.Context#STORAGE_SERVICE}.
*/
public class StorageManager {
+
+ /// Consts to match the password types in cryptfs.h
+ /** Master key is encrypted with a password.
+ */
+ public static final int CRYPT_TYPE_PASSWORD = 0;
+
+ /** Master key is encrypted with the default password.
+ */
+ public static final int CRYPT_TYPE_DEFAULT = 1;
+
+ /** Master key is encrypted with a pattern.
+ */
+ public static final int CRYPT_TYPE_PATTERN = 2;
+
+ /** Master key is encrypted with a pin.
+ */
+ public static final int CRYPT_TYPE_PIN = 3;
+
private static final String TAG = "StorageManager";
private final ContentResolver mResolver;
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..b65eac7 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.
*
@@ -161,7 +169,7 @@ public abstract class DialogPreference extends Preference implements
* @param dialogIconRes The icon, as a resource ID.
*/
public void setDialogIcon(int dialogIconRes) {
- mDialogIcon = getContext().getResources().getDrawable(dialogIconRes);
+ mDialogIcon = getContext().getDrawable(dialogIconRes);
}
/**
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/GenericInflater.java b/core/java/android/preference/GenericInflater.java
index 3003290..7de7d1c 100644
--- a/core/java/android/preference/GenericInflater.java
+++ b/core/java/android/preference/GenericInflater.java
@@ -191,7 +191,7 @@ abstract class GenericInflater<T, P extends GenericInflater.Parent> {
public void setFactory(Factory<T> factory) {
if (mFactorySet) {
throw new IllegalStateException("" +
- "A factory has already been set on this inflater");
+ "A factory has already been set on this inflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
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..144c909 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
@@ -534,7 +561,7 @@ public class Preference implements Comparable<Preference> {
if (imageView != null) {
if (mIconResId != 0 || mIcon != null) {
if (mIcon == null) {
- mIcon = getContext().getResources().getDrawable(mIconResId);
+ mIcon = getContext().getDrawable(mIconResId);
}
if (mIcon != null) {
imageView.setImageDrawable(mIcon);
@@ -667,7 +694,7 @@ public class Preference implements Comparable<Preference> {
*/
public void setIcon(int iconResId) {
mIconResId = iconResId;
- setIcon(mContext.getResources().getDrawable(iconResId));
+ setIcon(mContext.getDrawable(iconResId));
}
/**
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 2ab5a91..0418049 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -33,7 +33,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.TypedValue;
import android.util.Xml;
import android.view.LayoutInflater;
@@ -794,8 +793,8 @@ public abstract class PreferenceActivity extends ListActivity implements
if ("header".equals(nodeName)) {
Header header = new Header();
- TypedArray sa = getResources().obtainAttributes(attrs,
- com.android.internal.R.styleable.PreferenceHeader);
+ TypedArray sa = obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.PreferenceHeader);
header.id = sa.getResourceId(
com.android.internal.R.styleable.PreferenceHeader_id,
(int)HEADER_ID_UNDEFINED);
@@ -1173,7 +1172,7 @@ public abstract class PreferenceActivity extends ListActivity implements
}
}
- private void switchToHeaderInner(String fragmentName, Bundle args, int direction) {
+ private void switchToHeaderInner(String fragmentName, Bundle args) {
getFragmentManager().popBackStack(BACK_STACK_PREFS,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
if (!isValidFragment(fragmentName)) {
@@ -1196,7 +1195,7 @@ public abstract class PreferenceActivity extends ListActivity implements
*/
public void switchToHeader(String fragmentName, Bundle args) {
setSelectedHeader(null);
- switchToHeaderInner(fragmentName, args, 0);
+ switchToHeaderInner(fragmentName, args);
}
/**
@@ -1215,8 +1214,7 @@ public abstract class PreferenceActivity extends ListActivity implements
if (header.fragment == null) {
throw new IllegalStateException("can't switch to header that has no fragment");
}
- int direction = mHeaders.indexOf(header) - mHeaders.indexOf(mCurHeader);
- switchToHeaderInner(header.fragment, header.fragmentArguments, direction);
+ switchToHeaderInner(header.fragment, header.fragmentArguments);
setSelectedHeader(header);
}
}
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/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java
index 17f88f1..5c8c8e9 100644
--- a/core/java/android/preference/PreferenceManager.java
+++ b/core/java/android/preference/PreferenceManager.java
@@ -800,8 +800,10 @@ public class PreferenceManager {
* Interface definition for a callback to be invoked when a
* {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is
* clicked.
+ *
+ * @hide
*/
- interface OnPreferenceTreeClickListener {
+ public interface OnPreferenceTreeClickListener {
/**
* Called when a preference in the tree rooted at this
* {@link PreferenceScreen} has been clicked.
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/CallLog.java b/core/java/android/provider/CallLog.java
index a6f23a8..3b0d7ff 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -86,10 +86,8 @@ public class CallLog {
public static final String ALLOW_VOICEMAILS_PARAM_KEY = "allow_voicemails";
/**
- * Content uri with {@link #ALLOW_VOICEMAILS_PARAM_KEY} set. This can directly be used to
- * access call log entries that includes voicemail records.
- *
- * @hide
+ * Content uri used to access call log entries, including voicemail records. You must have
+ * the READ_CALL_LOG and WRITE_CALL_LOG permissions to read and write to the call log.
*/
public static final Uri CONTENT_URI_WITH_VOICEMAIL = CONTENT_URI.buildUpon()
.appendQueryParameter(ALLOW_VOICEMAILS_PARAM_KEY, "true")
@@ -124,10 +122,7 @@ public class CallLog {
public static final int OUTGOING_TYPE = 2;
/** Call log type for missed calls. */
public static final int MISSED_TYPE = 3;
- /**
- * Call log type for voicemails.
- * @hide
- */
+ /** Call log type for voicemails. */
public static final int VOICEMAIL_TYPE = 4;
/**
@@ -168,8 +163,6 @@ public class CallLog {
* <P>
* Type: TEXT
* </P>
- *
- * @hide
*/
public static final String COUNTRY_ISO = "countryiso";
@@ -220,7 +213,6 @@ public class CallLog {
/**
* URI of the voicemail entry. Populated only for {@link #VOICEMAIL_TYPE}.
* <P>Type: TEXT</P>
- * @hide
*/
public static final String VOICEMAIL_URI = "voicemail_uri";
@@ -238,51 +230,48 @@ public class CallLog {
* <p>
* The string represents a city, state, or country associated with the number.
* <P>Type: TEXT</P>
- * @hide
*/
public static final String GEOCODED_LOCATION = "geocoded_location";
/**
* The cached URI to look up the contact associated with the phone number, if it exists.
- * This value is not guaranteed to be current, if the contact information
- * associated with this number has changed.
+ * This value may not be current if the contact information associated with this number
+ * has changed.
* <P>Type: TEXT</P>
- * @hide
*/
public static final String CACHED_LOOKUP_URI = "lookup_uri";
/**
* The cached phone number of the contact which matches this entry, if it exists.
- * This value is not guaranteed to be current, if the contact information
- * associated with this number has changed.
+ * This value may not be current if the contact information associated with this number
+ * has changed.
* <P>Type: TEXT</P>
- * @hide
*/
public static final String CACHED_MATCHED_NUMBER = "matched_number";
/**
- * The cached normalized version of the phone number, if it exists.
- * This value is not guaranteed to be current, if the contact information
- * associated with this number has changed.
+ * The cached normalized(E164) version of the phone number, if it exists.
+ * This value may not be current if the contact information associated with this number
+ * has changed.
* <P>Type: TEXT</P>
- * @hide
*/
public static final String CACHED_NORMALIZED_NUMBER = "normalized_number";
/**
* The cached photo id of the picture associated with the phone number, if it exists.
- * This value is not guaranteed to be current, if the contact information
- * associated with this number has changed.
+ * This value may not be current if the contact information associated with this number
+ * has changed.
* <P>Type: INTEGER (long)</P>
- * @hide
*/
public static final String CACHED_PHOTO_ID = "photo_id";
/**
- * The cached formatted phone number.
- * This value is not guaranteed to be present.
+ * The cached phone number, formatted with formatting rules based on the country the
+ * user was in when the call was made or received.
+ * This value is not guaranteed to be present, and may not be current if the contact
+ * information associated with this number
+ * has changed.
* <P>Type: TEXT</P>
- * @hide
*/
public static final String CACHED_FORMATTED_NUMBER = "formatted_number";
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 0863368..11678a6 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -47,9 +47,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>
@@ -167,8 +164,6 @@ public final class ContactsContract {
* {@link Contacts#CONTENT_STREQUENT_FILTER_URI}, which requires the ContactsProvider to
* return only phone-related results. For example, frequently contacted person list should
* include persons contacted via phone (not email, sms, etc.)
- *
- * @hide
*/
public static final String STREQUENT_PHONE_ONLY = "strequent_phone_only";
@@ -193,8 +188,6 @@ public final class ContactsContract {
* {@link CommonDataKinds.Email#CONTENT_URI}, and
* {@link CommonDataKinds.StructuredPostal#CONTENT_URI}.
* This enables a content provider to remove duplicate entries in results.
- *
- * @hide
*/
public static final String REMOVE_DUPLICATE_ENTRIES = "remove_duplicate_entries";
@@ -251,30 +244,21 @@ public final class ContactsContract {
public static final String KEY_AUTHORIZED_URI = "authorized_uri";
}
- /**
- * @hide
- */
public static final class Preferences {
/**
* A key in the {@link android.provider.Settings android.provider.Settings} provider
* that stores the preferred sorting order for contacts (by given name vs. by family name).
- *
- * @hide
*/
public static final String SORT_ORDER = "android.contacts.SORT_ORDER";
/**
* The value for the SORT_ORDER key corresponding to sorting by given name first.
- *
- * @hide
*/
public static final int SORT_ORDER_PRIMARY = 1;
/**
* The value for the SORT_ORDER key corresponding to sorting by family name first.
- *
- * @hide
*/
public static final int SORT_ORDER_ALTERNATIVE = 2;
@@ -282,22 +266,16 @@ public final class ContactsContract {
* A key in the {@link android.provider.Settings android.provider.Settings} provider
* that stores the preferred display order for contacts (given name first vs. family
* name first).
- *
- * @hide
*/
public static final String DISPLAY_ORDER = "android.contacts.DISPLAY_ORDER";
/**
* The value for the DISPLAY_ORDER key corresponding to showing the given name first.
- *
- * @hide
*/
public static final int DISPLAY_ORDER_PRIMARY = 1;
/**
* The value for the DISPLAY_ORDER key corresponding to showing the family name first.
- *
- * @hide
*/
public static final int DISPLAY_ORDER_ALTERNATIVE = 2;
}
@@ -827,10 +805,9 @@ public final class ContactsContract {
public static final String STARRED = "starred";
/**
- * The position at which the contact is pinned. If {@link PinnedPositions.UNPINNED},
+ * The position at which the contact is pinned. If {@link PinnedPositions#UNPINNED},
* the contact is not pinned. Also see {@link PinnedPositions}.
* <P>Type: INTEGER </P>
- * @hide
*/
public static final String PINNED = "pinned";
@@ -924,6 +901,14 @@ public final class ContactsContract {
public static final String PHOTO_THUMBNAIL_URI = "photo_thumb_uri";
/**
+ * Flag that reflects whether the contact exists inside the default directory.
+ * Ie, whether the contact is designed to only be visible outside search.
+ *
+ * @hide
+ */
+ public static final String IN_DEFAULT_DIRECTORY = "in_default_directory";
+
+ /**
* Flag that reflects the {@link Groups#GROUP_VISIBLE} state of any
* {@link CommonDataKinds.GroupMembership} for this contact.
*/
@@ -1470,17 +1455,43 @@ public final class ContactsContract {
* Base {@link Uri} for referencing multiple {@link Contacts} entry,
* created by appending {@link #LOOKUP_KEY} using
* {@link Uri#withAppendedPath(Uri, String)}. The lookup keys have to be
- * encoded and joined with the colon (":") separator. The resulting string
- * has to be encoded again. Provides
- * {@link OpenableColumns} columns when queried, or returns the
+ * joined with the colon (":") separator, and the resulting string encoded.
+ *
+ * Provides {@link OpenableColumns} columns when queried, or returns the
* referenced contact formatted as a vCard when opened through
* {@link ContentResolver#openAssetFileDescriptor(Uri, String)}.
*
- * This is private API because we do not have a well-defined way to
- * specify several entities yet. The format of this Uri might change in the future
- * or the Uri might be completely removed.
+ * <p>
+ * Usage example:
+ * <dl>
+ * <dt>The following code snippet creates a multi-vcard URI that references all the
+ * contacts in a user's database.</dt>
+ * <dd>
*
- * @hide
+ * <pre>
+ * public Uri getAllContactsVcardUri() {
+ * Cursor cursor = getActivity().getContentResolver().query(Contacts.CONTENT_URI,
+ * new String[] {Contacts.LOOKUP_KEY}, null, null, null);
+ * if (cursor == null) {
+ * return null;
+ * }
+ * try {
+ * StringBuilder uriListBuilder = new StringBuilder();
+ * int index = 0;
+ * while (cursor.moveToNext()) {
+ * if (index != 0) uriListBuilder.append(':');
+ * uriListBuilder.append(cursor.getString(0));
+ * index++;
+ * }
+ * return Uri.withAppendedPath(Contacts.CONTENT_MULTI_VCARD_URI,
+ * Uri.encode(uriListBuilder.toString()));
+ * } finally {
+ * cursor.close();
+ * }
+ * }
+ * </pre>
+ *
+ * </p>
*/
public static final Uri CONTENT_MULTI_VCARD_URI = Uri.withAppendedPath(CONTENT_URI,
"as_multi_vcard");
@@ -4794,11 +4805,11 @@ public final class ContactsContract {
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/phone_lookup";
- /**
- * Boolean parameter that is used to look up a SIP address.
- *
- * @hide
- */
+ /**
+ * If this boolean parameter is set to true, then the appended query is treated as a
+ * SIP address and the lookup will be performed against SIP addresses in the user's
+ * contacts.
+ */
public static final String QUERY_PARAMETER_SIP_ADDRESS = "sip";
}
@@ -5307,8 +5318,6 @@ public final class ContactsContract {
/**
* The style used for combining given/middle/family name into a full name.
* See {@link ContactsContract.FullNameStyle}.
- *
- * @hide
*/
public static final String FULL_NAME_STYLE = DATA10;
@@ -6900,8 +6909,6 @@ public final class ContactsContract {
* each column. For example the meaning for {@link Phone}'s type is different than
* {@link SipAddress}'s.
* </p>
- *
- * @hide
*/
public static final class Callable implements DataColumnsWithJoins, CommonColumns {
/**
@@ -7759,7 +7766,6 @@ public final class ContactsContract {
* {@link PinnedPositions#STAR_WHEN_PINNING} to true to force all pinned and unpinned
* contacts to be automatically starred and unstarred.
* </p>
- * @hide
*/
public static final class PinnedPositions {
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index f69cad0..bd576af 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -169,6 +169,14 @@ public final class MediaStore {
*/
public static final String EXTRA_MEDIA_TITLE = "android.intent.extra.title";
/**
+ * The name of the Intent-extra used to define the genre.
+ */
+ public static final String EXTRA_MEDIA_GENRE = "android.intent.extra.genre";
+ /**
+ * The name of the Intent-extra used to define the radio channel.
+ */
+ public static final String EXTRA_MEDIA_RADIO_CHANNEL = "android.intent.extra.radio_channel";
+ /**
* The name of the Intent-extra used to define the search focus. The search focus
* indicates whether the search should be for things related to the artist, album
* or song that is identified by the other extras.
@@ -1389,6 +1397,11 @@ public final class MediaStore {
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio";
/**
+ * The MIME type for an audio track.
+ */
+ public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/audio";
+
+ /**
* The default sort order for this table
*/
public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
@@ -1859,6 +1872,13 @@ public final class MediaStore {
*/
public static final String DEFAULT_SORT_ORDER = ALBUM_KEY;
}
+
+ public static final class Radio {
+ /**
+ * The MIME type for entries in this table.
+ */
+ public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio";
+ }
}
public static final class Video {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 04f3f0a..7777334 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -749,6 +749,14 @@ public final class Settings {
public static final String ACTION_PRINT_SETTINGS =
"android.settings.ACTION_PRINT_SETTINGS";
+ /**
+ * Activity Action: Show Zen Mode configuration settings.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_ZEN_MODE_SETTINGS = "android.settings.ZEN_MODE_SETTINGS";
+
// End of Intent actions for Settings
/**
@@ -3352,21 +3360,29 @@ public final class Settings {
public static final String INSTALL_NON_MARKET_APPS = Global.INSTALL_NON_MARKET_APPS;
/**
- * Comma-separated list of location providers that activities may access.
+ * Comma-separated list of location providers that activities may access. Do not rely on
+ * this value being present in settings.db or on ContentObserver notifications on the
+ * corresponding Uri.
*
- * @deprecated use {@link #LOCATION_MODE}
+ * @deprecated use {@link #LOCATION_MODE} and
+ * {@link LocationManager#MODE_CHANGED_ACTION} (or
+ * {@link LocationManager#PROVIDERS_CHANGED_ACTION})
*/
@Deprecated
public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
/**
* The degree of location access enabled by the user.
- * <p/>
+ * <p>
* When used with {@link #putInt(ContentResolver, String, int)}, must be one of {@link
* #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY}, {@link
* #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}. When used with {@link
* #getInt(ContentResolver, String)}, the caller must gracefully handle additional location
* modes that might be added in the future.
+ * <p>
+ * Note: do not rely on this value being present in settings.db or on ContentObserver
+ * notifications for the corresponding Uri. Use {@link LocationManager#MODE_CHANGED_ACTION}
+ * to receive changes in this value.
*/
public static final String LOCATION_MODE = "location_mode";
@@ -3407,6 +3423,11 @@ public final class Settings {
public static final String LOCK_PATTERN_VISIBLE = "lock_pattern_visible_pattern";
/**
+ * Whether the NFC unlock feature is enabled (0 = false, 1 = true)
+ */
+ public static final String NFC_UNLOCK_ENABLED = "nfc_unlock_enabled";
+
+ /**
* Whether lock pattern will vibrate as user enters (0 = false, 1 =
* true)
*
@@ -3463,6 +3484,14 @@ public final class Settings {
"lock_screen_owner_info_enabled";
/**
+ * When set by a user, allows notifications to be shown atop a securely locked screen
+ * in their full "private" form (same as when the device is unlocked).
+ * @hide
+ */
+ public static final String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS =
+ "lock_screen_allow_private_notifications";
+
+ /**
* The Logging ID (a unique 64-bit value) as a hex string.
* Used as a pseudonymous identifier for logging.
* @deprecated This identifier is poorly initialized and has
@@ -3741,6 +3770,16 @@ public final class Settings {
"accessibility_captioning_edge_color";
/**
+ * Integer property that specifes the window color for captions as a
+ * packed 32-bit color.
+ *
+ * @see android.graphics.Color#argb
+ * @hide
+ */
+ public static final String ACCESSIBILITY_CAPTIONING_WINDOW_COLOR =
+ "accessibility_captioning_window_color";
+
+ /**
* String property that specifies the typeface for captions, one of:
* <ul>
* <li>DEFAULT
@@ -3764,6 +3803,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
*/
@@ -5140,6 +5270,12 @@ public final class Settings {
public static final String SMS_SHORT_CODE_RULE = "sms_short_code_rule";
/**
+ * Used to select TCP's default initial receiver window size in segments - defaults to a build config value
+ * @hide
+ */
+ public static final String TCP_DEFAULT_INIT_RWND = "tcp_default_init_rwnd";
+
+ /**
* Used to disable Tethering on a device - defaults to true
* @hide
*/
@@ -5935,6 +6071,62 @@ public final class Settings {
public static final String LOW_BATTERY_SOUND_TIMEOUT = "low_battery_sound_timeout";
/**
+ * Milliseconds to wait before bouncing Wi-Fi after settings is restored. Note that after
+ * the caller is done with this, they should call {@link ContentResolver#delete(Uri)} to
+ * clean up any value that they may have written.
+ *
+ * @hide
+ */
+ public static final String WIFI_BOUNCE_DELAY_OVERRIDE_MS = "wifi_bounce_delay_override_ms";
+
+ /**
+ * Defines global runtime overrides to window policy.
+ *
+ * See {@link com.android.internal.policy.impl.PolicyControl} for value format.
+ *
+ * @hide
+ */
+ public static final String POLICY_CONTROL = "policy_control";
+
+
+ /**
+ * This preference enables notification display even over a securely
+ * locked screen.
+ * @hide
+ */
+ public static final String LOCK_SCREEN_SHOW_NOTIFICATIONS =
+ "lock_screen_show_notifications";
+
+ /**
+ * Defines global zen mode. One of ZEN_MODE_OFF, ZEN_MODE_LIMITED, ZEN_MODE_FULL.
+ *
+ * @hide
+ */
+ public static final String ZEN_MODE = "zen_mode";
+
+ /** @hide */ public static final int ZEN_MODE_OFF = 0;
+ /** @hide */ public static final int ZEN_MODE_LIMITED = 1;
+ /** @hide */ public static final int ZEN_MODE_FULL = 2;
+
+ /** @hide */ public static String zenModeToString(int mode) {
+ if (mode == ZEN_MODE_OFF) return "ZEN_MODE_OFF";
+ if (mode == ZEN_MODE_LIMITED) return "ZEN_MODE_LIMITED";
+ if (mode == ZEN_MODE_FULL) return "ZEN_MODE_FULL";
+ throw new IllegalArgumentException("Invalid zen mode: " + mode);
+ }
+
+ /**
+ * Defines global heads up toggle. One of HEADS_UP_OFF, HEADS_UP_ON.
+ *
+ * @hide
+ */
+ public static final String HEADS_UP_NOTIFICATIONS_ENABLED =
+ "heads_up_notifications_enabled";
+
+ /** @hide */ public static final int HEADS_UP_OFF = 0;
+ /** @hide */ public static final int HEADS_UP_ON = 1;
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
*
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 1396204..6c491a0 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..a1da49c
--- /dev/null
+++ b/core/java/android/speech/tts/SynthesisRequestV2.java
@@ -0,0 +1,144 @@
+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;
+
+ /**
+ * Constructor for test purposes.
+ */
+ public SynthesisRequestV2(String text, String utteranceId, String voiceName,
+ Bundle voiceParams, Bundle audioParams) {
+ this.mText = text;
+ this.mUtteranceId = utteranceId;
+ this.mVoiceName = voiceName;
+ this.mVoiceParams = voiceParams;
+ this.mAudioParams = audioParams;
+ }
+
+ /**
+ * 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..c6a14f2
--- /dev/null
+++ b/core/java/android/speech/tts/TextToSpeechClient.java
@@ -0,0 +1,1047 @@
+/*
+ * 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;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 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 class UtteranceId {
+ /** Unique identifier */
+ private final int id;
+
+ /** Unique identifier generator */
+ private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
+
+ /**
+ * Create new, unique UtteranceId instance.
+ */
+ public UtteranceId() {
+ id = ID_GENERATOR.getAndIncrement();
+ }
+
+ /**
+ * Returns a unique string associated with an instance of this object.
+ *
+ * This string will be used to identify the synthesis request/utterance inside the
+ * TTS service.
+ */
+ public final String toUniqueString() {
+ return "UID" + id;
+ }
+ }
+
+ /**
+ * 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/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 34274a6..b55cd6a 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -29,6 +29,7 @@ import java.lang.reflect.Array;
*/
public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable,
Appendable, GraphicsOperations {
+ private final static String TAG = "SpannableStringBuilder";
/**
* Create a new SpannableStringBuilder with empty contents
*/
@@ -436,10 +437,26 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
}
// Documentation from interface
- public SpannableStringBuilder replace(final int start, final int end,
+ public SpannableStringBuilder replace(int start, int end,
CharSequence tb, int tbstart, int tbend) {
checkRange("replace", start, end);
+ // Sanity check
+ if (start > end) {
+ Log.w(TAG, "Bad arguments to #replace : "
+ + "start = " + start + ", end = " + end);
+ final int tmp = start;
+ start = end;
+ end = tmp;
+ }
+ if (tbstart > tbend) {
+ Log.w(TAG, "Bad arguments to #replace : "
+ + "tbstart = " + tbstart + ", tbend = " + tbend);
+ final int tmp = tbstart;
+ tbstart = tbend;
+ tbend = tmp;
+ }
+
int filtercount = mFilters.length;
for (int i = 0; i < filtercount; i++) {
CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
@@ -613,8 +630,9 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
// 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
if (flagsStart == POINT && flagsEnd == MARK && start == end) {
- if (send) Log.e("SpannableStringBuilder",
- "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
+ if (send) {
+ Log.e(TAG, "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
+ }
// Silently ignore invalid spans when they are created from this class.
// This avoids the duplication of the above test code before all the
// calls to setSpan that are done in this class
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/BackgroundColorSpan.java b/core/java/android/text/style/BackgroundColorSpan.java
index 580a369..cda8015 100644
--- a/core/java/android/text/style/BackgroundColorSpan.java
+++ b/core/java/android/text/style/BackgroundColorSpan.java
@@ -26,9 +26,9 @@ public class BackgroundColorSpan extends CharacterStyle
private final int mColor;
- public BackgroundColorSpan(int color) {
- mColor = color;
- }
+ public BackgroundColorSpan(int color) {
+ mColor = color;
+ }
public BackgroundColorSpan(Parcel src) {
mColor = src.readInt();
@@ -46,12 +46,12 @@ public class BackgroundColorSpan extends CharacterStyle
dest.writeInt(mColor);
}
- public int getBackgroundColor() {
- return mColor;
- }
+ public int getBackgroundColor() {
+ return mColor;
+ }
- @Override
- public void updateDrawState(TextPaint ds) {
- ds.bgColor = mColor;
- }
+ @Override
+ public void updateDrawState(TextPaint ds) {
+ ds.bgColor = mColor;
+ }
}
diff --git a/core/java/android/text/style/CharacterStyle.java b/core/java/android/text/style/CharacterStyle.java
index 14dfddd..5b95f1a 100644
--- a/core/java/android/text/style/CharacterStyle.java
+++ b/core/java/android/text/style/CharacterStyle.java
@@ -24,7 +24,7 @@ import android.text.TextPaint;
* ones may just implement {@link UpdateAppearance}.
*/
public abstract class CharacterStyle {
- public abstract void updateDrawState(TextPaint tp);
+ public abstract void updateDrawState(TextPaint tp);
/**
* A given CharacterStyle can only applied to a single region of a given
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/ForegroundColorSpan.java b/core/java/android/text/style/ForegroundColorSpan.java
index 476124d..c9e09bd 100644
--- a/core/java/android/text/style/ForegroundColorSpan.java
+++ b/core/java/android/text/style/ForegroundColorSpan.java
@@ -26,9 +26,9 @@ public class ForegroundColorSpan extends CharacterStyle
private final int mColor;
- public ForegroundColorSpan(int color) {
- mColor = color;
- }
+ public ForegroundColorSpan(int color) {
+ mColor = color;
+ }
public ForegroundColorSpan(Parcel src) {
mColor = src.readInt();
@@ -46,12 +46,12 @@ public class ForegroundColorSpan extends CharacterStyle
dest.writeInt(mColor);
}
- public int getForegroundColor() {
- return mColor;
- }
+ public int getForegroundColor() {
+ return mColor;
+ }
- @Override
- public void updateDrawState(TextPaint ds) {
- ds.setColor(mColor);
- }
+ @Override
+ public void updateDrawState(TextPaint ds) {
+ ds.setColor(mColor);
+ }
}
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/ImageSpan.java b/core/java/android/text/style/ImageSpan.java
index 74b9463..3d6f8e6 100644
--- a/core/java/android/text/style/ImageSpan.java
+++ b/core/java/android/text/style/ImageSpan.java
@@ -145,7 +145,7 @@ public class ImageSpan extends DynamicDrawableSpan {
}
} else {
try {
- drawable = mContext.getResources().getDrawable(mResourceId);
+ drawable = mContext.getDrawable(mResourceId);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight());
} catch (Exception e) {
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/MaskFilterSpan.java b/core/java/android/text/style/MaskFilterSpan.java
index 64ab0d8..2ff52a8 100644
--- a/core/java/android/text/style/MaskFilterSpan.java
+++ b/core/java/android/text/style/MaskFilterSpan.java
@@ -21,18 +21,18 @@ import android.text.TextPaint;
public class MaskFilterSpan extends CharacterStyle implements UpdateAppearance {
- private MaskFilter mFilter;
+ private MaskFilter mFilter;
- public MaskFilterSpan(MaskFilter filter) {
- mFilter = filter;
- }
+ public MaskFilterSpan(MaskFilter filter) {
+ mFilter = filter;
+ }
- public MaskFilter getMaskFilter() {
- return mFilter;
- }
+ public MaskFilter getMaskFilter() {
+ return mFilter;
+ }
- @Override
- public void updateDrawState(TextPaint ds) {
- ds.setMaskFilter(mFilter);
- }
+ @Override
+ public void updateDrawState(TextPaint ds) {
+ ds.setMaskFilter(mFilter);
+ }
}
diff --git a/core/java/android/text/style/MetricAffectingSpan.java b/core/java/android/text/style/MetricAffectingSpan.java
index 92558eb..853ecc6 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;
/**
@@ -27,7 +26,7 @@ public abstract class MetricAffectingSpan
extends CharacterStyle
implements UpdateLayout {
- public abstract void updateMeasureState(TextPaint p);
+ public abstract void updateMeasureState(TextPaint p);
/**
* Returns "this" for most MetricAffectingSpans, but for
diff --git a/core/java/android/text/style/RasterizerSpan.java b/core/java/android/text/style/RasterizerSpan.java
index 75b5bcc..cae9640 100644
--- a/core/java/android/text/style/RasterizerSpan.java
+++ b/core/java/android/text/style/RasterizerSpan.java
@@ -21,18 +21,18 @@ import android.text.TextPaint;
public class RasterizerSpan extends CharacterStyle implements UpdateAppearance {
- private Rasterizer mRasterizer;
+ private Rasterizer mRasterizer;
- public RasterizerSpan(Rasterizer r) {
- mRasterizer = r;
- }
+ public RasterizerSpan(Rasterizer r) {
+ mRasterizer = r;
+ }
- public Rasterizer getRasterizer() {
- return mRasterizer;
- }
+ public Rasterizer getRasterizer() {
+ return mRasterizer;
+ }
- @Override
- public void updateDrawState(TextPaint ds) {
- ds.setRasterizer(mRasterizer);
- }
+ @Override
+ public void updateDrawState(TextPaint ds) {
+ ds.setRasterizer(mRasterizer);
+ }
}
diff --git a/core/java/android/text/style/RelativeSizeSpan.java b/core/java/android/text/style/RelativeSizeSpan.java
index 9717362..632dbd4 100644
--- a/core/java/android/text/style/RelativeSizeSpan.java
+++ b/core/java/android/text/style/RelativeSizeSpan.java
@@ -23,11 +23,11 @@ import android.text.TextUtils;
public class RelativeSizeSpan extends MetricAffectingSpan implements ParcelableSpan {
- private final float mProportion;
+ private final float mProportion;
- public RelativeSizeSpan(float proportion) {
- mProportion = proportion;
- }
+ public RelativeSizeSpan(float proportion) {
+ mProportion = proportion;
+ }
public RelativeSizeSpan(Parcel src) {
mProportion = src.readFloat();
@@ -45,17 +45,17 @@ public class RelativeSizeSpan extends MetricAffectingSpan implements ParcelableS
dest.writeFloat(mProportion);
}
- public float getSizeChange() {
- return mProportion;
- }
+ public float getSizeChange() {
+ return mProportion;
+ }
- @Override
- public void updateDrawState(TextPaint ds) {
- ds.setTextSize(ds.getTextSize() * mProportion);
- }
+ @Override
+ public void updateDrawState(TextPaint ds) {
+ ds.setTextSize(ds.getTextSize() * mProportion);
+ }
- @Override
- public void updateMeasureState(TextPaint ds) {
- ds.setTextSize(ds.getTextSize() * mProportion);
- }
+ @Override
+ public void updateMeasureState(TextPaint ds) {
+ ds.setTextSize(ds.getTextSize() * mProportion);
+ }
}
diff --git a/core/java/android/text/style/ScaleXSpan.java b/core/java/android/text/style/ScaleXSpan.java
index 655064b..a22a5a1 100644
--- a/core/java/android/text/style/ScaleXSpan.java
+++ b/core/java/android/text/style/ScaleXSpan.java
@@ -23,11 +23,11 @@ import android.text.TextUtils;
public class ScaleXSpan extends MetricAffectingSpan implements ParcelableSpan {
- private final float mProportion;
+ private final float mProportion;
- public ScaleXSpan(float proportion) {
- mProportion = proportion;
- }
+ public ScaleXSpan(float proportion) {
+ mProportion = proportion;
+ }
public ScaleXSpan(Parcel src) {
mProportion = src.readFloat();
@@ -45,17 +45,17 @@ public class ScaleXSpan extends MetricAffectingSpan implements ParcelableSpan {
dest.writeFloat(mProportion);
}
- public float getScaleX() {
- return mProportion;
- }
+ public float getScaleX() {
+ return mProportion;
+ }
- @Override
- public void updateDrawState(TextPaint ds) {
- ds.setTextScaleX(ds.getTextScaleX() * mProportion);
- }
+ @Override
+ public void updateDrawState(TextPaint ds) {
+ ds.setTextScaleX(ds.getTextScaleX() * mProportion);
+ }
- @Override
- public void updateMeasureState(TextPaint ds) {
- ds.setTextScaleX(ds.getTextScaleX() * mProportion);
- }
+ @Override
+ public void updateMeasureState(TextPaint ds) {
+ ds.setTextScaleX(ds.getTextScaleX() * mProportion);
+ }
}
diff --git a/core/java/android/text/style/StrikethroughSpan.java b/core/java/android/text/style/StrikethroughSpan.java
index b51363a..303e415 100644
--- a/core/java/android/text/style/StrikethroughSpan.java
+++ b/core/java/android/text/style/StrikethroughSpan.java
@@ -40,8 +40,8 @@ public class StrikethroughSpan extends CharacterStyle
public void writeToParcel(Parcel dest, int flags) {
}
- @Override
- public void updateDrawState(TextPaint ds) {
- ds.setStrikeThruText(true);
- }
+ @Override
+ public void updateDrawState(TextPaint ds) {
+ ds.setStrikeThruText(true);
+ }
}
diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java
index 8e6147c..b08f70e 100644
--- a/core/java/android/text/style/StyleSpan.java
+++ b/core/java/android/text/style/StyleSpan.java
@@ -33,17 +33,17 @@ import android.text.TextUtils;
*/
public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan {
- private final int mStyle;
-
- /**
- *
- * @param style An integer constant describing the style for this span. Examples
- * include bold, italic, and normal. Values are constants defined
- * in {@link android.graphics.Typeface}.
- */
- public StyleSpan(int style) {
- mStyle = style;
- }
+ private final int mStyle;
+
+ /**
+ *
+ * @param style An integer constant describing the style for this span. Examples
+ * include bold, italic, and normal. Values are constants defined
+ * in {@link android.graphics.Typeface}.
+ */
+ public StyleSpan(int style) {
+ mStyle = style;
+ }
public StyleSpan(Parcel src) {
mStyle = src.readInt();
@@ -61,19 +61,19 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan {
dest.writeInt(mStyle);
}
- /**
- * Returns the style constant defined in {@link android.graphics.Typeface}.
- */
- public int getStyle() {
- return mStyle;
- }
+ /**
+ * Returns the style constant defined in {@link android.graphics.Typeface}.
+ */
+ public int getStyle() {
+ return mStyle;
+ }
- @Override
+ @Override
public void updateDrawState(TextPaint ds) {
apply(ds, mStyle);
}
- @Override
+ @Override
public void updateMeasureState(TextPaint paint) {
apply(paint, mStyle);
}
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/text/style/UnderlineSpan.java b/core/java/android/text/style/UnderlineSpan.java
index b0cb0e8..80b2427 100644
--- a/core/java/android/text/style/UnderlineSpan.java
+++ b/core/java/android/text/style/UnderlineSpan.java
@@ -40,8 +40,8 @@ public class UnderlineSpan extends CharacterStyle
public void writeToParcel(Parcel dest, int flags) {
}
- @Override
- public void updateDrawState(TextPaint ds) {
- ds.setUnderlineText(true);
- }
+ @Override
+ public void updateDrawState(TextPaint ds) {
+ ds.setUnderlineText(true);
+ }
}
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/transition/Transition.java b/core/java/android/transition/Transition.java
index da9ba5a..c88b4c0 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -28,6 +28,7 @@ import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOverlay;
+import android.view.WindowId;
import android.widget.ListView;
import android.widget.Spinner;
@@ -496,7 +497,8 @@ public abstract class Transition implements Cloneable {
view = (start != null) ? start.view : null;
}
if (animator != null) {
- AnimationInfo info = new AnimationInfo(view, getName(), infoValues);
+ AnimationInfo info = new AnimationInfo(view, getName(),
+ sceneRoot.getWindowId(), infoValues);
runningAnimators.put(animator, info);
mAnimators.add(animator);
}
@@ -1077,6 +1079,9 @@ public abstract class Transition implements Cloneable {
if (view == null) {
return;
}
+ if (!isValidTarget(view, view.getId())) {
+ return;
+ }
boolean isListViewItem = false;
if (view.getParent() instanceof ListView) {
isListViewItem = true;
@@ -1109,30 +1114,32 @@ public abstract class Transition implements Cloneable {
}
}
}
- TransitionValues values = new TransitionValues();
- values.view = view;
- if (start) {
- captureStartValues(values);
- } else {
- captureEndValues(values);
- }
- if (start) {
- if (!isListViewItem) {
- mStartValues.viewValues.put(view, values);
- if (id >= 0) {
- mStartValues.idValues.put((int) id, values);
- }
+ if (view.getParent() instanceof ViewGroup) {
+ TransitionValues values = new TransitionValues();
+ values.view = view;
+ if (start) {
+ captureStartValues(values);
} else {
- mStartValues.itemIdValues.put(itemId, values);
+ captureEndValues(values);
}
- } else {
- if (!isListViewItem) {
- mEndValues.viewValues.put(view, values);
- if (id >= 0) {
- mEndValues.idValues.put((int) id, values);
+ if (start) {
+ if (!isListViewItem) {
+ mStartValues.viewValues.put(view, values);
+ if (id >= 0) {
+ mStartValues.idValues.put((int) id, values);
+ }
+ } else {
+ mStartValues.itemIdValues.put(itemId, values);
}
} else {
- mEndValues.itemIdValues.put(itemId, values);
+ if (!isListViewItem) {
+ mEndValues.viewValues.put(view, values);
+ if (id >= 0) {
+ mEndValues.idValues.put((int) id, values);
+ }
+ } else {
+ mEndValues.itemIdValues.put(itemId, values);
+ }
}
}
if (view instanceof ViewGroup) {
@@ -1194,13 +1201,17 @@ public abstract class Transition implements Cloneable {
*
* @hide
*/
- public void pause() {
+ public void pause(View sceneRoot) {
if (!mEnded) {
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
int numOldAnims = runningAnimators.size();
+ WindowId windowId = sceneRoot.getWindowId();
for (int i = numOldAnims - 1; i >= 0; i--) {
- Animator anim = runningAnimators.keyAt(i);
- anim.pause();
+ AnimationInfo info = runningAnimators.valueAt(i);
+ if (info.view != null && windowId.equals(info.windowId)) {
+ Animator anim = runningAnimators.keyAt(i);
+ anim.pause();
+ }
}
if (mListeners != null && mListeners.size() > 0) {
ArrayList<TransitionListener> tmpListeners =
@@ -1221,14 +1232,18 @@ public abstract class Transition implements Cloneable {
*
* @hide
*/
- public void resume() {
+ public void resume(View sceneRoot) {
if (mPaused) {
if (!mEnded) {
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
int numOldAnims = runningAnimators.size();
+ WindowId windowId = sceneRoot.getWindowId();
for (int i = numOldAnims - 1; i >= 0; i--) {
- Animator anim = runningAnimators.keyAt(i);
- anim.resume();
+ AnimationInfo info = runningAnimators.valueAt(i);
+ if (info.view != null && windowId.equals(info.windowId)) {
+ Animator anim = runningAnimators.keyAt(i);
+ anim.resume();
+ }
}
if (mListeners != null && mListeners.size() > 0) {
ArrayList<TransitionListener> tmpListeners =
@@ -1467,6 +1482,10 @@ public abstract class Transition implements Cloneable {
mCanRemoveViews = canRemoveViews;
}
+ public boolean canRemoveViews() {
+ return mCanRemoveViews;
+ }
+
@Override
public String toString() {
return toString("");
@@ -1629,16 +1648,19 @@ public abstract class Transition implements Cloneable {
* animation should be canceled or a new animation noop'd. The structure holds
* information about the state that an animation is going to, to be compared to
* end state of a new animation.
+ * @hide
*/
- private static class AnimationInfo {
- View view;
+ public static class AnimationInfo {
+ public View view;
String name;
TransitionValues values;
+ WindowId windowId;
- AnimationInfo(View view, String name, TransitionValues values) {
+ AnimationInfo(View view, String name, WindowId windowId, TransitionValues values) {
this.view = view;
this.name = name;
this.values = values;
+ this.windowId = windowId;
}
}
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index 9f77d5e..912f2ed 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Xml;
import android.view.InflateException;
@@ -284,25 +285,27 @@ public class TransitionInflater {
com.android.internal.R.styleable.TransitionManager);
int transitionId = a.getResourceId(
com.android.internal.R.styleable.TransitionManager_transition, -1);
- Scene fromScene = null, toScene = null;
int fromId = a.getResourceId(
com.android.internal.R.styleable.TransitionManager_fromScene, -1);
- if (fromId >= 0) fromScene = Scene.getSceneForLayout(sceneRoot, fromId, mContext);
+ Scene fromScene = (fromId < 0) ? null: Scene.getSceneForLayout(sceneRoot, fromId, mContext);
int toId = a.getResourceId(
com.android.internal.R.styleable.TransitionManager_toScene, -1);
- if (toId >= 0) toScene = Scene.getSceneForLayout(sceneRoot, toId, mContext);
+ Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext);
+
if (transitionId >= 0) {
Transition transition = inflateTransition(transitionId);
if (transition != null) {
- if (fromScene != null) {
- if (toScene == null){
- throw new RuntimeException("No matching toScene for given fromScene " +
+ if (fromScene == null) {
+ if (toScene == null) {
+ throw new RuntimeException("No matching fromScene or toScene " +
"for transition ID " + transitionId);
} else {
- transitionManager.setTransition(fromScene, toScene, transition);
+ transitionManager.setTransition(toScene, transition);
}
- } else if (toId >= 0) {
- transitionManager.setTransition(toScene, transition);
+ } else if (toScene == null) {
+ transitionManager.setExitTransition(fromScene, transition);
+ } else {
+ transitionManager.setTransition(fromScene, toScene, transition);
}
}
}
diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java
index 3bf6790..1614d34 100644
--- a/core/java/android/transition/TransitionManager.java
+++ b/core/java/android/transition/TransitionManager.java
@@ -67,7 +67,10 @@ public class TransitionManager {
private static Transition sDefaultTransition = new AutoTransition();
+ private static final String[] EMPTY_STRINGS = new String[0];
+
ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>();
+ ArrayMap<Scene, Transition> mExitSceneTransitions = new ArrayMap<Scene, Transition>();
ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions =
new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>
@@ -116,6 +119,21 @@ public class TransitionManager {
}
/**
+ * Sets a specific transition to occur when the given scene is exited. This
+ * has the lowest priority -- if a Scene-to-Scene transition or
+ * Scene enter transition can be applied, it will.
+ *
+ * @param scene The scene which, when exited, will cause the given
+ * transition to run.
+ * @param transition The transition that will play when the given scene is
+ * exited. A value of null will result in the default behavior of
+ * using the default transition instead.
+ */
+ public void setExitTransition(Scene scene, Transition transition) {
+ mExitSceneTransitions.put(scene, transition);
+ }
+
+ /**
* Sets a specific transition to occur when the given pair of scenes is
* exited/entered.
*
@@ -163,6 +181,9 @@ public class TransitionManager {
}
}
transition = mSceneTransitions.get(scene);
+ if (transition == null && sceneRoot != null) {
+ transition = mExitSceneTransitions.get(Scene.getCurrentScene(sceneRoot));
+ }
return (transition != null) ? transition : sDefaultTransition;
}
@@ -218,6 +239,34 @@ public class TransitionManager {
}
/**
+ * Retrieve the transition to a target defined scene if one has been
+ * associated with this TransitionManager.
+ *
+ * @param toScene Target scene that this transition will move to
+ * @return Transition corresponding to the given toScene or null
+ * if no association exists in this TransitionManager
+ *
+ * @see #setTransition(Scene, Transition)
+ * @hide
+ */
+ public Transition getEnterTransition(Scene toScene) {
+ return mSceneTransitions.get(toScene);
+ }
+
+ /**
+ * Retrieve the transition from a defined scene to a target named scene if one has been
+ * associated with this TransitionManager.
+ *
+ * @param fromScene Scene that this transition starts from
+ * @return Transition corresponding to the given fromScene or null
+ * if no association exists in this TransitionManager
+ * @hide
+ */
+ public Transition getExitTransition(Scene fromScene) {
+ return mExitSceneTransitions.get(fromScene);
+ }
+
+ /**
* This private utility class is used to listen for both OnPreDraw and
* OnAttachStateChange events. OnPreDraw events are the main ones we care
* about since that's what triggers the transition to take place.
@@ -253,7 +302,7 @@ public class TransitionManager {
ArrayList<Transition> runningTransitions = getRunningTransitions().get(mSceneRoot);
if (runningTransitions != null && runningTransitions.size() > 0) {
for (Transition runningTransition : runningTransitions) {
- runningTransition.resume();
+ runningTransition.resume(mSceneRoot);
}
}
mTransition.clearValues(true);
@@ -286,7 +335,7 @@ public class TransitionManager {
mTransition.captureValues(mSceneRoot, false);
if (previousRunningTransitions != null) {
for (Transition runningTransition : previousRunningTransitions) {
- runningTransition.resume();
+ runningTransition.resume(mSceneRoot);
}
}
mTransition.playTransition(mSceneRoot);
@@ -302,7 +351,7 @@ public class TransitionManager {
if (runningTransitions != null && runningTransitions.size() > 0) {
for (Transition runningTransition : runningTransitions) {
- runningTransition.pause();
+ runningTransition.pause(sceneRoot);
}
}
@@ -329,7 +378,6 @@ public class TransitionManager {
// Auto transition if there is no transition declared for the Scene, but there is
// a root or parent view
changeScene(scene, getTransition(scene));
-
}
/**
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index 4545e3b..19d6b3d 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -317,21 +317,21 @@ public class TransitionSet extends Transition {
/** @hide */
@Override
- public void pause() {
- super.pause();
+ public void pause(View sceneRoot) {
+ super.pause(sceneRoot);
int numTransitions = mTransitions.size();
for (int i = 0; i < numTransitions; ++i) {
- mTransitions.get(i).pause();
+ mTransitions.get(i).pause(sceneRoot);
}
}
/** @hide */
@Override
- public void resume() {
- super.resume();
+ public void resume(View sceneRoot) {
+ super.resume(sceneRoot);
int numTransitions = mTransitions.size();
for (int i = 0; i < numTransitions; ++i) {
- mTransitions.get(i).resume();
+ mTransitions.get(i).resume(sceneRoot);
}
}
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..290a89b
--- /dev/null
+++ b/core/java/android/util/LongArray.java
@@ -0,0 +1,166 @@
+/*
+ * 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(values.mValues, 0, mValues, mSize, 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) {
+ if (index >= mSize) {
+ throw new ArrayIndexOutOfBoundsException(mSize, 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) {
+ if (index >= mSize) {
+ throw new ArrayIndexOutOfBoundsException(mSize, 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/LruCache.java b/core/java/android/util/LruCache.java
index dd504c1..4015488 100644
--- a/core/java/android/util/LruCache.java
+++ b/core/java/android/util/LruCache.java
@@ -87,9 +87,8 @@ public class LruCache<K, V> {
/**
* Sets the size of the cache.
- * @param maxSize The new maximum size.
*
- * @hide
+ * @param maxSize The new maximum size.
*/
public void resize(int maxSize) {
if (maxSize <= 0) {
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/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java
index 905dcb0..f59ef0f6d 100644
--- a/core/java/android/util/SparseBooleanArray.java
+++ b/core/java/android/util/SparseBooleanArray.java
@@ -115,6 +115,13 @@ public class SparseBooleanArray implements Cloneable {
}
}
+ /** @hide */
+ public void removeAt(int index) {
+ System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
+ System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
+ mSize--;
+ }
+
/**
* Adds a mapping from the specified key to the specified value,
* replacing the previous mapping from the specified key if there
@@ -191,6 +198,11 @@ public class SparseBooleanArray implements Cloneable {
return mValues[index];
}
+ /** @hide */
+ public void setValueAt(int index, boolean value) {
+ mValues[index] = value;
+ }
+
/**
* Returns the index for which {@link #keyAt} would return the
* specified key, or a negative number if the specified
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..0afbde9 100644
--- a/core/java/android/view/ContextThemeWrapper.java
+++ b/core/java/android/view/ContextThemeWrapper.java
@@ -20,14 +20,12 @@ 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
* wrapped context.
*/
public class ContextThemeWrapper extends ContextWrapper {
- private Context mBase;
private int mThemeResource;
private Resources.Theme mTheme;
private LayoutInflater mInflater;
@@ -40,13 +38,11 @@ public class ContextThemeWrapper extends ContextWrapper {
public ContextThemeWrapper(Context base, int themeres) {
super(base);
- mBase = base;
mThemeResource = themeres;
}
@Override protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
- mBase = newBase;
}
/**
@@ -110,11 +106,11 @@ public class ContextThemeWrapper extends ContextWrapper {
@Override public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
- mInflater = LayoutInflater.from(mBase).cloneInContext(this);
+ mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
- return mBase.getSystemService(name);
+ return getBaseContext().getSystemService(name);
}
/**
@@ -136,7 +132,7 @@ public class ContextThemeWrapper extends ContextWrapper {
final boolean first = mTheme == null;
if (first) {
mTheme = getResources().newTheme();
- Resources.Theme theme = mBase.getTheme();
+ Resources.Theme theme = getBaseContext().getTheme();
if (theme != null) {
mTheme.setTo(theme);
}
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..be6f401 100644
--- a/core/java/android/view/DisplayList.java
+++ b/core/java/android/view/DisplayList.java
@@ -17,6 +17,7 @@
package android.view;
import android.graphics.Matrix;
+import android.graphics.Path;
/**
* <p>A display list records a series of graphics related operations and can replay
@@ -86,18 +87,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();
* }
* }
*
@@ -123,12 +121,10 @@ import android.graphics.Matrix;
*
* @hide
*/
-public abstract class DisplayList {
- private boolean mDirty;
-
+public class DisplayList {
/**
* Flag used when calling
- * {@link HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)}
+ * {@link HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)}
* When this flag is set, draw operations lying outside of the bounds of the
* display list will be culled early. It is recommeneded to always set this
* flag.
@@ -141,7 +137,7 @@ public abstract class DisplayList {
/**
* Indicates that the display list is done drawing.
- *
+ *
* @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
*
* @hide
@@ -150,7 +146,7 @@ public abstract class DisplayList {
/**
* Indicates that the display list needs another drawing pass.
- *
+ *
* @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
*
* @hide
@@ -159,9 +155,9 @@ public abstract class DisplayList {
/**
* Indicates that the display list needs to re-execute its GL functors.
- *
- * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
- * @see HardwareCanvas#callDrawGLFunction(int)
+ *
+ * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
+ * @see HardwareCanvas#callDrawGLFunction(long)
*
* @hide
*/
@@ -176,6 +172,29 @@ public abstract class DisplayList {
*/
public static final int STATUS_DREW = 0x4;
+ private boolean mValid;
+ private final long mNativeDisplayList;
+ private HardwareRenderer mRenderer;
+
+ private DisplayList(String name) {
+ mNativeDisplayList = nCreate();
+ nSetDisplayListName(mNativeDisplayList, name);
+ }
+
+ /**
+ * 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 DisplayList(name);
+ }
+
/**
* Starts recording the display list. All operations performed on the
* returned canvas are recorded and stored in this display list.
@@ -191,7 +210,13 @@ public abstract class DisplayList {
* @see #end()
* @see #isValid()
*/
- public abstract HardwareCanvas start(int width, int height);
+ public HardwareCanvas start(int width, int height) {
+ HardwareCanvas canvas = GLES20RecordingCanvas.obtain();
+ canvas.setViewport(width, height);
+ // The dirty rect should always be null for a display list
+ canvas.onPreDraw(null);
+ return canvas;
+ }
/**
* Ends the recording for this display list. A display list cannot be
@@ -201,65 +226,46 @@ public abstract class DisplayList {
* @see #start(int, int)
* @see #isValid()
*/
- public abstract void end();
-
- /**
- * Clears resources held onto by this display list. After calling this method
- * {@link #isValid()} will return false.
- *
- * @see #isValid()
- * @see #reset()
- */
- public abstract void clear();
-
+ public void end(HardwareRenderer renderer, HardwareCanvas endCanvas) {
+ if (!(endCanvas instanceof GLES20RecordingCanvas)) {
+ throw new IllegalArgumentException("Passed an invalid canvas to end!");
+ }
+
+ GLES20RecordingCanvas canvas = (GLES20RecordingCanvas) endCanvas;
+ canvas.onPostDraw();
+ long displayListData = canvas.finishRecording();
+ if (renderer != mRenderer) {
+ // If we are changing renderers first destroy with the old
+ // renderer, then set with the new one
+ destroyDisplayListData();
+ }
+ mRenderer = renderer;
+ setDisplayListData(displayListData);
+ canvas.recycle();
+ mValid = true;
+ }
/**
* Reset native resources. This is called when cleaning up the state of display lists
* during destruction of hardware resources, to ensure that we do not hold onto
* obsolete resources after related resources are gone.
*
- * @see #clear()
- *
* @hide
*/
- public abstract void reset();
+ public void destroyDisplayListData() {
+ if (!mValid) return;
- /**
- * Sets the dirty flag. When a display list is dirty, {@link #clear()} should
- * be invoked whenever possible.
- *
- * @see #isDirty()
- * @see #clear()
- *
- * @hide
- */
- public void markDirty() {
- mDirty = true;
+ setDisplayListData(0);
+ mRenderer = null;
+ mValid = false;
}
- /**
- * Removes the dirty flag. This method can be used to cancel a cleanup
- * previously scheduled by setting the dirty flag.
- *
- * @see #isDirty()
- * @see #clear()
- *
- * @hide
- */
- protected void clearDirty() {
- mDirty = false;
- }
-
- /**
- * Indicates whether the display list is dirty.
- *
- * @see #markDirty()
- * @see #clear()
- *
- * @hide
- */
- public boolean isDirty() {
- return mDirty;
+ private void setDisplayListData(long newData) {
+ if (mRenderer != null) {
+ mRenderer.setDisplayListData(mNativeDisplayList, newData);
+ } else {
+ throw new IllegalStateException("Trying to set data without a renderer! data=" + newData);
+ }
}
/**
@@ -268,16 +274,14 @@ public abstract class DisplayList {
*
* @return boolean true if the display list is able to be replayed, false otherwise.
*/
- public abstract boolean isValid();
+ public boolean isValid() { return mValid; }
- /**
- * Return the amount of memory used by this display list.
- *
- * @return The size of this display list in bytes
- *
- * @hide
- */
- public abstract int getSize();
+ long getNativeDisplayList() {
+ if (!mValid) {
+ throw new IllegalStateException("The display list is not valid.");
+ }
+ return mNativeDisplayList;
+ }
///////////////////////////////////////////////////////////////////////////
// DisplayList Property Setters
@@ -292,7 +296,9 @@ public abstract class DisplayList {
*
* @hide
*/
- public abstract void setCaching(boolean caching);
+ public void setCaching(boolean caching) {
+ nSetCaching(mNativeDisplayList, caching);
+ }
/**
* Set whether the display list should clip itself to its bounds. This property is controlled by
@@ -300,44 +306,95 @@ public abstract class DisplayList {
*
* @param clipToBounds true if the display list should clip to its bounds
*/
- public abstract void setClipToBounds(boolean clipToBounds);
+ public void setClipToBounds(boolean clipToBounds) {
+ nSetClipToBounds(mNativeDisplayList, clipToBounds);
+ }
/**
- * Set the static matrix on the display list. The specified matrix is combined with other
- * transforms (such as {@link #setScaleX(float)}, {@link #setRotation(float)}, etc.)
+ * Set whether the display list should collect and Z order all 3d composited descendents, and
+ * draw them in order with the default Z=0 content.
*
- * @param matrix A transform matrix to apply to this display list
+ * @param isolatedZVolume true if the display list should collect and Z order descendents.
+ */
+ public void setIsolatedZVolume(boolean isolatedZVolume) {
+ nSetIsolatedZVolume(mNativeDisplayList, isolatedZVolume);
+ }
+
+ /**
+ * Sets whether the display list should be drawn immediately after the
+ * closest ancestor display list where isolateZVolume is true. If the
+ * display list itself satisfies this constraint, changing this attribute
+ * has no effect on drawing order.
*
- * @see #getMatrix(android.graphics.Matrix)
- * @see #getMatrix()
+ * @param shouldProject true if the display list should be projected onto a
+ * containing volume.
*/
- public abstract void setMatrix(Matrix matrix);
+ public void setProjectBackwards(boolean shouldProject) {
+ nSetProjectBackwards(mNativeDisplayList, shouldProject);
+ }
+
+ /**
+ * Sets whether the display list is a projection receiver - that its parent
+ * DisplayList should draw any descendent DisplayLists with
+ * ProjectBackwards=true directly on top of it. Default value is false.
+ */
+ public void setProjectionReceiver(boolean shouldRecieve) {
+ nSetProjectionReceiver(mNativeDisplayList, shouldRecieve);
+ }
/**
- * Returns the static matrix set on this display list.
+ * Sets the outline, defining the shape that casts a shadow, and the path to
+ * be clipped if setClipToOutline is set.
*
- * @return A new {@link Matrix} instance populated with this display list's static
- * matrix
+ * Deep copies the native path to simplify reference ownership.
*
- * @see #getMatrix(android.graphics.Matrix)
- * @see #setMatrix(android.graphics.Matrix)
+ * @param outline Convex, CW Path to store in the DisplayList. May be null.
+ */
+ public void setOutline(Path outline) {
+ long nativePath = (outline == null) ? 0 : outline.mNativePath;
+ nSetOutline(mNativeDisplayList, nativePath);
+ }
+
+ /**
+ * Enables or disables clipping to the outline.
+ *
+ * @param clipToOutline true if clipping to the outline.
+ */
+ public void setClipToOutline(boolean clipToOutline) {
+ nSetClipToOutline(mNativeDisplayList, clipToOutline);
+ }
+
+ /**
+ * Set whether the DisplayList should cast a shadow.
+ *
+ * The shape of the shadow casting area is defined by the outline of the display list, if set
+ * and non-empty, otherwise it will be the bounds rect.
*/
- public Matrix getMatrix() {
- return getMatrix(new Matrix());
+ public void setCastsShadow(boolean castsShadow) {
+ nSetCastsShadow(mNativeDisplayList, castsShadow);
}
/**
- * Copies this display list's static matrix into the specified matrix.
+ * Sets whether the DisplayList should be drawn with perspective applied from the global camera.
*
- * @param matrix The {@link Matrix} instance in which to copy this display
- * list's static matrix. Cannot be null
+ * If set to true, camera distance will be ignored. Defaults to false.
+ */
+ public void setUsesGlobalCamera(boolean usesGlobalCamera) {
+ nSetUsesGlobalCamera(mNativeDisplayList, usesGlobalCamera);
+ }
+
+ /**
+ * Set the static matrix on the display list. The specified matrix is combined with other
+ * transforms (such as {@link #setScaleX(float)}, {@link #setRotation(float)}, etc.)
*
- * @return The <code>matrix</code> parameter, for convenience
+ * @param matrix A transform matrix to apply to this display list
*
+ * @see #getMatrix(android.graphics.Matrix)
* @see #getMatrix()
- * @see #setMatrix(android.graphics.Matrix)
*/
- public abstract Matrix getMatrix(Matrix matrix);
+ public void setStaticMatrix(Matrix matrix) {
+ nSetStaticMatrix(mNativeDisplayList, matrix.native_instance);
+ }
/**
* Set the Animation matrix on the display list. This matrix exists if an Animation is
@@ -349,7 +406,10 @@ public abstract class DisplayList {
*
* @hide
*/
- public abstract void setAnimationMatrix(Matrix matrix);
+ public void setAnimationMatrix(Matrix matrix) {
+ nSetAnimationMatrix(mNativeDisplayList,
+ (matrix != null) ? matrix.native_instance : 0);
+ }
/**
* Sets the translucency level for the display list.
@@ -359,7 +419,9 @@ public abstract class DisplayList {
* @see View#setAlpha(float)
* @see #getAlpha()
*/
- public abstract void setAlpha(float alpha);
+ public void setAlpha(float alpha) {
+ nSetAlpha(mNativeDisplayList, alpha);
+ }
/**
* Returns the translucency level of this display list.
@@ -368,7 +430,9 @@ public abstract class DisplayList {
*
* @see #setAlpha(float)
*/
- public abstract float getAlpha();
+ public float getAlpha() {
+ return nGetAlpha(mNativeDisplayList);
+ }
/**
* Sets whether the display list renders content which overlaps. Non-overlapping rendering
@@ -381,7 +445,9 @@ public abstract class DisplayList {
* @see android.view.View#hasOverlappingRendering()
* @see #hasOverlappingRendering()
*/
- public abstract void setHasOverlappingRendering(boolean hasOverlappingRendering);
+ public void setHasOverlappingRendering(boolean hasOverlappingRendering) {
+ nSetHasOverlappingRendering(mNativeDisplayList, hasOverlappingRendering);
+ }
/**
* Indicates whether the content of this display list overlaps.
@@ -390,126 +456,176 @@ public abstract class DisplayList {
*
* @see #setHasOverlappingRendering(boolean)
*/
- public abstract boolean hasOverlappingRendering();
+ public boolean hasOverlappingRendering() {
+ //noinspection SimplifiableIfStatement
+ return nHasOverlappingRendering(mNativeDisplayList);
+ }
/**
- * Sets the translation value for the display list on the X axis
+ * Sets the translation value for the display list on the X axis.
*
* @param translationX The X axis translation value of the display list, in pixels
*
* @see View#setTranslationX(float)
* @see #getTranslationX()
*/
- public abstract void setTranslationX(float translationX);
+ public void setTranslationX(float translationX) {
+ nSetTranslationX(mNativeDisplayList, translationX);
+ }
/**
* Returns the translation value for this display list on the X axis, in pixels.
*
* @see #setTranslationX(float)
*/
- public abstract float getTranslationX();
+ public float getTranslationX() {
+ return nGetTranslationX(mNativeDisplayList);
+ }
/**
- * Sets the translation value for the display list on the Y axis
+ * Sets the translation value for the display list on the Y axis.
*
* @param translationY The Y axis translation value of the display list, in pixels
*
* @see View#setTranslationY(float)
* @see #getTranslationY()
*/
- public abstract void setTranslationY(float translationY);
+ public void setTranslationY(float translationY) {
+ nSetTranslationY(mNativeDisplayList, translationY);
+ }
/**
* Returns the translation value for this display list on the Y axis, in pixels.
*
* @see #setTranslationY(float)
*/
- public abstract float getTranslationY();
+ public float getTranslationY() {
+ return nGetTranslationY(mNativeDisplayList);
+ }
+
+ /**
+ * Sets the translation value for the display list on the Z axis.
+ *
+ * @see View#setTranslationZ(float)
+ * @see #getTranslationZ()
+ */
+ public void setTranslationZ(float translationZ) {
+ nSetTranslationZ(mNativeDisplayList, translationZ);
+ }
/**
- * Sets the rotation value for the display list around the Z axis
+ * Returns the translation value for this display list on the Z axis.
+ *
+ * @see #setTranslationZ(float)
+ */
+ public float getTranslationZ() {
+ return nGetTranslationZ(mNativeDisplayList);
+ }
+
+ /**
+ * Sets the rotation value for the display list around the Z axis.
*
* @param rotation The rotation value of the display list, in degrees
*
* @see View#setRotation(float)
* @see #getRotation()
*/
- public abstract void setRotation(float rotation);
+ public void setRotation(float rotation) {
+ nSetRotation(mNativeDisplayList, rotation);
+ }
/**
* Returns the rotation value for this display list around the Z axis, in degrees.
*
* @see #setRotation(float)
*/
- public abstract float getRotation();
+ public float getRotation() {
+ return nGetRotation(mNativeDisplayList);
+ }
/**
- * Sets the rotation value for the display list around the X axis
+ * Sets the rotation value for the display list around the X axis.
*
* @param rotationX The rotation value of the display list, in degrees
*
* @see View#setRotationX(float)
* @see #getRotationX()
*/
- public abstract void setRotationX(float rotationX);
+ public void setRotationX(float rotationX) {
+ nSetRotationX(mNativeDisplayList, rotationX);
+ }
/**
* Returns the rotation value for this display list around the X axis, in degrees.
*
* @see #setRotationX(float)
*/
- public abstract float getRotationX();
+ public float getRotationX() {
+ return nGetRotationX(mNativeDisplayList);
+ }
/**
- * Sets the rotation value for the display list around the Y axis
+ * Sets the rotation value for the display list around the Y axis.
*
* @param rotationY The rotation value of the display list, in degrees
*
* @see View#setRotationY(float)
* @see #getRotationY()
*/
- public abstract void setRotationY(float rotationY);
+ public void setRotationY(float rotationY) {
+ nSetRotationY(mNativeDisplayList, rotationY);
+ }
/**
* Returns the rotation value for this display list around the Y axis, in degrees.
*
* @see #setRotationY(float)
*/
- public abstract float getRotationY();
+ public float getRotationY() {
+ return nGetRotationY(mNativeDisplayList);
+ }
/**
- * Sets the scale value for the display list on the X axis
+ * Sets the scale value for the display list on the X axis.
*
* @param scaleX The scale value of the display list
*
* @see View#setScaleX(float)
* @see #getScaleX()
*/
- public abstract void setScaleX(float scaleX);
+ public void setScaleX(float scaleX) {
+ nSetScaleX(mNativeDisplayList, scaleX);
+ }
/**
* Returns the scale value for this display list on the X axis.
*
* @see #setScaleX(float)
*/
- public abstract float getScaleX();
+ public float getScaleX() {
+ return nGetScaleX(mNativeDisplayList);
+ }
/**
- * Sets the scale value for the display list on the Y axis
+ * Sets the scale value for the display list on the Y axis.
*
* @param scaleY The scale value of the display list
*
* @see View#setScaleY(float)
* @see #getScaleY()
*/
- public abstract void setScaleY(float scaleY);
+ public void setScaleY(float scaleY) {
+ nSetScaleY(mNativeDisplayList, scaleY);
+ }
/**
* Returns the scale value for this display list on the Y axis.
*
* @see #setScaleY(float)
*/
- public abstract float getScaleY();
+ public float getScaleY() {
+ return nGetScaleY(mNativeDisplayList);
+ }
/**
* Sets all of the transform-related values of the display list
@@ -525,8 +641,13 @@ public abstract class DisplayList {
*
* @hide
*/
- public abstract void setTransformationInfo(float alpha, float translationX, float translationY,
- float rotation, float rotationX, float rotationY, float scaleX, float scaleY);
+ public void setTransformationInfo(float alpha,
+ float translationX, float translationY, float translationZ,
+ float rotation, float rotationX, float rotationY, float scaleX, float scaleY) {
+ nSetTransformationInfo(mNativeDisplayList, alpha,
+ translationX, translationY, translationZ,
+ rotation, rotationX, rotationY, scaleX, scaleY);
+ }
/**
* Sets the pivot value for the display list on the X axis
@@ -536,14 +657,18 @@ public abstract class DisplayList {
* @see View#setPivotX(float)
* @see #getPivotX()
*/
- public abstract void setPivotX(float pivotX);
+ public void setPivotX(float pivotX) {
+ nSetPivotX(mNativeDisplayList, pivotX);
+ }
/**
* Returns the pivot value for this display list on the X axis, in pixels.
*
* @see #setPivotX(float)
*/
- public abstract float getPivotX();
+ public float getPivotX() {
+ return nGetPivotX(mNativeDisplayList);
+ }
/**
* Sets the pivot value for the display list on the Y axis
@@ -553,14 +678,18 @@ public abstract class DisplayList {
* @see View#setPivotY(float)
* @see #getPivotY()
*/
- public abstract void setPivotY(float pivotY);
+ public void setPivotY(float pivotY) {
+ nSetPivotY(mNativeDisplayList, pivotY);
+ }
/**
* Returns the pivot value for this display list on the Y axis, in pixels.
*
* @see #setPivotY(float)
*/
- public abstract float getPivotY();
+ public float getPivotY() {
+ return nGetPivotY(mNativeDisplayList);
+ }
/**
* Sets the camera distance for the display list. Refer to
@@ -572,14 +701,18 @@ public abstract class DisplayList {
* @see View#setCameraDistance(float)
* @see #getCameraDistance()
*/
- public abstract void setCameraDistance(float distance);
+ public void setCameraDistance(float distance) {
+ nSetCameraDistance(mNativeDisplayList, distance);
+ }
/**
* Returns the distance in Z of the camera of the display list.
*
* @see #setCameraDistance(float)
*/
- public abstract float getCameraDistance();
+ public float getCameraDistance() {
+ return nGetCameraDistance(mNativeDisplayList);
+ }
/**
* Sets the left position for the display list.
@@ -589,14 +722,18 @@ public abstract class DisplayList {
* @see View#setLeft(int)
* @see #getLeft()
*/
- public abstract void setLeft(int left);
+ public void setLeft(int left) {
+ nSetLeft(mNativeDisplayList, left);
+ }
/**
* Returns the left position for the display list in pixels.
*
* @see #setLeft(int)
*/
- public abstract float getLeft();
+ public float getLeft() {
+ return nGetLeft(mNativeDisplayList);
+ }
/**
* Sets the top position for the display list.
@@ -606,14 +743,18 @@ public abstract class DisplayList {
* @see View#setTop(int)
* @see #getTop()
*/
- public abstract void setTop(int top);
+ public void setTop(int top) {
+ nSetTop(mNativeDisplayList, top);
+ }
/**
* Returns the top position for the display list in pixels.
*
* @see #setTop(int)
*/
- public abstract float getTop();
+ public float getTop() {
+ return nGetTop(mNativeDisplayList);
+ }
/**
* Sets the right position for the display list.
@@ -623,14 +764,18 @@ public abstract class DisplayList {
* @see View#setRight(int)
* @see #getRight()
*/
- public abstract void setRight(int right);
+ public void setRight(int right) {
+ nSetRight(mNativeDisplayList, right);
+ }
/**
* Returns the right position for the display list in pixels.
*
* @see #setRight(int)
*/
- public abstract float getRight();
+ public float getRight() {
+ return nGetRight(mNativeDisplayList);
+ }
/**
* Sets the bottom position for the display list.
@@ -640,14 +785,18 @@ public abstract class DisplayList {
* @see View#setBottom(int)
* @see #getBottom()
*/
- public abstract void setBottom(int bottom);
+ public void setBottom(int bottom) {
+ nSetBottom(mNativeDisplayList, bottom);
+ }
/**
* Returns the bottom position for the display list in pixels.
*
* @see #setBottom(int)
*/
- public abstract float getBottom();
+ public float getBottom() {
+ return nGetBottom(mNativeDisplayList);
+ }
/**
* Sets the left and top positions for the display list
@@ -662,7 +811,9 @@ public abstract class DisplayList {
* @see View#setRight(int)
* @see View#setBottom(int)
*/
- public abstract void setLeftTopRightBottom(int left, int top, int right, int bottom);
+ public void setLeftTopRightBottom(int left, int top, int right, int bottom) {
+ nSetLeftTopRightBottom(mNativeDisplayList, left, top, right, bottom);
+ }
/**
* Offsets the left and right positions for the display list
@@ -672,7 +823,9 @@ public abstract class DisplayList {
*
* @see View#offsetLeftAndRight(int)
*/
- public abstract void offsetLeftAndRight(float offset);
+ public void offsetLeftAndRight(float offset) {
+ nOffsetLeftAndRight(mNativeDisplayList, offset);
+ }
/**
* Offsets the top and bottom values for the display list
@@ -682,5 +835,97 @@ public abstract class DisplayList {
*
* @see View#offsetTopAndBottom(int)
*/
- public abstract void offsetTopAndBottom(float offset);
+ public void offsetTopAndBottom(float offset) {
+ nOffsetTopAndBottom(mNativeDisplayList, offset);
+ }
+
+ /**
+ * Outputs the display list to the log. This method exists for use by
+ * tools to output display lists for selected nodes to the log.
+ *
+ * @hide
+ */
+ public void output() {
+ nOutput(mNativeDisplayList);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Native methods
+ ///////////////////////////////////////////////////////////////////////////
+
+ private static native long nCreate();
+ private static native void nDestroyDisplayList(long displayList);
+ private static native void nSetDisplayListName(long displayList, String name);
+
+ // Properties
+
+ private static native void nOffsetTopAndBottom(long displayList, float offset);
+ private static native void nOffsetLeftAndRight(long displayList, float offset);
+ private static native void nSetLeftTopRightBottom(long displayList, int left, int top,
+ int right, int bottom);
+ private static native void nSetBottom(long displayList, int bottom);
+ private static native void nSetRight(long displayList, int right);
+ private static native void nSetTop(long displayList, int top);
+ private static native void nSetLeft(long displayList, int left);
+ private static native void nSetCameraDistance(long displayList, float distance);
+ private static native void nSetPivotY(long displayList, float pivotY);
+ private static native void nSetPivotX(long displayList, float pivotX);
+ private static native void nSetCaching(long displayList, boolean caching);
+ private static native void nSetClipToBounds(long displayList, boolean clipToBounds);
+ private static native void nSetProjectBackwards(long displayList, boolean shouldProject);
+ private static native void nSetProjectionReceiver(long displayList, boolean shouldRecieve);
+ private static native void nSetIsolatedZVolume(long displayList, boolean isolateZVolume);
+ private static native void nSetOutline(long displayList, long nativePath);
+ private static native void nSetClipToOutline(long displayList, boolean clipToOutline);
+ private static native void nSetCastsShadow(long displayList, boolean castsShadow);
+ private static native void nSetUsesGlobalCamera(long displayList, boolean usesGlobalCamera);
+ private static native void nSetAlpha(long displayList, float alpha);
+ private static native void nSetHasOverlappingRendering(long displayList,
+ boolean hasOverlappingRendering);
+ private static native void nSetTranslationX(long displayList, float translationX);
+ private static native void nSetTranslationY(long displayList, float translationY);
+ private static native void nSetTranslationZ(long displayList, float translationZ);
+ private static native void nSetRotation(long displayList, float rotation);
+ private static native void nSetRotationX(long displayList, float rotationX);
+ private static native void nSetRotationY(long displayList, float rotationY);
+ private static native void nSetScaleX(long displayList, float scaleX);
+ private static native void nSetScaleY(long displayList, float scaleY);
+ private static native void nSetTransformationInfo(long displayList, float alpha,
+ float translationX, float translationY, float translationZ,
+ float rotation, float rotationX, float rotationY, float scaleX, float scaleY);
+ private static native void nSetStaticMatrix(long displayList, long nativeMatrix);
+ private static native void nSetAnimationMatrix(long displayList, long animationMatrix);
+
+ private static native boolean nHasOverlappingRendering(long displayList);
+ private static native float nGetAlpha(long displayList);
+ private static native float nGetLeft(long displayList);
+ private static native float nGetTop(long displayList);
+ private static native float nGetRight(long displayList);
+ private static native float nGetBottom(long displayList);
+ private static native float nGetCameraDistance(long displayList);
+ private static native float nGetScaleX(long displayList);
+ private static native float nGetScaleY(long displayList);
+ private static native float nGetTranslationX(long displayList);
+ private static native float nGetTranslationY(long displayList);
+ private static native float nGetTranslationZ(long displayList);
+ private static native float nGetRotation(long displayList);
+ private static native float nGetRotationX(long displayList);
+ private static native float nGetRotationY(long displayList);
+ private static native float nGetPivotX(long displayList);
+ private static native float nGetPivotY(long displayList);
+ private static native void nOutput(long displayList);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Finalization
+ ///////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ destroyDisplayListData();
+ nDestroyDisplayList(mNativeDisplayList);
+ } finally {
+ super.finalize();
+ }
+ }
}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index d533060..6c6fc9b 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -18,7 +18,6 @@ package android.view;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.ColorFilter;
import android.graphics.DrawFilter;
import android.graphics.Matrix;
import android.graphics.NinePatch;
@@ -31,7 +30,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Shader;
-import android.graphics.SurfaceTexture;
import android.graphics.TemporaryBuffer;
import android.text.GraphicsOperations;
import android.text.SpannableString;
@@ -46,10 +44,9 @@ class GLES20Canvas extends HardwareCanvas {
private static final int MODIFIER_NONE = 0;
private static final int MODIFIER_SHADOW = 1;
private static final int MODIFIER_SHADER = 2;
- private static final int MODIFIER_COLOR_FILTER = 4;
private final boolean mOpaque;
- private long mRenderer;
+ protected long mRenderer;
// The native renderer will be destroyed when this object dies.
// DO NOT overwrite this reference once it is set.
@@ -88,15 +85,6 @@ class GLES20Canvas extends HardwareCanvas {
GLES20Canvas(boolean translucent) {
this(false, translucent);
}
-
- /**
- * Creates a canvas to render into an FBO.
- */
- GLES20Canvas(long layer, boolean translucent) {
- mOpaque = !translucent;
- mRenderer = nCreateLayerRenderer(layer);
- setupFinalizer();
- }
protected GLES20Canvas(boolean record, boolean translucent) {
mOpaque = !translucent;
@@ -118,12 +106,7 @@ class GLES20Canvas extends HardwareCanvas {
}
}
- protected void resetDisplayListRenderer() {
- nResetDisplayListRenderer(mRenderer);
- }
-
private static native long nCreateRenderer();
- private static native long nCreateLayerRenderer(long layer);
private static native long nCreateDisplayListRenderer();
private static native void nResetDisplayListRenderer(long renderer);
private static native void nDestroyRenderer(long renderer);
@@ -145,13 +128,11 @@ class GLES20Canvas extends HardwareCanvas {
}
}
- @Override
- public void setName(String name) {
- super.setName(name);
- nSetName(mRenderer, name);
+ public static void setProperty(String name, String value) {
+ nSetProperty(name, value);
}
- private static native void nSetName(long renderer, String name);
+ private static native void nSetProperty(String name, String value);
///////////////////////////////////////////////////////////////////////////
// Hardware layers
@@ -159,12 +140,12 @@ class GLES20Canvas extends HardwareCanvas {
@Override
void pushLayerUpdate(HardwareLayer layer) {
- nPushLayerUpdate(mRenderer, ((GLES20RenderLayer) layer).mLayer);
+ nPushLayerUpdate(mRenderer, layer.getLayer());
}
@Override
void cancelLayerUpdate(HardwareLayer layer) {
- nCancelLayerUpdate(mRenderer, ((GLES20RenderLayer) layer).mLayer);
+ nCancelLayerUpdate(mRenderer, layer.getLayer());
}
@Override
@@ -177,22 +158,7 @@ class GLES20Canvas extends HardwareCanvas {
nClearLayerUpdates(mRenderer);
}
- static native long nCreateTextureLayer(boolean opaque, int[] layerInfo);
- static native long nCreateLayer(int width, int height, boolean isOpaque, int[] layerInfo);
- static native boolean nResizeLayer(long layerId, int width, int height, int[] layerInfo);
- static native void nSetOpaqueLayer(long layerId, boolean isOpaque);
- static native void nSetLayerPaint(long layerId, long nativePaint);
- static native void nSetLayerColorFilter(long layerId, long nativeColorFilter);
- static native void nUpdateTextureLayer(long layerId, int width, int height, boolean opaque,
- SurfaceTexture surface);
- static native void nClearLayerTexture(long layerId);
- static native void nSetTextureLayerTransform(long layerId, long matrix);
- static native void nDestroyLayer(long layerId);
- static native void nDestroyLayerDeferred(long layerId);
- static native void nUpdateRenderLayer(long layerId, long renderer, long displayList,
- int left, int top, int right, int bottom);
static native boolean nCopyLayer(long layerId, long bitmap);
-
private static native void nClearLayerUpdates(long renderer);
private static native void nFlushLayerUpdates(long renderer);
private static native void nPushLayerUpdate(long renderer, long layer);
@@ -286,18 +252,6 @@ class GLES20Canvas extends HardwareCanvas {
private static native int nGetStencilSize();
- void setCountOverdrawEnabled(boolean enabled) {
- nSetCountOverdrawEnabled(mRenderer, enabled);
- }
-
- static native void nSetCountOverdrawEnabled(long renderer, boolean enabled);
-
- float getOverdraw() {
- return nGetOverdraw(mRenderer);
- }
-
- static native float nGetOverdraw(long renderer);
-
///////////////////////////////////////////////////////////////////////////
// Functor
///////////////////////////////////////////////////////////////////////////
@@ -402,22 +356,11 @@ class GLES20Canvas extends HardwareCanvas {
// Display list
///////////////////////////////////////////////////////////////////////////
- long getDisplayList(long displayList) {
- return nGetDisplayList(mRenderer, displayList);
- }
-
- private static native long nGetDisplayList(long renderer, long displayList);
-
- @Override
- void outputDisplayList(DisplayList displayList) {
- nOutputDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList());
- }
-
- private static native void nOutputDisplayList(long renderer, long displayList);
+ protected static native long nFinishRecording(long renderer);
@Override
public int drawDisplayList(DisplayList displayList, Rect dirty, int flags) {
- return nDrawDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList(),
+ return nDrawDisplayList(mRenderer, displayList.getNativeDisplayList(),
dirty, flags);
}
@@ -430,9 +373,7 @@ class GLES20Canvas extends HardwareCanvas {
void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) {
layer.setLayerPaint(paint);
-
- final GLES20Layer glLayer = (GLES20Layer) layer;
- nDrawLayer(mRenderer, glLayer.getLayer(), x, y);
+ nDrawLayer(mRenderer, layer.getLayer(), x, y);
}
private static native void nDrawLayer(long renderer, long layer, float x, float y);
@@ -648,15 +589,8 @@ class GLES20Canvas extends HardwareCanvas {
return saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, paint, saveFlags);
}
- int count;
- int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
- try {
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- count = nSaveLayer(mRenderer, nativePaint, saveFlags);
- } finally {
- if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
- }
- return count;
+ final long nativePaint = paint == null ? 0 : paint.mNativePaint;
+ return nSaveLayer(mRenderer, nativePaint, saveFlags);
}
private static native int nSaveLayer(long renderer, long paint, int saveFlags);
@@ -665,15 +599,8 @@ class GLES20Canvas extends HardwareCanvas {
public int saveLayer(float left, float top, float right, float bottom, Paint paint,
int saveFlags) {
if (left < right && top < bottom) {
- int count;
- int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
- try {
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- count = nSaveLayer(mRenderer, left, top, right, bottom, nativePaint, saveFlags);
- } finally {
- if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
- }
- return count;
+ final long nativePaint = paint == null ? 0 : paint.mNativePaint;
+ return nSaveLayer(mRenderer, left, top, right, bottom, nativePaint, saveFlags);
}
return save(saveFlags);
}
@@ -755,7 +682,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
Paint paint) {
- int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER);
+ int modifiers = setupModifiers(paint, MODIFIER_SHADER);
try {
nDrawArc(mRenderer, oval.left, oval.top, oval.right, oval.bottom,
startAngle, sweepAngle, useCenter, paint.mNativePaint);
@@ -778,14 +705,9 @@ class GLES20Canvas extends HardwareCanvas {
Bitmap bitmap = patch.getBitmap();
throwIfCannotDraw(bitmap);
// Shaders are ignored when drawing patches
- int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
- try {
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, patch.mNativeChunk,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- } finally {
- if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
- }
+ final long nativePaint = paint == null ? 0 : paint.mNativePaint;
+ nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, patch.mNativeChunk,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint);
}
@Override
@@ -793,14 +715,9 @@ class GLES20Canvas extends HardwareCanvas {
Bitmap bitmap = patch.getBitmap();
throwIfCannotDraw(bitmap);
// Shaders are ignored when drawing patches
- int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
- try {
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, patch.mNativeChunk,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- } finally {
- if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
- }
+ final long nativePaint = paint == null ? 0 : paint.mNativePaint;
+ nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, patch.mNativeChunk,
+ dst.left, dst.top, dst.right, dst.bottom, nativePaint);
}
private static native void nDrawPatch(long renderer, long bitmap, byte[] buffer, long chunk,
@@ -921,14 +838,9 @@ class GLES20Canvas extends HardwareCanvas {
}
// Shaders are ignored when drawing bitmaps
- int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
- try {
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmap(mRenderer, colors, offset, stride, x, y,
- width, height, hasAlpha, nativePaint);
- } finally {
- if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
- }
+ final long nativePaint = paint == null ? 0 : paint.mNativePaint;
+ nDrawBitmap(mRenderer, colors, offset, stride, x, y,
+ width, height, hasAlpha, nativePaint);
}
private static native void nDrawBitmap(long renderer, int[] colors, int offset, int stride,
@@ -976,7 +888,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawCircle(float cx, float cy, float radius, Paint paint) {
- int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER);
+ int modifiers = setupModifiers(paint, MODIFIER_SHADER);
try {
nDrawCircle(mRenderer, cx, cy, radius, paint.mNativePaint);
} finally {
@@ -1016,7 +928,7 @@ class GLES20Canvas extends HardwareCanvas {
if ((offset | count) < 0 || offset + count > pts.length) {
throw new IllegalArgumentException("The lines array must contain 4 elements per line.");
}
- int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER);
+ int modifiers = setupModifiers(paint, MODIFIER_SHADER);
try {
nDrawLines(mRenderer, pts, offset, count, paint.mNativePaint);
} finally {
@@ -1034,7 +946,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawOval(RectF oval, Paint paint) {
- int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER);
+ int modifiers = setupModifiers(paint, MODIFIER_SHADER);
try {
nDrawOval(mRenderer, oval.left, oval.top, oval.right, oval.bottom, paint.mNativePaint);
} finally {
@@ -1054,7 +966,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawPath(Path path, Paint paint) {
- int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER);
+ int modifiers = setupModifiers(paint, MODIFIER_SHADER);
try {
if (path.isSimplePath) {
if (path.rects != null) {
@@ -1072,7 +984,7 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nDrawRects(long renderer, long region, long paint);
void drawRects(float[] rects, int count, Paint paint) {
- int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER);
+ int modifiers = setupModifiers(paint, MODIFIER_SHADER);
try {
nDrawRects(mRenderer, rects, count, paint.mNativePaint);
} finally {
@@ -1139,7 +1051,7 @@ class GLES20Canvas extends HardwareCanvas {
public void drawPoints(float[] pts, int offset, int count, Paint paint) {
if (count < 2) return;
- int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER);
+ int modifiers = setupModifiers(paint, MODIFIER_SHADER);
try {
nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint);
} finally {
@@ -1189,7 +1101,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawRect(float left, float top, float right, float bottom, Paint paint) {
if (left == right || top == bottom) return;
- int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER);
+ int modifiers = setupModifiers(paint, MODIFIER_SHADER);
try {
nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint);
} finally {
@@ -1217,7 +1129,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
- int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER);
+ int modifiers = setupModifiers(paint, MODIFIER_SHADER);
try {
nDrawRoundRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
rx, ry, paint.mNativePaint);
@@ -1397,12 +1309,6 @@ class GLES20Canvas extends HardwareCanvas {
private int setupModifiers(Bitmap b, Paint paint) {
if (b.getConfig() != Bitmap.Config.ALPHA_8) {
- final ColorFilter filter = paint.getColorFilter();
- if (filter != null) {
- nSetupColorFilter(mRenderer, filter.nativeColorFilter);
- return MODIFIER_COLOR_FILTER;
- }
-
return MODIFIER_NONE;
} else {
return setupModifiers(paint);
@@ -1424,12 +1330,6 @@ class GLES20Canvas extends HardwareCanvas {
modifiers |= MODIFIER_SHADER;
}
- final ColorFilter filter = paint.getColorFilter();
- if (filter != null) {
- nSetupColorFilter(mRenderer, filter.nativeColorFilter);
- modifiers |= MODIFIER_COLOR_FILTER;
- }
-
return modifiers;
}
@@ -1448,26 +1348,10 @@ class GLES20Canvas extends HardwareCanvas {
modifiers |= MODIFIER_SHADER;
}
- final ColorFilter filter = paint.getColorFilter();
- if (filter != null && (flags & MODIFIER_COLOR_FILTER) != 0) {
- nSetupColorFilter(mRenderer, filter.nativeColorFilter);
- modifiers |= MODIFIER_COLOR_FILTER;
- }
-
return modifiers;
}
- private int setupColorFilter(Paint paint) {
- final ColorFilter filter = paint.getColorFilter();
- if (filter != null) {
- nSetupColorFilter(mRenderer, filter.nativeColorFilter);
- return MODIFIER_COLOR_FILTER;
- }
- return MODIFIER_NONE;
- }
-
private static native void nSetupShader(long renderer, long shader);
- private static native void nSetupColorFilter(long renderer, long colorFilter);
private static native void nSetupShadow(long renderer, float radius,
float dx, float dy, int color);
diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java
deleted file mode 100644
index 7f8b3bd..0000000
--- a/core/java/android/view/GLES20DisplayList.java
+++ /dev/null
@@ -1,511 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.graphics.Matrix;
-
-import java.util.ArrayList;
-
-/**
- * An implementation of display list for OpenGL ES 2.0.
- */
-class GLES20DisplayList extends DisplayList {
- private ArrayList<DisplayList> mChildDisplayLists;
-
- private GLES20RecordingCanvas mCanvas;
- private boolean mValid;
-
- // Used for debugging
- private final String mName;
-
- // The native display list will be destroyed when this object dies.
- // DO NOT overwrite this reference once it is set.
- private DisplayListFinalizer mFinalizer;
-
- GLES20DisplayList(String name) {
- mName = name;
- }
-
- boolean hasNativeDisplayList() {
- return mValid && mFinalizer != null;
- }
-
- long getNativeDisplayList() {
- if (!mValid || mFinalizer == null) {
- throw new IllegalStateException("The display list is not valid.");
- }
- return mFinalizer.mNativeDisplayList;
- }
-
- @Override
- public HardwareCanvas start(int width, int height) {
- if (mCanvas != null) {
- throw new IllegalStateException("Recording has already started");
- }
-
- mValid = false;
- mCanvas = GLES20RecordingCanvas.obtain(this);
- mCanvas.start();
-
- mCanvas.setViewport(width, height);
- // The dirty rect should always be null for a display list
- mCanvas.onPreDraw(null);
-
- return mCanvas;
- }
- @Override
- public void clear() {
- clearDirty();
-
- if (mCanvas != null) {
- mCanvas.recycle();
- mCanvas = null;
- }
- mValid = false;
-
- clearReferences();
- }
-
- void clearReferences() {
- if (mChildDisplayLists != null) mChildDisplayLists.clear();
- }
-
- ArrayList<DisplayList> getChildDisplayLists() {
- if (mChildDisplayLists == null) mChildDisplayLists = new ArrayList<DisplayList>();
- return mChildDisplayLists;
- }
-
- @Override
- public void reset() {
- if (hasNativeDisplayList()) {
- nReset(mFinalizer.mNativeDisplayList);
- }
- clear();
- }
-
- @Override
- public boolean isValid() {
- return mValid;
- }
-
- @Override
- public void end() {
- if (mCanvas != null) {
- mCanvas.onPostDraw();
- if (mFinalizer != null) {
- mCanvas.end(mFinalizer.mNativeDisplayList);
- } else {
- mFinalizer = new DisplayListFinalizer(mCanvas.end(0));
- nSetDisplayListName(mFinalizer.mNativeDisplayList, mName);
- }
- mCanvas.recycle();
- mCanvas = null;
- mValid = true;
- }
- }
-
- @Override
- public int getSize() {
- if (mFinalizer == null) return 0;
- return nGetDisplayListSize(mFinalizer.mNativeDisplayList);
- }
-
- private static native void nDestroyDisplayList(long displayList);
- private static native int nGetDisplayListSize(long displayList);
- private static native void nSetDisplayListName(long displayList, String name);
-
- ///////////////////////////////////////////////////////////////////////////
- // Native View Properties
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public void setCaching(boolean caching) {
- if (hasNativeDisplayList()) {
- nSetCaching(mFinalizer.mNativeDisplayList, caching);
- }
- }
-
- @Override
- public void setClipToBounds(boolean clipToBounds) {
- if (hasNativeDisplayList()) {
- nSetClipToBounds(mFinalizer.mNativeDisplayList, clipToBounds);
- }
- }
-
- @Override
- public void setMatrix(Matrix matrix) {
- if (hasNativeDisplayList()) {
- nSetStaticMatrix(mFinalizer.mNativeDisplayList, matrix.native_instance);
- }
- }
-
- @Override
- public Matrix getMatrix(Matrix matrix) {
- if (hasNativeDisplayList()) {
- nGetMatrix(mFinalizer.mNativeDisplayList, matrix.native_instance);
- }
- return matrix;
- }
-
- @Override
- public void setAnimationMatrix(Matrix matrix) {
- if (hasNativeDisplayList()) {
- nSetAnimationMatrix(mFinalizer.mNativeDisplayList,
- (matrix != null) ? matrix.native_instance : 0);
- }
- }
-
- @Override
- public void setAlpha(float alpha) {
- if (hasNativeDisplayList()) {
- nSetAlpha(mFinalizer.mNativeDisplayList, alpha);
- }
- }
-
- @Override
- public float getAlpha() {
- if (hasNativeDisplayList()) {
- return nGetAlpha(mFinalizer.mNativeDisplayList);
- }
- return 1.0f;
- }
-
- @Override
- public void setHasOverlappingRendering(boolean hasOverlappingRendering) {
- if (hasNativeDisplayList()) {
- nSetHasOverlappingRendering(mFinalizer.mNativeDisplayList, hasOverlappingRendering);
- }
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- //noinspection SimplifiableIfStatement
- if (hasNativeDisplayList()) {
- return nHasOverlappingRendering(mFinalizer.mNativeDisplayList);
- }
- return true;
- }
-
- @Override
- public void setTranslationX(float translationX) {
- if (hasNativeDisplayList()) {
- nSetTranslationX(mFinalizer.mNativeDisplayList, translationX);
- }
- }
-
- @Override
- public float getTranslationX() {
- if (hasNativeDisplayList()) {
- return nGetTranslationX(mFinalizer.mNativeDisplayList);
- }
- return 0.0f;
- }
-
- @Override
- public void setTranslationY(float translationY) {
- if (hasNativeDisplayList()) {
- nSetTranslationY(mFinalizer.mNativeDisplayList, translationY);
- }
- }
-
- @Override
- public float getTranslationY() {
- if (hasNativeDisplayList()) {
- return nGetTranslationY(mFinalizer.mNativeDisplayList);
- }
- return 0.0f;
- }
-
- @Override
- public void setRotation(float rotation) {
- if (hasNativeDisplayList()) {
- nSetRotation(mFinalizer.mNativeDisplayList, rotation);
- }
- }
-
- @Override
- public float getRotation() {
- if (hasNativeDisplayList()) {
- return nGetRotation(mFinalizer.mNativeDisplayList);
- }
- return 0.0f;
- }
-
- @Override
- public void setRotationX(float rotationX) {
- if (hasNativeDisplayList()) {
- nSetRotationX(mFinalizer.mNativeDisplayList, rotationX);
- }
- }
-
- @Override
- public float getRotationX() {
- if (hasNativeDisplayList()) {
- return nGetRotationX(mFinalizer.mNativeDisplayList);
- }
- return 0.0f;
- }
-
- @Override
- public void setRotationY(float rotationY) {
- if (hasNativeDisplayList()) {
- nSetRotationY(mFinalizer.mNativeDisplayList, rotationY);
- }
- }
-
- @Override
- public float getRotationY() {
- if (hasNativeDisplayList()) {
- return nGetRotationY(mFinalizer.mNativeDisplayList);
- }
- return 0.0f;
- }
-
- @Override
- public void setScaleX(float scaleX) {
- if (hasNativeDisplayList()) {
- nSetScaleX(mFinalizer.mNativeDisplayList, scaleX);
- }
- }
-
- @Override
- public float getScaleX() {
- if (hasNativeDisplayList()) {
- return nGetScaleX(mFinalizer.mNativeDisplayList);
- }
- return 1.0f;
- }
-
- @Override
- public void setScaleY(float scaleY) {
- if (hasNativeDisplayList()) {
- nSetScaleY(mFinalizer.mNativeDisplayList, scaleY);
- }
- }
-
- @Override
- public float getScaleY() {
- if (hasNativeDisplayList()) {
- return nGetScaleY(mFinalizer.mNativeDisplayList);
- }
- return 1.0f;
- }
-
- @Override
- public void setTransformationInfo(float alpha, float translationX, float translationY,
- float rotation, float rotationX, float rotationY, float scaleX, float scaleY) {
- if (hasNativeDisplayList()) {
- nSetTransformationInfo(mFinalizer.mNativeDisplayList, alpha, translationX, translationY,
- rotation, rotationX, rotationY, scaleX, scaleY);
- }
- }
-
- @Override
- public void setPivotX(float pivotX) {
- if (hasNativeDisplayList()) {
- nSetPivotX(mFinalizer.mNativeDisplayList, pivotX);
- }
- }
-
- @Override
- public float getPivotX() {
- if (hasNativeDisplayList()) {
- return nGetPivotX(mFinalizer.mNativeDisplayList);
- }
- return 0.0f;
- }
-
- @Override
- public void setPivotY(float pivotY) {
- if (hasNativeDisplayList()) {
- nSetPivotY(mFinalizer.mNativeDisplayList, pivotY);
- }
- }
-
- @Override
- public float getPivotY() {
- if (hasNativeDisplayList()) {
- return nGetPivotY(mFinalizer.mNativeDisplayList);
- }
- return 0.0f;
- }
-
- @Override
- public void setCameraDistance(float distance) {
- if (hasNativeDisplayList()) {
- nSetCameraDistance(mFinalizer.mNativeDisplayList, distance);
- }
- }
-
- @Override
- public float getCameraDistance() {
- if (hasNativeDisplayList()) {
- return nGetCameraDistance(mFinalizer.mNativeDisplayList);
- }
- return 0.0f;
- }
-
- @Override
- public void setLeft(int left) {
- if (hasNativeDisplayList()) {
- nSetLeft(mFinalizer.mNativeDisplayList, left);
- }
- }
-
- @Override
- public float getLeft() {
- if (hasNativeDisplayList()) {
- return nGetLeft(mFinalizer.mNativeDisplayList);
- }
- return 0.0f;
- }
-
- @Override
- public void setTop(int top) {
- if (hasNativeDisplayList()) {
- nSetTop(mFinalizer.mNativeDisplayList, top);
- }
- }
-
- @Override
- public float getTop() {
- if (hasNativeDisplayList()) {
- return nGetTop(mFinalizer.mNativeDisplayList);
- }
- return 0.0f;
- }
-
- @Override
- public void setRight(int right) {
- if (hasNativeDisplayList()) {
- nSetRight(mFinalizer.mNativeDisplayList, right);
- }
- }
-
- @Override
- public float getRight() {
- if (hasNativeDisplayList()) {
- return nGetRight(mFinalizer.mNativeDisplayList);
- }
- return 0.0f;
- }
-
- @Override
- public void setBottom(int bottom) {
- if (hasNativeDisplayList()) {
- nSetBottom(mFinalizer.mNativeDisplayList, bottom);
- }
- }
-
- @Override
- public float getBottom() {
- if (hasNativeDisplayList()) {
- return nGetBottom(mFinalizer.mNativeDisplayList);
- }
- return 0.0f;
- }
-
- @Override
- public void setLeftTopRightBottom(int left, int top, int right, int bottom) {
- if (hasNativeDisplayList()) {
- nSetLeftTopRightBottom(mFinalizer.mNativeDisplayList, left, top, right, bottom);
- }
- }
-
- @Override
- public void offsetLeftAndRight(float offset) {
- if (hasNativeDisplayList()) {
- nOffsetLeftAndRight(mFinalizer.mNativeDisplayList, offset);
- }
- }
-
- @Override
- public void offsetTopAndBottom(float offset) {
- if (hasNativeDisplayList()) {
- nOffsetTopAndBottom(mFinalizer.mNativeDisplayList, offset);
- }
- }
-
- private static native void nReset(long displayList);
- private static native void nOffsetTopAndBottom(long displayList, float offset);
- private static native void nOffsetLeftAndRight(long displayList, float offset);
- private static native void nSetLeftTopRightBottom(long displayList, int left, int top,
- int right, int bottom);
- private static native void nSetBottom(long displayList, int bottom);
- private static native void nSetRight(long displayList, int right);
- private static native void nSetTop(long displayList, int top);
- private static native void nSetLeft(long displayList, int left);
- private static native void nSetCameraDistance(long displayList, float distance);
- private static native void nSetPivotY(long displayList, float pivotY);
- private static native void nSetPivotX(long displayList, float pivotX);
- private static native void nSetCaching(long displayList, boolean caching);
- private static native void nSetClipToBounds(long displayList, boolean clipToBounds);
- private static native void nSetAlpha(long displayList, float alpha);
- private static native void nSetHasOverlappingRendering(long displayList,
- boolean hasOverlappingRendering);
- private static native void nSetTranslationX(long displayList, float translationX);
- private static native void nSetTranslationY(long displayList, float translationY);
- private static native void nSetRotation(long displayList, float rotation);
- private static native void nSetRotationX(long displayList, float rotationX);
- private static native void nSetRotationY(long displayList, float rotationY);
- private static native void nSetScaleX(long displayList, float scaleX);
- private static native void nSetScaleY(long displayList, float scaleY);
- private static native void nSetTransformationInfo(long displayList, float alpha,
- float translationX, float translationY, float rotation, float rotationX,
- float rotationY, float scaleX, float scaleY);
- private static native void nSetStaticMatrix(long displayList, long nativeMatrix);
- private static native void nSetAnimationMatrix(long displayList, long animationMatrix);
-
- private static native boolean nHasOverlappingRendering(long displayList);
- private static native void nGetMatrix(long displayList, long matrix);
- private static native float nGetAlpha(long displayList);
- private static native float nGetLeft(long displayList);
- private static native float nGetTop(long displayList);
- private static native float nGetRight(long displayList);
- private static native float nGetBottom(long displayList);
- private static native float nGetCameraDistance(long displayList);
- private static native float nGetScaleX(long displayList);
- private static native float nGetScaleY(long displayList);
- private static native float nGetTranslationX(long displayList);
- private static native float nGetTranslationY(long displayList);
- private static native float nGetRotation(long displayList);
- private static native float nGetRotationX(long displayList);
- private static native float nGetRotationY(long displayList);
- private static native float nGetPivotX(long displayList);
- private static native float nGetPivotY(long displayList);
-
- ///////////////////////////////////////////////////////////////////////////
- // Finalization
- ///////////////////////////////////////////////////////////////////////////
-
- private static class DisplayListFinalizer {
- final long mNativeDisplayList;
-
- public DisplayListFinalizer(long nativeDisplayList) {
- mNativeDisplayList = nativeDisplayList;
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- nDestroyDisplayList(mNativeDisplayList);
- } finally {
- super.finalize();
- }
- }
- }
-}
diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java
deleted file mode 100644
index 37154eb..0000000
--- a/core/java/android/view/GLES20Layer.java
+++ /dev/null
@@ -1,100 +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.view;
-
-import android.graphics.Bitmap;
-import android.graphics.Paint;
-
-/**
- * An OpenGL ES 2.0 implementation of {@link HardwareLayer}.
- */
-abstract class GLES20Layer extends HardwareLayer {
- long mLayer;
- Finalizer mFinalizer;
-
- GLES20Layer() {
- }
-
- GLES20Layer(int width, int height, boolean opaque) {
- super(width, height, opaque);
- }
-
- /**
- * Returns the native layer object used to render this layer.
- *
- * @return A pointer to the native layer object, or 0 if the object is NULL
- */
- public long getLayer() {
- return mLayer;
- }
-
- @Override
- void setLayerPaint(Paint paint) {
- if (paint != null) {
- GLES20Canvas.nSetLayerPaint(mLayer, paint.mNativePaint);
- GLES20Canvas.nSetLayerColorFilter(mLayer, paint.getColorFilter() != null ?
- paint.getColorFilter().nativeColorFilter : 0);
- }
- }
-
- @Override
- public boolean copyInto(Bitmap bitmap) {
- return GLES20Canvas.nCopyLayer(mLayer, bitmap.mNativeBitmap);
- }
-
- @Override
- public void destroy() {
- if (mDisplayList != null) {
- mDisplayList.reset();
- }
- if (mFinalizer != null) {
- mFinalizer.destroy();
- mFinalizer = null;
- }
- mLayer = 0;
- }
-
- @Override
- void clearStorage() {
- if (mLayer != 0) GLES20Canvas.nClearLayerTexture(mLayer);
- }
-
- static class Finalizer {
- private long mLayerId;
-
- public Finalizer(long layerId) {
- mLayerId = layerId;
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mLayerId != 0) {
- GLES20Canvas.nDestroyLayerDeferred(mLayerId);
- }
- } finally {
- super.finalize();
- }
- }
-
- void destroy() {
- GLES20Canvas.nDestroyLayer(mLayerId);
- mLayerId = 0;
- }
- }
-}
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index e3e1c76..2b29e5c 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -16,7 +16,6 @@
package android.view;
-import android.graphics.Rect;
import android.util.Pools.SynchronizedPool;
/**
@@ -33,39 +32,23 @@ class GLES20RecordingCanvas extends GLES20Canvas {
private static final SynchronizedPool<GLES20RecordingCanvas> sPool =
new SynchronizedPool<GLES20RecordingCanvas>(POOL_LIMIT);
- private GLES20DisplayList mDisplayList;
-
private GLES20RecordingCanvas() {
super(true, true);
}
- static GLES20RecordingCanvas obtain(GLES20DisplayList displayList) {
+ static GLES20RecordingCanvas obtain() {
GLES20RecordingCanvas canvas = sPool.acquire();
if (canvas == null) {
canvas = new GLES20RecordingCanvas();
}
- canvas.mDisplayList = displayList;
return canvas;
}
void recycle() {
- mDisplayList = null;
- resetDisplayListRenderer();
sPool.release(this);
}
- void start() {
- mDisplayList.clearReferences();
- }
-
- long end(long nativeDisplayList) {
- return getDisplayList(nativeDisplayList);
- }
-
- @Override
- public int drawDisplayList(DisplayList displayList, Rect dirty, int flags) {
- int status = super.drawDisplayList(displayList, dirty, flags);
- mDisplayList.getChildDisplayLists().add(displayList);
- return status;
+ long finishRecording() {
+ return nFinishRecording(mRenderer);
}
}
diff --git a/core/java/android/view/GLES20RenderLayer.java b/core/java/android/view/GLES20RenderLayer.java
deleted file mode 100644
index 68ba77c..0000000
--- a/core/java/android/view/GLES20RenderLayer.java
+++ /dev/null
@@ -1,130 +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.view;
-
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-
-/**
- * An OpenGL ES 2.0 implementation of {@link HardwareLayer}. This
- * implementation can be used a rendering target. It generates a
- * {@link Canvas} that can be used to render into an FBO using OpenGL.
- */
-class GLES20RenderLayer extends GLES20Layer {
- private int mLayerWidth;
- private int mLayerHeight;
-
- private final GLES20Canvas mCanvas;
-
- GLES20RenderLayer(int width, int height, boolean isOpaque) {
- super(width, height, isOpaque);
-
- int[] layerInfo = new int[2];
- mLayer = GLES20Canvas.nCreateLayer(width, height, isOpaque, layerInfo);
- if (mLayer != 0) {
- mLayerWidth = layerInfo[0];
- mLayerHeight = layerInfo[1];
-
- mCanvas = new GLES20Canvas(mLayer, !isOpaque);
- mFinalizer = new Finalizer(mLayer);
- } else {
- mCanvas = null;
- mFinalizer = null;
- }
- }
-
- @Override
- boolean isValid() {
- return mLayer != 0 && mLayerWidth > 0 && mLayerHeight > 0;
- }
-
- @Override
- boolean resize(int width, int height) {
- if (!isValid() || width <= 0 || height <= 0) return false;
-
- mWidth = width;
- mHeight = height;
-
- if (width != mLayerWidth || height != mLayerHeight) {
- int[] layerInfo = new int[2];
-
- if (GLES20Canvas.nResizeLayer(mLayer, width, height, layerInfo)) {
- mLayerWidth = layerInfo[0];
- mLayerHeight = layerInfo[1];
- } else {
- // Failure: not enough GPU resources for requested size
- mLayer = 0;
- mLayerWidth = 0;
- mLayerHeight = 0;
- }
- }
- return isValid();
- }
-
- @Override
- void setOpaque(boolean isOpaque) {
- mOpaque = isOpaque;
- GLES20Canvas.nSetOpaqueLayer(mLayer, isOpaque);
- }
-
- @Override
- HardwareCanvas getCanvas() {
- return mCanvas;
- }
-
- @Override
- void end(Canvas currentCanvas) {
- HardwareCanvas canvas = getCanvas();
- if (canvas != null) {
- canvas.onPostDraw();
- }
- if (currentCanvas instanceof GLES20Canvas) {
- ((GLES20Canvas) currentCanvas).resume();
- }
- }
-
- @Override
- HardwareCanvas start(Canvas currentCanvas) {
- return start(currentCanvas, null);
- }
-
- @Override
- HardwareCanvas start(Canvas currentCanvas, Rect dirty) {
- if (currentCanvas instanceof GLES20Canvas) {
- ((GLES20Canvas) currentCanvas).interrupt();
- }
- HardwareCanvas canvas = getCanvas();
- canvas.setViewport(mWidth, mHeight);
- canvas.onPreDraw(dirty);
- return canvas;
- }
-
- /**
- * Ignored
- */
- @Override
- void setTransform(Matrix matrix) {
- }
-
- @Override
- void redrawLater(DisplayList displayList, Rect dirtyRect) {
- GLES20Canvas.nUpdateRenderLayer(mLayer, mCanvas.getRenderer(),
- ((GLES20DisplayList) displayList).getNativeDisplayList(),
- dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom);
- }
-}
diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java
deleted file mode 100644
index bb5a6eb..0000000
--- a/core/java/android/view/GLES20TextureLayer.java
+++ /dev/null
@@ -1,108 +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.view;
-
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.SurfaceTexture;
-
-/**
- * An OpenGL ES 2.0 implementation of {@link HardwareLayer}. This
- * implementation can be used as a texture. Rendering into this
- * layer is not controlled by a {@link HardwareCanvas}.
- */
-class GLES20TextureLayer extends GLES20Layer {
- private int mTexture;
- private SurfaceTexture mSurface;
-
- GLES20TextureLayer(boolean isOpaque) {
- int[] layerInfo = new int[2];
- mLayer = GLES20Canvas.nCreateTextureLayer(isOpaque, layerInfo);
-
- if (mLayer != 0) {
- mTexture = layerInfo[0];
- mFinalizer = new Finalizer(mLayer);
- } else {
- mFinalizer = null;
- }
- }
-
- @Override
- boolean isValid() {
- return mLayer != 0 && mTexture != 0;
- }
-
- @Override
- boolean resize(int width, int height) {
- return isValid();
- }
-
- @Override
- HardwareCanvas getCanvas() {
- return null;
- }
-
- @Override
- HardwareCanvas start(Canvas currentCanvas) {
- return null;
- }
-
- @Override
- HardwareCanvas start(Canvas currentCanvas, Rect dirty) {
- return null;
- }
-
- @Override
- void end(Canvas currentCanvas) {
- }
-
- SurfaceTexture getSurfaceTexture() {
- if (mSurface == null) {
- mSurface = new SurfaceTexture(mTexture);
- }
- return mSurface;
- }
-
- void setSurfaceTexture(SurfaceTexture surfaceTexture) {
- if (mSurface != null) {
- mSurface.release();
- }
- mSurface = surfaceTexture;
- mSurface.attachToGLContext(mTexture);
- }
-
- @Override
- void update(int width, int height, boolean isOpaque) {
- super.update(width, height, isOpaque);
- GLES20Canvas.nUpdateTextureLayer(mLayer, width, height, isOpaque, mSurface);
- }
-
- @Override
- void setOpaque(boolean isOpaque) {
- throw new UnsupportedOperationException("Use update(int, int, boolean) instead");
- }
-
- @Override
- void setTransform(Matrix matrix) {
- GLES20Canvas.nSetTextureLayerTransform(mLayer, matrix.native_instance);
- }
-
- @Override
- void redrawLater(DisplayList displayList, Rect dirtyRect) {
- }
-}
diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java
new file mode 100644
index 0000000..81f778d
--- /dev/null
+++ b/core/java/android/view/GLRenderer.java
@@ -0,0 +1,1554 @@
+/*
+ * 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.Bitmap;
+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.ArrayList;
+import java.util.List;
+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,
+ };
+ 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;
+
+ 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 List<HardwareLayer> mAttachedLayers = new ArrayList<HardwareLayer>();
+
+ 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) {
+ long[] 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 flushLayerUpdates() {
+ if (validate()) {
+ flushLayerChanges();
+ mGlCanvas.flushLayerUpdates();
+ }
+ }
+
+ @Override
+ HardwareLayer createTextureLayer() {
+ validate();
+ return HardwareLayer.createTextureLayer(this);
+ }
+
+ @Override
+ public HardwareLayer createDisplayListLayer(int width, int height) {
+ validate();
+ return HardwareLayer.createDisplayListLayer(this, width, height);
+ }
+
+ @Override
+ void onLayerCreated(HardwareLayer hardwareLayer) {
+ mAttachedLayers.add(hardwareLayer);
+ }
+
+ boolean hasContext() {
+ return sEgl != null && mEglContext != null
+ && mEglContext.equals(sEgl.eglGetCurrentContext());
+ }
+
+ @Override
+ void onLayerDestroyed(HardwareLayer layer) {
+ if (mGlCanvas != null) {
+ mGlCanvas.cancelLayerUpdate(layer);
+ }
+ if (hasContext()) {
+ long backingLayer = layer.detachBackingLayer();
+ nDestroyLayer(backingLayer);
+ }
+ mAttachedLayers.remove(layer);
+ }
+
+ @Override
+ public SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
+ return layer.createSurfaceTexture();
+ }
+
+ @Override
+ boolean copyLayerInto(HardwareLayer layer, Bitmap bitmap) {
+ if (!validate()) {
+ throw new IllegalStateException("Could not acquire hardware rendering context");
+ }
+ layer.flushChanges();
+ return GLES20Canvas.nCopyLayer(layer.getLayer(), bitmap.mNativeBitmap);
+ }
+
+ @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 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();
+ }
+
+ @Override
+ boolean loadSystemProperties() {
+ 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 (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();
+ }
+ 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;
+ }
+
+ 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
+ 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;
+
+ if (mProfileEnabled) {
+ mProfileLock.lock();
+ }
+
+ dirty = beginFrame(canvas, dirty, surfaceState);
+
+ DisplayList displayList = buildDisplayList(view, canvas);
+
+ flushLayerChanges();
+
+ // 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++;
+
+ debugDirtyRegions(dirty, canvas);
+ drawProfileData(attachInfo);
+ }
+ }
+
+ onPostDraw();
+
+ swapBuffers(status);
+
+ if (mProfileEnabled) {
+ mProfileLock.unlock();
+ }
+
+ attachInfo.mIgnoreDirtyState = false;
+ }
+ }
+ }
+
+ private void flushLayerChanges() {
+ // Loop through and apply any pending layer changes
+ for (int i = 0; i < mAttachedLayers.size(); i++) {
+ HardwareLayer layer = mAttachedLayers.get(i);
+ layer.flushChanges();
+ if (!layer.isValid()) {
+ // The layer was removed from mAttachedLayers, rewind i by 1
+ // Note that this shouldn't actually happen as View.getHardwareLayer()
+ // is already flushing for error checking reasons
+ i--;
+ }
+ }
+ }
+
+ void setDisplayListData(long displayList, long newData) {
+ nSetDisplayListData(displayList, newData);
+ }
+ private static native void nSetDisplayListData(long displayList, long newData);
+
+ 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(long functor) {
+ if (mCanvas != null) {
+ mCanvas.detachFunctor(functor);
+ }
+ }
+
+ @Override
+ void attachFunctor(View.AttachInfo attachInfo, long functor) {
+ if (mCanvas != null) {
+ mCanvas.attachFunctor(functor);
+ mFunctorsRunnable.attachInfo = attachInfo;
+ attachInfo.mHandler.removeCallbacks(mFunctorsRunnable);
+ attachInfo.mHandler.postDelayed(mFunctorsRunnable, 0);
+ }
+ }
+
+ /**
+ * 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);
+ }
+
+ static native boolean loadProperties();
+
+ static native void setupShadersDiskCache(String cacheFile);
+
+ /**
+ * Notifies EGL that the frame is about to be rendered.
+ * @param size
+ */
+ 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.
+ */
+ 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.
+ */
+ 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
+ */
+ static native boolean isBackBufferPreserved();
+
+ static native void nDestroyLayer(long layerPtr);
+
+ 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/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index 10f700c..a3c7b63 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -27,7 +27,6 @@ import android.graphics.Rect;
* @hide
*/
public abstract class HardwareCanvas extends Canvas {
- private String mName;
@Override
public boolean isHardwareAccelerated() {
@@ -40,33 +39,6 @@ public abstract class HardwareCanvas extends Canvas {
}
/**
- * Specifies the name of this canvas. Naming the canvas is entirely
- * optional but can be useful for debugging purposes.
- *
- * @param name The name of the canvas, can be null
- *
- * @see #getName()
- *
- * @hide
- */
- public void setName(String name) {
- mName = name;
- }
-
- /**
- * Returns the name of this canvas.
- *
- * @return The name of the canvas or null
- *
- * @see #setName(String)
- *
- * @hide
- */
- public String getName() {
- return mName;
- }
-
- /**
* Invoked before any drawing operation is performed in this canvas.
*
* @param dirty The dirty rectangle to update, can be null.
@@ -112,16 +84,6 @@ public abstract class HardwareCanvas extends Canvas {
public abstract int drawDisplayList(DisplayList displayList, Rect dirty, int flags);
/**
- * Outputs the specified display list to the log. This method exists for use by
- * tools to output display lists for selected nodes to the log.
- *
- * @param displayList The display list to be logged.
- *
- * @hide
- */
- abstract void outputDisplayList(DisplayList displayList);
-
- /**
* Draws the specified layer onto this canvas.
*
* @param layer The layer to composite on this canvas
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index 23383d9..46e2690 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -17,10 +17,10 @@
package android.view;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
/**
* A hardware layer can be used to render graphics operations into a hardware
@@ -28,38 +28,35 @@ import android.graphics.Rect;
* would use a Frame Buffer Object (FBO.) The hardware layer can be used as
* a drawing cache when a complex set of graphics operations needs to be
* drawn several times.
+ *
+ * @hide
*/
-abstract class HardwareLayer {
- /**
- * Indicates an unknown dimension (width or height.)
- */
- static final int DIMENSION_UNDEFINED = -1;
-
- int mWidth;
- int mHeight;
- DisplayList mDisplayList;
+final class HardwareLayer {
+ private static final int LAYER_TYPE_TEXTURE = 1;
+ private static final int LAYER_TYPE_DISPLAY_LIST = 2;
- boolean mOpaque;
+ private HardwareRenderer mRenderer;
+ private Finalizer mFinalizer;
+ private DisplayList mDisplayList;
+ private final int mLayerType;
- /**
- * Creates a new hardware layer with undefined dimensions.
- */
- HardwareLayer() {
- this(DIMENSION_UNDEFINED, DIMENSION_UNDEFINED, false);
+ private HardwareLayer(HardwareRenderer renderer, long deferredUpdater, int type) {
+ if (renderer == null || deferredUpdater == 0) {
+ throw new IllegalArgumentException("Either hardware renderer: " + renderer
+ + " or deferredUpdater: " + deferredUpdater + " is invalid");
+ }
+ mRenderer = renderer;
+ mLayerType = type;
+ mFinalizer = new Finalizer(deferredUpdater);
+
+ // Layer is considered initialized at this point, notify the HardwareRenderer
+ mRenderer.onLayerCreated(this);
}
- /**
- * Creates a new hardware layer at least as large as the supplied
- * dimensions.
- *
- * @param width The minimum width of the layer
- * @param height The minimum height of the layer
- * @param isOpaque Whether the layer should be opaque or not
- */
- HardwareLayer(int width, int height, boolean isOpaque) {
- mWidth = width;
- mHeight = height;
- mOpaque = isOpaque;
+ private void assertType(int type) {
+ if (mLayerType != type) {
+ throw new IllegalAccessError("Method not appropriate for this layer type! " + mLayerType);
+ }
}
/**
@@ -68,158 +65,244 @@ abstract class HardwareLayer {
* @param paint The paint used when the layer is drawn into the destination canvas.
* @see View#setLayerPaint(android.graphics.Paint)
*/
- void setLayerPaint(Paint paint) { }
+ public void setLayerPaint(Paint paint) {
+ nSetLayerPaint(mFinalizer.mDeferredUpdater, paint.mNativePaint);
+ }
/**
- * Returns the minimum width of the layer.
- *
- * @return The minimum desired width of the hardware layer
+ * Indicates whether this layer can be rendered.
+ *
+ * @return True if the layer can be rendered into, false otherwise
*/
- int getWidth() {
- return mWidth;
+ public boolean isValid() {
+ return mFinalizer != null && mFinalizer.mDeferredUpdater != 0;
}
/**
- * Returns the minimum height of the layer.
- *
- * @return The minimum desired height of the hardware layer
+ * Destroys resources without waiting for a GC.
*/
- int getHeight() {
- return mHeight;
+ public void destroy() {
+ if (!isValid()) {
+ // Already destroyed
+ return;
+ }
+
+ if (mDisplayList != null) {
+ mDisplayList.destroyDisplayListData();
+ mDisplayList = null;
+ }
+ if (mRenderer != null) {
+ mRenderer.onLayerDestroyed(this);
+ mRenderer = null;
+ }
+ doDestroyLayerUpdater();
}
- /**
- * Returns the DisplayList for the layer.
- *
- * @return The DisplayList of the hardware layer
- */
- DisplayList getDisplayList() {
- return mDisplayList;
+ public long getDeferredLayerUpdater() {
+ return mFinalizer.mDeferredUpdater;
}
/**
- * Sets the DisplayList for the layer.
+ * Destroys the deferred layer updater but not the backing layer. The
+ * backing layer is instead returned and is the caller's responsibility
+ * to destroy/recycle as appropriate.
*
- * @param displayList The new DisplayList for this layer
+ * It is safe to call this in onLayerDestroyed only
*/
- void setDisplayList(DisplayList displayList) {
- mDisplayList = displayList;
+ public long detachBackingLayer() {
+ long backingLayer = nDetachBackingLayer(mFinalizer.mDeferredUpdater);
+ doDestroyLayerUpdater();
+ return backingLayer;
}
- /**
- * Returns whether or not this layer is opaque.
- *
- * @return True if the layer is opaque, false otherwise
- */
- boolean isOpaque() {
- return mOpaque;
+ private void doDestroyLayerUpdater() {
+ if (mFinalizer != null) {
+ mFinalizer.destroy();
+ mFinalizer = null;
+ }
}
- /**
- * Sets whether or not this layer should be considered opaque.
- *
- * @param isOpaque True if the layer is opaque, false otherwise
- */
- abstract void setOpaque(boolean isOpaque);
+ public DisplayList startRecording() {
+ assertType(LAYER_TYPE_DISPLAY_LIST);
- /**
- * Indicates whether this layer can be rendered.
- *
- * @return True if the layer can be rendered into, false otherwise
- */
- abstract boolean isValid();
+ if (mDisplayList == null) {
+ mDisplayList = DisplayList.create("HardwareLayer");
+ }
+ return mDisplayList;
+ }
- /**
- * Resize the layer, if necessary, to be at least as large
- * as the supplied dimensions.
- *
- * @param width The new desired minimum width for this layer
- * @param height The new desired minimum height for this layer
- * @return True if the resulting layer is valid, false otherwise
- */
- abstract boolean resize(int width, int height);
+ public void endRecording(Rect dirtyRect) {
+ nUpdateRenderLayer(mFinalizer.mDeferredUpdater, mDisplayList.getNativeDisplayList(),
+ dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom);
+ mRenderer.pushLayerUpdate(this);
+ }
/**
- * Returns a hardware canvas that can be used to render onto
- * this layer.
- *
- * @return A hardware canvas, or null if a canvas cannot be created
+ * Copies this layer into the specified bitmap.
*
- * @see #start(android.graphics.Canvas)
- * @see #end(android.graphics.Canvas)
- */
- abstract HardwareCanvas getCanvas();
-
- /**
- * Destroys resources without waiting for a GC.
+ * @param bitmap The bitmap to copy they layer into
+ *
+ * @return True if the copy was successful, false otherwise
*/
- abstract void destroy();
+ public boolean copyInto(Bitmap bitmap) {
+ return mRenderer.copyLayerInto(this, bitmap);
+ }
/**
- * This must be invoked before drawing onto this layer.
+ * Update the layer's properties. Note that after calling this isValid() may
+ * return false if the requested width/height cannot be satisfied
+ *
+ * @param width The new width of this layer
+ * @param height The new height of this layer
+ * @param isOpaque Whether this layer is opaque
*
- * @param currentCanvas The canvas whose rendering needs to be interrupted
+ * @return true if the layer's properties will change, false if they already
+ * match the desired values.
*/
- abstract HardwareCanvas start(Canvas currentCanvas);
+ public boolean prepare(int width, int height, boolean isOpaque) {
+ return nPrepare(mFinalizer.mDeferredUpdater, width, height, isOpaque);
+ }
/**
- * This must be invoked before drawing onto this layer.
+ * Sets an optional transform on this layer.
*
- * @param dirty The dirty area to repaint
- * @param currentCanvas The canvas whose rendering needs to be interrupted
+ * @param matrix The transform to apply to the layer.
*/
- abstract HardwareCanvas start(Canvas currentCanvas, Rect dirty);
+ public void setTransform(Matrix matrix) {
+ nSetTransform(mFinalizer.mDeferredUpdater, matrix.native_instance);
+ }
/**
- * This must be invoked after drawing onto this layer.
- *
- * @param currentCanvas The canvas whose rendering needs to be resumed
+ * Indicates that this layer has lost its texture.
*/
- abstract void end(Canvas currentCanvas);
+ public void detachSurfaceTexture(final SurfaceTexture surface) {
+ assertType(LAYER_TYPE_TEXTURE);
+ mRenderer.safelyRun(new Runnable() {
+ @Override
+ public void run() {
+ surface.detachFromGLContext();
+ // SurfaceTexture owns the texture name and detachFromGLContext
+ // should have deleted it
+ nOnTextureDestroyed(mFinalizer.mDeferredUpdater);
+ }
+ });
+ }
/**
- * Copies this layer into the specified bitmap.
- *
- * @param bitmap The bitmap to copy they layer into
- *
- * @return True if the copy was successful, false otherwise
+ * This exists to minimize impact into the current HardwareLayer paths as
+ * some of the specifics of how to handle error cases in the fully
+ * deferred model will work
*/
- abstract boolean copyInto(Bitmap bitmap);
+ @Deprecated
+ public void flushChanges() {
+ if (HardwareRenderer.sUseRenderThread) {
+ // Not supported, don't try.
+ return;
+ }
+
+ boolean success = nFlushChanges(mFinalizer.mDeferredUpdater);
+ if (!success) {
+ destroy();
+ }
+ }
+
+ public long getLayer() {
+ return nGetLayer(mFinalizer.mDeferredUpdater);
+ }
+
+ public void setSurfaceTexture(SurfaceTexture surface) {
+ assertType(LAYER_TYPE_TEXTURE);
+ nSetSurfaceTexture(mFinalizer.mDeferredUpdater, surface, false);
+ }
+
+ public void updateSurfaceTexture() {
+ assertType(LAYER_TYPE_TEXTURE);
+ nUpdateSurfaceTexture(mFinalizer.mDeferredUpdater);
+ }
/**
- * Update the layer's properties. This method should be used
- * when the underlying storage is modified by an external entity.
- * To change the underlying storage, use the {@link #resize(int, int)}
- * method instead.
- *
- * @param width The new width of this layer
- * @param height The new height of this layer
- * @param isOpaque Whether this layer is opaque
+ * This should only be used by HardwareRenderer! Do not call directly
*/
- void update(int width, int height, boolean isOpaque) {
- mWidth = width;
- mHeight = height;
- mOpaque = isOpaque;
+ SurfaceTexture createSurfaceTexture() {
+ assertType(LAYER_TYPE_TEXTURE);
+ SurfaceTexture st = new SurfaceTexture(nGetTexName(mFinalizer.mDeferredUpdater));
+ nSetSurfaceTexture(mFinalizer.mDeferredUpdater, st, true);
+ return st;
}
/**
- * Sets an optional transform on this layer.
- *
- * @param matrix The transform to apply to the layer.
+ * This should only be used by HardwareRenderer! Do not call directly
*/
- abstract void setTransform(Matrix matrix);
+ static HardwareLayer createTextureLayer(HardwareRenderer renderer) {
+ return new HardwareLayer(renderer, nCreateTextureLayer(), LAYER_TYPE_TEXTURE);
+ }
+
+ static HardwareLayer adoptTextureLayer(HardwareRenderer renderer, long layer) {
+ return new HardwareLayer(renderer, layer, LAYER_TYPE_TEXTURE);
+ }
/**
- * Specifies the display list to use to refresh the layer.
- *
- * @param displayList The display list containing the drawing commands to
- * execute in this layer
- * @param dirtyRect The dirty region of the layer that needs to be redrawn
+ * This should only be used by HardwareRenderer! Do not call directly
*/
- abstract void redrawLater(DisplayList displayList, Rect dirtyRect);
+ static HardwareLayer createDisplayListLayer(HardwareRenderer renderer,
+ int width, int height) {
+ return new HardwareLayer(renderer, nCreateRenderLayer(width, height), LAYER_TYPE_DISPLAY_LIST);
+ }
- /**
- * Indicates that this layer has lost its underlying storage.
+ static HardwareLayer adoptDisplayListLayer(HardwareRenderer renderer, long layer) {
+ return new HardwareLayer(renderer, layer, LAYER_TYPE_DISPLAY_LIST);
+ }
+
+ /** This also creates the underlying layer */
+ private static native long nCreateTextureLayer();
+ private static native long nCreateRenderLayer(int width, int height);
+
+ private static native void nOnTextureDestroyed(long layerUpdater);
+ private static native long nDetachBackingLayer(long layerUpdater);
+
+ /** This also destroys the underlying layer if it is still attached.
+ * Note it does not recycle the underlying layer, but instead queues it
+ * for deferred deletion.
+ * The HardwareRenderer should use detachBackingLayer() in the
+ * onLayerDestroyed() callback to do recycling if desired.
*/
- abstract void clearStorage();
+ private static native void nDestroyLayerUpdater(long layerUpdater);
+
+ private static native boolean nPrepare(long layerUpdater, int width, int height, boolean isOpaque);
+ private static native void nSetLayerPaint(long layerUpdater, long paint);
+ private static native void nSetTransform(long layerUpdater, long matrix);
+ private static native void nSetSurfaceTexture(long layerUpdater,
+ SurfaceTexture surface, boolean isAlreadyAttached);
+ private static native void nUpdateSurfaceTexture(long layerUpdater);
+ private static native void nUpdateRenderLayer(long layerUpdater, long displayList,
+ int left, int top, int right, int bottom);
+
+ private static native boolean nFlushChanges(long layerUpdater);
+
+ private static native long nGetLayer(long layerUpdater);
+ private static native int nGetTexName(long layerUpdater);
+
+ private static class Finalizer {
+ private long mDeferredUpdater;
+
+ public Finalizer(long deferredUpdater) {
+ mDeferredUpdater = deferredUpdater;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ destroy();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ void destroy() {
+ if (mDeferredUpdater != 0) {
+ nDestroyLayerUpdater(mDeferredUpdater);
+ mDeferredUpdater = 0;
+ }
+ }
+ }
}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index f09a111..34efcf5 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -16,41 +16,15 @@
package android.view;
-import android.content.ComponentCallbacks2;
-import android.graphics.Color;
+import android.graphics.Bitmap;
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 +40,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.
@@ -187,14 +154,6 @@ public abstract class HardwareRenderer {
public static final String OVERDRAW_PROPERTY_SHOW = "show";
/**
- * Value for {@link #DEBUG_OVERDRAW_PROPERTY}. When the property is set to this
- * value, an overdraw counter will be shown on screen.
- *
- * @hide
- */
- public static final String OVERDRAW_PROPERTY_COUNT = "count";
-
- /**
* Turn on to debug non-rectangular clip operations.
*
* Possible values:
@@ -222,15 +181,8 @@ 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;
+ /** @hide */
+ public static boolean sUseRenderThread = false;
private boolean mEnabled;
private boolean mRequested = true;
@@ -282,13 +234,6 @@ public abstract class HardwareRenderer {
abstract void updateSurface(Surface surface) throws OutOfResourcesException;
/**
- * Destroys the layers used by the specified view hierarchy.
- *
- * @param view The root of the view hierarchy
- */
- abstract void destroyLayers(View view);
-
- /**
* Destroys all hardware rendering resources associated with the specified
* view hierarchy.
*
@@ -305,15 +250,6 @@ public abstract class HardwareRenderer {
abstract void invalidate(Surface surface);
/**
- * This method should be invoked to ensure the hardware renderer is in
- * valid state (for instance, to ensure the correct EGL context is bound
- * to the current thread.)
- *
- * @return true if the renderer is now valid, false otherwise
- */
- abstract boolean validate();
-
- /**
* This method ensures the hardware renderer is in a valid state
* before executing the specified action.
*
@@ -350,13 +286,6 @@ public abstract class HardwareRenderer {
abstract int getHeight();
/**
- * Gets the current canvas associated with this HardwareRenderer.
- *
- * @return the current HardwareCanvas
- */
- abstract HardwareCanvas getCanvas();
-
- /**
* Outputs extra debugging information in the specified file descriptor.
* @param pw
*/
@@ -379,9 +308,7 @@ public abstract class HardwareRenderer {
*
* @return True if a property has changed.
*/
- abstract boolean loadSystemProperties(Surface surface);
-
- private static native boolean nLoadProperties();
+ abstract boolean loadSystemProperties();
/**
* Sets the directory to use as a persistent storage for hardware rendering
@@ -392,60 +319,9 @@ public abstract class HardwareRenderer {
* @hide
*/
public static void setupDiskCache(File cacheDir) {
- nSetupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
+ GLRenderer.setupShadersDiskCache(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);
- }
-
- 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.
@@ -453,19 +329,20 @@ public abstract class HardwareRenderer {
* @param layer The hardware layer that needs an update
*
* @see #flushLayerUpdates()
- * @see #cancelLayerUpdate(HardwareLayer)
*/
abstract void pushLayerUpdate(HardwareLayer layer);
/**
- * Cancels a queued layer update. If the specified layer was not
- * queued for update, this method has no effect.
- *
- * @param layer The layer whose update to cancel
- *
- * @see #pushLayerUpdate(HardwareLayer)
+ * Tells the HardwareRenderer that a layer was created. The renderer should
+ * make sure to apply any pending layer changes at the start of a new frame
*/
- abstract void cancelLayerUpdate(HardwareLayer layer);
+ abstract void onLayerCreated(HardwareLayer hardwareLayer);
+
+ /**
+ * Tells the HardwareRenderer that the layer is destroyed. The renderer
+ * should remove the layer from any update queues.
+ */
+ abstract void onLayerDestroyed(HardwareLayer layer);
/**
* Forces all enqueued layer updates to be executed immediately.
@@ -509,37 +386,22 @@ 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.
*
- * @param isOpaque Whether the layer should be opaque or not
- *
* @return A hardware layer
*/
- abstract HardwareLayer createHardwareLayer(boolean isOpaque);
+ abstract HardwareLayer createTextureLayer();
/**
* Creates a new hardware layer.
*
* @param width The minimum width of the layer
* @param height The minimum height of the layer
- * @param isOpaque Whether the layer should be opaque or not
*
* @return A hardware layer
*/
- abstract HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque);
+ abstract HardwareLayer createDisplayListLayer(int width, int height);
/**
* Creates a new {@link SurfaceTexture} that can be used to render into the
@@ -551,14 +413,7 @@ public abstract class HardwareRenderer {
*/
abstract SurfaceTexture createSurfaceTexture(HardwareLayer layer);
- /**
- * Sets the {@link android.graphics.SurfaceTexture} that will be used to
- * render into the specified hardware layer.
- *
- * @param layer The layer to render into using a {@link android.graphics.SurfaceTexture}
- * @param surfaceTexture The {@link android.graphics.SurfaceTexture} to use for the layer
- */
- abstract void setSurfaceTexture(HardwareLayer layer, SurfaceTexture surfaceTexture);
+ abstract boolean copyLayerInto(HardwareLayer layer, Bitmap bitmap);
/**
* Detaches the specified functor from the current functor execution queue.
@@ -566,7 +421,7 @@ public abstract class HardwareRenderer {
* @param functor The native functor to remove from the execution queue.
*
* @see HardwareCanvas#callDrawGLFunction(int)
- * @see #attachFunctor(android.view.View.AttachInfo, int)
+ * @see #attachFunctor(android.view.View.AttachInfo, long)
*/
abstract void detachFunctor(long functor);
@@ -577,11 +432,10 @@ public abstract class HardwareRenderer {
* @param functor The native functor to insert in the execution queue.
*
* @see HardwareCanvas#callDrawGLFunction(int)
- * @see #detachFunctor(int)
+ * @see #detachFunctor(long)
*
- * @return true if the functor was attached successfully
*/
- abstract boolean attachFunctor(View.AttachInfo attachInfo, long functor);
+ abstract void attachFunctor(View.AttachInfo attachInfo, long functor);
/**
* Initializes the hardware renderer for the specified surface and setup the
@@ -621,17 +475,20 @@ 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) {
+ HardwareRenderer renderer = null;
+ if (GLES20Canvas.isAvailable()) {
+ if (sUseRenderThread) {
+ renderer = new ThreadedRenderer(translucent);
+ } else {
+ renderer = new GLRenderer(translucent);
+ }
}
- throw new IllegalArgumentException("Unknown GL version: " + glVersion);
+ return renderer;
}
/**
@@ -656,7 +513,7 @@ public abstract class HardwareRenderer {
* see {@link android.content.ComponentCallbacks}
*/
static void startTrimMemory(int level) {
- Gl20Renderer.startTrimMemory(level);
+ GLRenderer.startTrimMemory(level);
}
/**
@@ -664,7 +521,7 @@ public abstract class HardwareRenderer {
* cleanup special resources used by the memory trimming process.
*/
static void endTrimMemory() {
- Gl20Renderer.endTrimMemory();
+ GLRenderer.endTrimMemory();
}
/**
@@ -705,6 +562,8 @@ public abstract class HardwareRenderer {
mRequested = requested;
}
+ abstract void setDisplayListData(long displayList, long newData);
+
/**
* Describes a series of frames that should be drawn on screen as a graph.
* Each frame is composed of 1 or more elements.
@@ -798,1553 +657,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(long functor) {
- if (mCanvas != null) {
- mCanvas.detachFunctor(functor);
- }
- }
-
- @Override
- boolean attachFunctor(View.AttachInfo attachInfo, long 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) {
- long[] 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 d5cec49..aebc601 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 6a6c127..c183f08 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;
@@ -1169,25 +1168,25 @@ public class KeyEvent extends InputEvent implements Parcelable {
* This mask is set if the device woke because of this key event.
*/
public static final int FLAG_WOKE_HERE = 0x1;
-
+
/**
* This mask is set if the key event was generated by a software keyboard.
*/
public static final int FLAG_SOFT_KEYBOARD = 0x2;
-
+
/**
* This mask is set if we don't want the key event to cause us to leave
* touch mode.
*/
public static final int FLAG_KEEP_TOUCH_MODE = 0x4;
-
+
/**
* This mask is set if an event was known to come from a trusted part
* of the system. That is, the event is known to come from the user,
* and could not have been spoofed by a third party component.
*/
public static final int FLAG_FROM_SYSTEM = 0x8;
-
+
/**
* This mask is used for compatibility, to identify enter keys that are
* coming from an IME whose enter key has been auto-labelled "next" or
@@ -1196,7 +1195,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
* receiving them.
*/
public static final int FLAG_EDITOR_ACTION = 0x10;
-
+
/**
* When associated with up key events, this indicates that the key press
* has been canceled. Typically this is used with virtual touch screen
@@ -1205,29 +1204,29 @@ public class KeyEvent extends InputEvent implements Parcelable {
* event and should not perform the action normally associated with the
* key. Note that for this to work, the application can not perform an
* action for a key until it receives an up or the long press timeout has
- * expired.
+ * expired.
*/
public static final int FLAG_CANCELED = 0x20;
-
+
/**
* This key event was generated by a virtual (on-screen) hard key area.
* Typically this is an area of the touchscreen, outside of the regular
* display, dedicated to "hardware" buttons.
*/
public static final int FLAG_VIRTUAL_HARD_KEY = 0x40;
-
+
/**
* This flag is set for the first key repeat that occurs after the
* long press timeout.
*/
public static final int FLAG_LONG_PRESS = 0x80;
-
+
/**
* Set when a key event has {@link #FLAG_CANCELED} set because a long
- * press action was executed while it was down.
+ * press action was executed while it was down.
*/
public static final int FLAG_CANCELED_LONG_PRESS = 0x100;
-
+
/**
* Set for {@link #ACTION_UP} when this event's key code is still being
* tracked from its initial down. That is, somebody requested that tracking
@@ -1284,7 +1283,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public static int getDeadChar(int accent, int c) {
return KeyCharacterMap.getDeadChar(accent, c);
}
-
+
static final boolean DEBUG = false;
static final String TAG = "KeyEvent";
@@ -1314,10 +1313,10 @@ public class KeyEvent extends InputEvent implements Parcelable {
* KeyEvent.startTracking()} to have the framework track the event
* through its {@link #onKeyUp(int, KeyEvent)} and also call your
* {@link #onKeyLongPress(int, KeyEvent)} if it occurs.
- *
+ *
* @param keyCode The value in event.getKeyCode().
* @param event Description of the key event.
- *
+ *
* @return If you handled the event, return true. If you want to allow
* the event to be handled by the next receiver, return false.
*/
@@ -1330,10 +1329,10 @@ public class KeyEvent extends InputEvent implements Parcelable {
* order to receive this callback, someone in the event change
* <em>must</em> return true from {@link #onKeyDown} <em>and</em>
* call {@link KeyEvent#startTracking()} on the event.
- *
+ *
* @param keyCode The value in event.getKeyCode().
* @param event Description of the key event.
- *
+ *
* @return If you handled the event, return true. If you want to allow
* the event to be handled by the next receiver, return false.
*/
@@ -1341,10 +1340,10 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Called when a key up event has occurred.
- *
+ *
* @param keyCode The value in event.getKeyCode().
* @param event Description of the key event.
- *
+ *
* @return If you handled the event, return true. If you want to allow
* the event to be handled by the next receiver, return false.
*/
@@ -1353,11 +1352,11 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Called when multiple down/up pairs of the same key have occurred
* in a row.
- *
+ *
* @param keyCode The value in event.getKeyCode().
* @param count Number of pairs as returned by event.getRepeatCount().
* @param event Description of the key event.
- *
+ *
* @return If you handled the event, return true. If you want to allow
* the event to be handled by the next receiver, return false.
*/
@@ -1373,7 +1372,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Create a new key event.
- *
+ *
* @param action Action code: either {@link #ACTION_DOWN},
* {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
* @param code The key code.
@@ -1387,7 +1386,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Create a new key event.
- *
+ *
* @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
* at which this key code originally went down.
* @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
@@ -1410,7 +1409,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Create a new key event.
- *
+ *
* @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
* at which this key code originally went down.
* @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
@@ -1435,7 +1434,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Create a new key event.
- *
+ *
* @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
* at which this key code originally went down.
* @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
@@ -1464,7 +1463,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Create a new key event.
- *
+ *
* @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
* at which this key code originally went down.
* @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
@@ -1495,7 +1494,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Create a new key event.
- *
+ *
* @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
* at which this key code originally went down.
* @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
@@ -1531,7 +1530,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
* action, repeat count and source will automatically be set to
* {@link #KEYCODE_UNKNOWN}, {@link #ACTION_MULTIPLE}, 0, and
* {@link InputDevice#SOURCE_KEYBOARD} for you.
- *
+ *
* @param time The time (in {@link android.os.SystemClock#uptimeMillis})
* at which this event occured.
* @param characters The string of characters.
@@ -1569,10 +1568,10 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Copy an existing key event, modifying its time and repeat count.
- *
+ *
* @deprecated Use {@link #changeTimeRepeat(KeyEvent, long, int)}
* instead.
- *
+ *
* @param origEvent The existing event to be copied.
* @param eventTime The new event time
* (in {@link android.os.SystemClock#uptimeMillis}) of the event.
@@ -1688,7 +1687,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Create a new key event that is the same as the given one, but whose
* event time and repeat count are replaced with the given value.
- *
+ *
* @param event The existing event to be copied. This is not modified.
* @param eventTime The new event time
* (in {@link android.os.SystemClock#uptimeMillis}) of the event.
@@ -1698,11 +1697,11 @@ public class KeyEvent extends InputEvent implements Parcelable {
int newRepeat) {
return new KeyEvent(event, eventTime, newRepeat);
}
-
+
/**
* Create a new key event that is the same as the given one, but whose
* event time and repeat count are replaced with the given value.
- *
+ *
* @param event The existing event to be copied. This is not modified.
* @param eventTime The new event time
* (in {@link android.os.SystemClock#uptimeMillis}) of the event.
@@ -1718,10 +1717,10 @@ public class KeyEvent extends InputEvent implements Parcelable {
ret.mFlags = newFlags;
return ret;
}
-
+
/**
* Copy an existing key event, modifying its action.
- *
+ *
* @param origEvent The existing event to be copied.
* @param action The new action code of the event.
*/
@@ -1743,18 +1742,18 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Create a new key event that is the same as the given one, but whose
* action is replaced with the given value.
- *
+ *
* @param event The existing event to be copied. This is not modified.
* @param action The new action code of the event.
*/
public static KeyEvent changeAction(KeyEvent event, int action) {
return new KeyEvent(event, action);
}
-
+
/**
* Create a new key event that is the same as the given one, but whose
* flags are replaced with the given value.
- *
+ *
* @param event The existing event to be copied. This is not modified.
* @param flags The new flags constant.
*/
@@ -1779,7 +1778,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Don't use in new code, instead explicitly check
* {@link #getAction()}.
- *
+ *
* @return If the action is ACTION_DOWN, returns true; else false.
*
* @deprecated
@@ -1791,7 +1790,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Is this a system key? System keys can not be used for menu shortcuts.
- *
+ *
* TODO: this information should come from a table somewhere.
* TODO: should the dpad keys be here? arguably, because they also shouldn't be menu shortcuts
*/
@@ -1860,6 +1859,30 @@ public class KeyEvent extends InputEvent implements Parcelable {
}
}
+ /**
+ * Whether this key is a media key, which can be send to apps that are
+ * interested in media key events.
+ *
+ * @hide
+ */
+ public static final boolean isMediaKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ return true;
+ }
+ return false;
+ }
+
/** {@inheritDoc} */
@Override
public final int getDeviceId() {
@@ -2329,7 +2352,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Retrieve the action of this key event. May be either
* {@link #ACTION_DOWN}, {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
- *
+ *
* @return The event action: ACTION_DOWN, ACTION_UP, or ACTION_MULTIPLE.
*/
public final int getAction() {
@@ -2343,7 +2366,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public final boolean isCanceled() {
return (mFlags&FLAG_CANCELED) != 0;
}
-
+
/**
* Call this during {@link Callback#onKeyDown} to have the system track
* the key through its final up (possibly including a long press). Note
@@ -2354,7 +2377,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public final void startTracking() {
mFlags |= FLAG_START_TRACKING;
}
-
+
/**
* For {@link #ACTION_UP} events, indicates that the event is still being
* tracked from its initial down event as per
@@ -2363,7 +2386,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public final boolean isTracking() {
return (mFlags&FLAG_TRACKING) != 0;
}
-
+
/**
* For {@link #ACTION_DOWN} events, indicates that the event has been
* canceled as per {@link #FLAG_LONG_PRESS}.
@@ -2371,11 +2394,11 @@ public class KeyEvent extends InputEvent implements Parcelable {
public final boolean isLongPress() {
return (mFlags&FLAG_LONG_PRESS) != 0;
}
-
+
/**
* Retrieve the key code of the key event. This is the physical key that
* was pressed, <em>not</em> the Unicode character.
- *
+ *
* @return The key code of the event.
*/
public final int getKeyCode() {
@@ -2386,14 +2409,14 @@ public class KeyEvent extends InputEvent implements Parcelable {
* For the special case of a {@link #ACTION_MULTIPLE} event with key
* code of {@link #KEYCODE_UNKNOWN}, this is a raw string of characters
* associated with the event. In all other cases it is null.
- *
+ *
* @return Returns a String of 1 or more characters associated with
* the event.
*/
public final String getCharacters() {
return mCharacters;
}
-
+
/**
* Retrieve the hardware key id of this key event. These values are not
* reliable and vary from device to device.
@@ -2410,7 +2433,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
* events, this is the number of times the key has repeated with the first
* down starting at 0 and counting up from there. For multiple key
* events, this is the number of down/up pairs that have occurred.
- *
+ *
* @return The number of times the key has repeated.
*/
public final int getRepeatCount() {
@@ -2424,7 +2447,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
* Note that when chording keys, this value is the down time of the
* most recently pressed key, which may <em>not</em> be the same physical
* key of this event.
- *
+ *
* @return Returns the most recent key down time, in the
* {@link android.os.SystemClock#uptimeMillis} time base
*/
@@ -2436,7 +2459,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
* Retrieve the time this event occurred,
* in the {@link android.os.SystemClock#uptimeMillis} time base.
*
- * @return Returns the time this event occurred,
+ * @return Returns the time this event occurred,
* in the {@link android.os.SystemClock#uptimeMillis} time base.
*/
@Override
@@ -2465,7 +2488,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Renamed to {@link #getDeviceId}.
- *
+ *
* @hide
* @deprecated use {@link #getDeviceId()} instead.
*/
@@ -2497,7 +2520,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public char getDisplayLabel() {
return getKeyCharacterMap().getDisplayLabel(mKeyCode);
}
-
+
/**
* Gets the Unicode character generated by the specified key and meta
* key state combination.
@@ -2520,7 +2543,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public int getUnicodeChar() {
return getUnicodeChar(mMetaState);
}
-
+
/**
* Gets the Unicode character generated by the specified key and meta
* key state combination.
@@ -2544,7 +2567,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public int getUnicodeChar(int metaState) {
return getKeyCharacterMap().get(mKeyCode, metaState);
}
-
+
/**
* Get the character conversion data for a given key code.
*
@@ -2559,7 +2582,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public boolean getKeyData(KeyData results) {
return getKeyCharacterMap().getKeyData(mKeyCode, results);
}
-
+
/**
* Gets the first character in the character array that can be generated
* by the specified key code.
@@ -2574,7 +2597,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public char getMatch(char[] chars) {
return getMatch(chars, 0);
}
-
+
/**
* Gets the first character in the character array that can be generated
* by the specified key code. If there are multiple choices, prefers
@@ -2587,7 +2610,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public char getMatch(char[] chars, int metaState) {
return getKeyCharacterMap().getMatch(mKeyCode, chars, metaState);
}
-
+
/**
* Gets the number or symbol associated with the key.
* <p>
@@ -2611,7 +2634,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public char getNumber() {
return getKeyCharacterMap().getNumber(mKeyCode);
}
-
+
/**
* Returns true if this key produces a glyph.
*
@@ -2620,7 +2643,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public boolean isPrintingKey() {
return getKeyCharacterMap().isPrintingKey(mKeyCode);
}
-
+
/**
* @deprecated Use {@link #dispatch(Callback, DispatcherState, Object)} instead.
*/
@@ -2628,16 +2651,16 @@ public class KeyEvent extends InputEvent implements Parcelable {
public final boolean dispatch(Callback receiver) {
return dispatch(receiver, null, null);
}
-
+
/**
* Deliver this key event to a {@link Callback} interface. If this is
* an ACTION_MULTIPLE event and it is not handled, then an attempt will
* be made to deliver a single normal event.
- *
+ *
* @param receiver The Callback that will be given the event.
* @param state State information retained across events.
* @param target The target of the dispatch, for use in tracking.
- *
+ *
* @return The return value from the Callback method that was called.
*/
public final boolean dispatch(Callback receiver, DispatcherState state,
@@ -2703,7 +2726,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
int mDownKeyCode;
Object mDownTarget;
SparseIntArray mActiveLongPresses = new SparseIntArray();
-
+
/**
* Reset back to initial state.
*/
@@ -2713,7 +2736,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
mDownTarget = null;
mActiveLongPresses.clear();
}
-
+
/**
* Stop any tracking associated with this target.
*/
@@ -2724,14 +2747,14 @@ public class KeyEvent extends InputEvent implements Parcelable {
mDownTarget = null;
}
}
-
+
/**
* Start tracking the key code associated with the given event. This
* can only be called on a key down. It will allow you to see any
* long press associated with the key, and will result in
* {@link KeyEvent#isTracking} return true on the long press and up
* events.
- *
+ *
* <p>This is only needed if you are directly dispatching events, rather
* than handling them in {@link Callback#onKeyDown}.
*/
@@ -2744,7 +2767,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
mDownKeyCode = event.getKeyCode();
mDownTarget = target;
}
-
+
/**
* Return true if the key event is for a key code that is currently
* being tracked by the dispatcher.
@@ -2752,7 +2775,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public boolean isTracking(KeyEvent event) {
return mDownKeyCode == event.getKeyCode();
}
-
+
/**
* Keep track of the given event's key code as having performed an
* action with a long press, so no action should occur on the up.
@@ -2762,7 +2785,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public void performedLongPress(KeyEvent event) {
mActiveLongPresses.put(event.getKeyCode(), 1);
}
-
+
/**
* Handle key up event to stop tracking. This resets the dispatcher state,
* and updates the key event state based on it.
@@ -2917,12 +2940,12 @@ public class KeyEvent extends InputEvent implements Parcelable {
return new KeyEvent[size];
}
};
-
+
/** @hide */
public static KeyEvent createFromParcelBody(Parcel in) {
return new KeyEvent(in);
}
-
+
private KeyEvent(Parcel in) {
mDeviceId = in.readInt();
mSource = in.readInt();
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index aa43bad..c4fac46 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -90,6 +90,10 @@ public abstract class LayoutInflater {
private static final String TAG_INCLUDE = "include";
private static final String TAG_1995 = "blink";
private static final String TAG_REQUEST_FOCUS = "requestFocus";
+ private static final String TAG_TAG = "tag";
+
+ private static final int[] ATTRS_THEME = new int[] {
+ com.android.internal.R.attr.theme };
/**
* Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
@@ -459,15 +463,10 @@ public abstract class LayoutInflater {
+ "ViewGroup root and attachToRoot=true");
}
- rInflate(parser, root, attrs, false);
+ rInflate(parser, root, attrs, false, false);
} else {
// Temp is the root view that was found in the xml
- View temp;
- if (TAG_1995.equals(name)) {
- temp = new BlinkLayout(mContext, attrs);
- } else {
- temp = createViewFromTag(root, name, attrs);
- }
+ final View temp = createViewFromTag(root, name, attrs, false);
ViewGroup.LayoutParams params = null;
@@ -489,7 +488,7 @@ public abstract class LayoutInflater {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp
- rInflate(parser, temp, attrs, true);
+ rInflate(parser, temp, attrs, true, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
@@ -669,31 +668,68 @@ public abstract class LayoutInflater {
return onCreateView(name, attrs);
}
- /*
- * default visibility so the BridgeInflater can override it.
+ /**
+ * Creates a view from a tag name using the supplied attribute set.
+ * <p>
+ * If {@code inheritContext} is true and the parent is non-null, the view
+ * will be inflated in parent view's context. If the view specifies a
+ * &lt;theme&gt; attribute, the inflation context will be wrapped with the
+ * specified theme.
+ * <p>
+ * Note: Default visibility so the BridgeInflater can override it.
*/
- View createViewFromTag(View parent, String name, AttributeSet attrs) {
+ View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
+ Context viewContext;
+ if (parent != null && inheritContext) {
+ viewContext = parent.getContext();
+ } else {
+ viewContext = mContext;
+ }
+
+ // Apply a theme wrapper, if requested.
+ final TypedArray ta = viewContext.obtainStyledAttributes(attrs, ATTRS_THEME);
+ final int themeResId = ta.getResourceId(0, 0);
+ if (themeResId != 0) {
+ viewContext = new ContextThemeWrapper(viewContext, themeResId);
+ }
+ ta.recycle();
+
+ if (name.equals(TAG_1995)) {
+ // Let's party like it's 1995!
+ return new BlinkLayout(viewContext, attrs);
+ }
+
if (DEBUG) System.out.println("******** Creating view: " + name);
try {
View view;
- if (mFactory2 != null) view = mFactory2.onCreateView(parent, name, mContext, attrs);
- else if (mFactory != null) view = mFactory.onCreateView(name, mContext, attrs);
- else view = null;
+ if (mFactory2 != null) {
+ view = mFactory2.onCreateView(parent, name, viewContext, attrs);
+ } else if (mFactory != null) {
+ view = mFactory.onCreateView(name, viewContext, attrs);
+ } else {
+ view = null;
+ }
if (view == null && mPrivateFactory != null) {
- view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);
+ view = mPrivateFactory.onCreateView(parent, name, viewContext, attrs);
}
-
+
if (view == null) {
- if (-1 == name.indexOf('.')) {
- view = onCreateView(parent, name, attrs);
- } else {
- view = createView(name, null, attrs);
+ final Object lastContext = mConstructorArgs[0];
+ mConstructorArgs[0] = viewContext;
+ try {
+ if (-1 == name.indexOf('.')) {
+ view = onCreateView(parent, name, attrs);
+ } else {
+ view = createView(name, null, attrs);
+ }
+ } finally {
+ mConstructorArgs[0] = lastContext;
}
}
@@ -720,9 +756,14 @@ public abstract class LayoutInflater {
/**
* Recursive method used to descend down the xml hierarchy and instantiate
* views, instantiate their children, and then call onFinishInflate().
+ *
+ * @param inheritContext Whether the root view should be inflated in its
+ * parent's context. This should be true when called inflating
+ * child views recursively, or false otherwise.
*/
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
- boolean finishInflate) throws XmlPullParserException, IOException {
+ boolean finishInflate, boolean inheritContext) throws XmlPullParserException,
+ IOException {
final int depth = parser.getDepth();
int type;
@@ -738,24 +779,20 @@ public abstract class LayoutInflater {
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
+ } else if (TAG_TAG.equals(name)) {
+ parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
- parseInclude(parser, parent, attrs);
+ parseInclude(parser, parent, attrs, inheritContext);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
- } else if (TAG_1995.equals(name)) {
- final View view = new BlinkLayout(mContext, attrs);
- final ViewGroup viewGroup = (ViewGroup) parent;
- final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
- rInflate(parser, view, attrs, true);
- viewGroup.addView(view, params);
} else {
- final View view = createViewFromTag(parent, name, attrs);
+ final View view = createViewFromTag(parent, name, attrs, inheritContext);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
- rInflate(parser, view, attrs, true);
+ rInflate(parser, view, attrs, true, true);
viewGroup.addView(view, params);
}
}
@@ -763,10 +800,14 @@ public abstract class LayoutInflater {
if (finishInflate) parent.onFinishInflate();
}
- private void parseRequestFocus(XmlPullParser parser, View parent)
+ /**
+ * Parses a <code>&lt;request-focus&gt;</code> element and requests focus on
+ * the containing View.
+ */
+ private void parseRequestFocus(XmlPullParser parser, View view)
throws XmlPullParserException, IOException {
int type;
- parent.requestFocus();
+ view.requestFocus();
final int currentDepth = parser.getDepth();
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
@@ -774,9 +815,30 @@ public abstract class LayoutInflater {
}
}
- private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs)
+ /**
+ * Parses a <code>&lt;tag&gt;</code> element and sets a keyed tag on the
+ * containing View.
+ */
+ private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
throws XmlPullParserException, IOException {
+ int type;
+
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.ViewTag);
+ final int key = ta.getResourceId(com.android.internal.R.styleable.ViewTag_id, 0);
+ final CharSequence value = ta.getText(com.android.internal.R.styleable.ViewTag_value);
+ view.setTag(key, value);
+ ta.recycle();
+
+ final int currentDepth = parser.getDepth();
+ while (((type = parser.next()) != XmlPullParser.END_TAG ||
+ parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
+ // Empty
+ }
+ }
+ private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs,
+ boolean inheritContext) throws XmlPullParserException, IOException {
int type;
if (parent instanceof ViewGroup) {
@@ -811,9 +873,10 @@ public abstract class LayoutInflater {
if (TAG_MERGE.equals(childName)) {
// Inflate all children.
- rInflate(childParser, parent, childAttrs, false);
+ rInflate(childParser, parent, childAttrs, false, inheritContext);
} else {
- final View view = createViewFromTag(parent, childName, childAttrs);
+ final View view = createViewFromTag(parent, childName, childAttrs,
+ inheritContext);
final ViewGroup group = (ViewGroup) parent;
// We try to load the layout params set in the <include /> tag. If
@@ -836,7 +899,7 @@ public abstract class LayoutInflater {
}
// Inflate all children.
- rInflate(childParser, view, childAttrs, true);
+ rInflate(childParser, view, childAttrs, true, true);
// Attempt to override the included layout's android:id with the
// one set on the <include /> tag itself.
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index bb7ed41..063a08d 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -140,7 +140,7 @@ public final class PointerIcon implements Parcelable {
if ((resourceId & 0xff000000) == 0x01000000) {
icon.mSystemIconResourceId = resourceId;
} else {
- icon.loadResource(context.getResources(), resourceId);
+ icon.loadResource(context, context.getResources(), resourceId);
}
return icon;
}
@@ -198,7 +198,7 @@ public final class PointerIcon implements Parcelable {
}
PointerIcon icon = new PointerIcon(STYLE_CUSTOM);
- icon.loadResource(resources, resourceId);
+ icon.loadResource(null, resources, resourceId);
return icon;
}
@@ -224,7 +224,7 @@ public final class PointerIcon implements Parcelable {
PointerIcon result = new PointerIcon(mStyle);
result.mSystemIconResourceId = mSystemIconResourceId;
- result.loadResource(context.getResources(), mSystemIconResourceId);
+ result.loadResource(context, context.getResources(), mSystemIconResourceId);
return result;
}
@@ -373,7 +373,7 @@ public final class PointerIcon implements Parcelable {
return true;
}
- private void loadResource(Resources resources, int resourceId) {
+ private void loadResource(Context context, Resources resources, int resourceId) {
XmlResourceParser parser = resources.getXml(resourceId);
final int bitmapRes;
final float hotSpotX;
@@ -397,7 +397,12 @@ public final class PointerIcon implements Parcelable {
throw new IllegalArgumentException("<pointer-icon> is missing bitmap attribute.");
}
- Drawable drawable = resources.getDrawable(bitmapRes);
+ Drawable drawable;
+ if (context == null) {
+ drawable = resources.getDrawable(bitmapRes);
+ } else {
+ drawable = context.getDrawable(bitmapRes);
+ }
if (!(drawable instanceof BitmapDrawable)) {
throw new IllegalArgumentException("<pointer-icon> bitmap attribute must "
+ "refer to a bitmap drawable.");
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 91645e7..fdaae01 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 eea5884..e693b9e 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;
@@ -40,9 +39,11 @@ public class SurfaceControl {
private static native void nativeDestroy(long nativeObject);
private static native Bitmap nativeScreenshot(IBinder displayToken,
- int width, int height, int minLayer, int maxLayer, boolean allLayers);
+ int width, int height, int minLayer, int maxLayer, boolean allLayers,
+ boolean useIdentityTransform);
private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
- int width, int height, int minLayer, int maxLayer, boolean allLayers);
+ int width, int height, int minLayer, int maxLayer, boolean allLayers,
+ boolean useIdentityTransform);
private static native void nativeOpenTransaction();
private static native void nativeCloseTransaction();
@@ -166,6 +167,13 @@ public class SurfaceControl {
public static final int FX_SURFACE_DIM = 0x00020000;
/**
+ * Surface creation flag: Creates a video plane Surface.
+ * This surface is backed by a hardware video plane. It is an error to lock
+ * a video plane surface, since it doesn't have a backing store.
+ */
+ public static final int FX_SURFACE_VIDEO_PLANE = 0x00040000;
+
+ /**
* Mask used for FX values above.
*
*/
@@ -178,13 +186,13 @@ public class SurfaceControl {
* Equivalent to calling hide().
* Updates the value set during Surface creation (see {@link #HIDDEN}).
*/
- public static final int SURFACE_HIDDEN = 0x01;
+ private static final int SURFACE_HIDDEN = 0x01;
/**
* Surface flag: composite without blending when possible.
* Updates the value set during Surface creation (see {@link #OPAQUE}).
*/
- public static final int SURFACE_OPAQUE = 0x02;
+ private static final int SURFACE_OPAQUE = 0x02;
/* built-in physical display ids (keep in sync with ISurfaceComposer.h)
@@ -192,13 +200,13 @@ public class SurfaceControl {
/**
* Built-in physical display id: Main display.
- * Use only with {@link SurfaceControl#getBuiltInDisplay()}.
+ * Use only with {@link SurfaceControl#getBuiltInDisplay(int)}.
*/
public static final int BUILT_IN_DISPLAY_ID_MAIN = 0;
/**
* Built-in physical display id: Attached HDMI display.
- * Use only with {@link SurfaceControl#getBuiltInDisplay()}.
+ * Use only with {@link SurfaceControl#getBuiltInDisplay(int)}.
*/
public static final int BUILT_IN_DISPLAY_ID_HDMI = 1;
@@ -370,18 +378,6 @@ public class SurfaceControl {
nativeSetMatrix(mNativeObject, dsdx, dtdx, dsdy, dtdy);
}
- /**
- * Sets and clears flags, such as {@link #SURFACE_HIDDEN}. The new value will be:
- * <p>
- * <code>newFlags = (oldFlags & ~mask) | (flags & mask)</code>
- * <p>
- * Note this does not take the same set of flags as the constructor.
- */
- public void setFlags(int flags, int mask) {
- checkNotReleased();
- nativeSetFlags(mNativeObject, flags, mask);
- }
-
public void setWindowCrop(Rect crop) {
checkNotReleased();
if (crop != null) {
@@ -567,10 +563,15 @@ public class SurfaceControl {
* include in the screenshot.
* @param maxLayer The highest (top-most Z order) surface layer to
* include in the screenshot.
+ * @param useIdentityTransform Replace whatever transformation (rotation,
+ * scaling, translation) the surface layers are currently using with the
+ * identity transformation while taking the screenshot.
*/
public static void screenshot(IBinder display, Surface consumer,
- int width, int height, int minLayer, int maxLayer) {
- screenshot(display, consumer, width, height, minLayer, maxLayer, false);
+ int width, int height, int minLayer, int maxLayer,
+ boolean useIdentityTransform) {
+ screenshot(display, consumer, width, height, minLayer, maxLayer, false,
+ useIdentityTransform);
}
/**
@@ -585,7 +586,7 @@ public class SurfaceControl {
*/
public static void screenshot(IBinder display, Surface consumer,
int width, int height) {
- screenshot(display, consumer, width, height, 0, 0, true);
+ screenshot(display, consumer, width, height, 0, 0, true, false);
}
/**
@@ -595,7 +596,7 @@ public class SurfaceControl {
* @param consumer The {@link Surface} to take the screenshot into.
*/
public static void screenshot(IBinder display, Surface consumer) {
- screenshot(display, consumer, 0, 0, 0, 0, true);
+ screenshot(display, consumer, 0, 0, 0, 0, true, false);
}
@@ -615,15 +616,20 @@ public class SurfaceControl {
* include in the screenshot.
* @param maxLayer The highest (top-most Z order) surface layer to
* include in the screenshot.
+ * @param useIdentityTransform Replace whatever transformation (rotation,
+ * scaling, translation) the surface layers are currently using with the
+ * identity transformation while taking the screenshot.
* @return Returns a Bitmap containing the screen contents, or null
* if an error occurs. Make sure to call Bitmap.recycle() as soon as
* possible, once its content is not needed anymore.
*/
- public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer) {
+ public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer,
+ boolean useIdentityTransform) {
// TODO: should take the display as a parameter
IBinder displayToken = SurfaceControl.getBuiltInDisplay(
SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
- return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false);
+ return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false,
+ useIdentityTransform);
}
/**
@@ -642,17 +648,19 @@ public class SurfaceControl {
// TODO: should take the display as a parameter
IBinder displayToken = SurfaceControl.getBuiltInDisplay(
SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
- return nativeScreenshot(displayToken, width, height, 0, 0, true);
+ return nativeScreenshot(displayToken, width, height, 0, 0, true, false);
}
private static void screenshot(IBinder display, Surface consumer,
- int width, int height, int minLayer, int maxLayer, boolean allLayers) {
+ int width, int height, int minLayer, int maxLayer, boolean allLayers,
+ boolean useIdentityTransform) {
if (display == null) {
throw new IllegalArgumentException("displayToken must not be null");
}
if (consumer == null) {
throw new IllegalArgumentException("consumer must not be null");
}
- nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers);
+ nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers,
+ useIdentityTransform);
}
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 22d4c9b..1f211c2 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();
}
@@ -250,8 +255,9 @@ public class SurfaceView extends View {
updateWindow(false, false);
}
+ /** @hide */
@Override
- protected void onDetachedFromWindow() {
+ protected void onDetachedFromWindowInternal() {
if (mGlobalListenersAdded) {
ViewTreeObserver observer = getViewTreeObserver();
observer.removeOnScrollChangedListener(mScrollChangedListener);
@@ -273,7 +279,7 @@ public class SurfaceView extends View {
mSession = null;
mLayout.token = null;
- super.onDetachedFromWindow();
+ super.onDetachedFromWindowInternal();
}
@Override
@@ -416,7 +422,10 @@ public class SurfaceView extends View {
mWindowType = type;
}
- private void updateWindow(boolean force, boolean redrawNeeded) {
+ /**
+ * @hide
+ */
+ protected void updateWindow(boolean force, boolean redrawNeeded) {
if (!mHaveFrame) {
return;
}
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index b78af2e..3cfe5e9 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();
}
@@ -210,29 +228,16 @@ public class TextureView extends View {
}
}
+ /** @hide */
@Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mLayer != null) {
- boolean success = executeHardwareAction(new Runnable() {
- @Override
- public void run() {
- destroySurface();
- }
- });
-
- if (!success) {
- Log.w(LOG_TAG, "TextureView was not able to destroy its surface: " + this);
- }
- }
+ protected void onDetachedFromWindowInternal() {
+ destroySurface();
+ super.onDetachedFromWindowInternal();
}
private void destroySurface() {
if (mLayer != null) {
- mSurface.detachFromGLContext();
- // SurfaceTexture owns the texture name and detachFromGLContext
- // should have deleted it
- mLayer.clearStorage();
+ mLayer.detachSurfaceTexture(mSurface);
boolean shouldRelease = true;
if (mListener != null) {
@@ -357,7 +362,7 @@ public class TextureView extends View {
return null;
}
- mLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(mOpaque);
+ mLayer = mAttachInfo.mHardwareRenderer.createTextureLayer();
if (!mUpdateSurface) {
// Create a new SurfaceTexture for the layer.
mSurface = mAttachInfo.mHardwareRenderer.createSurfaceTexture(mLayer);
@@ -398,7 +403,7 @@ public class TextureView extends View {
updateLayer();
mMatrixChanged = true;
- mAttachInfo.mHardwareRenderer.setSurfaceTexture(mLayer, mSurface);
+ mLayer.setSurfaceTexture(mSurface);
mSurface.setDefaultBufferSize(getWidth(), getHeight());
}
@@ -451,7 +456,8 @@ public class TextureView extends View {
}
}
- mLayer.update(getWidth(), getHeight(), mOpaque);
+ mLayer.prepare(getWidth(), getHeight(), mOpaque);
+ mLayer.updateSurfaceTexture();
if (mListener != null) {
mListener.onSurfaceTextureUpdated(mSurface);
@@ -589,14 +595,6 @@ public class TextureView extends View {
*/
public Bitmap getBitmap(Bitmap bitmap) {
if (bitmap != null && isAvailable()) {
- AttachInfo info = mAttachInfo;
- if (info != null && info.mHardwareRenderer != null &&
- info.mHardwareRenderer.isEnabled()) {
- if (!info.mHardwareRenderer.validate()) {
- throw new IllegalStateException("Could not acquire hardware rendering context");
- }
- }
-
applyUpdate();
applyTransformMatrix();
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
new file mode 100644
index 0000000..a1fb123
--- /dev/null
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -0,0 +1,274 @@
+/*
+ * 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 android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.view.Surface.OutOfResourcesException;
+import android.view.View.AttachInfo;
+
+import java.io.PrintWriter;
+
+/**
+ * Hardware renderer that proxies the rendering to a render thread. Most calls
+ * are currently synchronous.
+ * TODO: Make draw() async.
+ * TODO: Figure out how to share the DisplayList between two threads (global lock?)
+ *
+ * The UI thread can block on the RenderThread, but RenderThread must never
+ * block on the UI thread.
+ *
+ * ThreadedRenderer creates an instance of RenderProxy. RenderProxy in turn creates
+ * and manages a CanvasContext on the RenderThread. The CanvasContext is fully managed
+ * by the lifecycle of the RenderProxy.
+ *
+ * Note that although currently the EGL context & surfaces are created & managed
+ * by the render thread, the goal is to move that into a shared structure that can
+ * be managed by both threads. EGLSurface creation & deletion should ideally be
+ * done on the UI thread and not the RenderThread to avoid stalling the
+ * RenderThread with surface buffer allocation.
+ *
+ * @hide
+ */
+public class ThreadedRenderer extends HardwareRenderer {
+ private static final String LOGTAG = "ThreadedRenderer";
+
+ private static final Rect NULL_RECT = new Rect(-1, -1, -1, -1);
+
+ private int mWidth, mHeight;
+ private long mNativeProxy;
+
+ ThreadedRenderer(boolean translucent) {
+ mNativeProxy = nCreateProxy(translucent);
+ setEnabled(mNativeProxy != 0);
+ }
+
+ @Override
+ void destroy(boolean full) {
+ nDestroyCanvas(mNativeProxy);
+ }
+
+ @Override
+ boolean initialize(Surface surface) throws OutOfResourcesException {
+ return nInitialize(mNativeProxy, surface);
+ }
+
+ @Override
+ void updateSurface(Surface surface) throws OutOfResourcesException {
+ nUpdateSurface(mNativeProxy, surface);
+ }
+
+ @Override
+ void destroyHardwareResources(View view) {
+ destroyResources(view);
+ // TODO: 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));
+ }
+ }
+ }
+
+ @Override
+ void invalidate(Surface surface) {
+ updateSurface(surface);
+ }
+
+ @Override
+ boolean safelyRun(Runnable action) {
+ nRunWithGlContext(mNativeProxy, action);
+ return true;
+ }
+
+ @Override
+ void setup(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ nSetup(mNativeProxy, width, height);
+ }
+
+ @Override
+ int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ void dumpGfxInfo(PrintWriter pw) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ long getFrameCount() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ boolean loadSystemProperties() {
+ return false;
+ }
+
+ /**
+ * TODO: Remove
+ * Temporary hack to allow RenderThreadTest prototype app to trigger
+ * replaying a DisplayList after modifying the displaylist properties
+ *
+ * @hide */
+ public void repeatLastDraw() {
+ }
+
+ @Override
+ void setDisplayListData(long displayList, long newData) {
+ nSetDisplayListData(mNativeProxy, displayList, newData);
+ }
+
+ @Override
+ void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) {
+ attachInfo.mIgnoreDirtyState = true;
+ attachInfo.mDrawingTime = SystemClock.uptimeMillis();
+ view.mPrivateFlags |= View.PFLAG_DRAWN;
+
+ view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
+ == View.PFLAG_INVALIDATED;
+ view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
+
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
+ DisplayList displayList = view.getDisplayList();
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+
+ view.mRecreateDisplayList = false;
+
+ if (dirty == null) {
+ dirty = NULL_RECT;
+ }
+ nDrawDisplayList(mNativeProxy, displayList.getNativeDisplayList(),
+ dirty.left, dirty.top, dirty.right, dirty.bottom);
+ }
+
+ @Override
+ void detachFunctor(long functor) {
+ nDetachFunctor(mNativeProxy, functor);
+ }
+
+ @Override
+ void attachFunctor(AttachInfo attachInfo, long functor) {
+ nAttachFunctor(mNativeProxy, functor);
+ }
+
+ @Override
+ HardwareLayer createDisplayListLayer(int width, int height) {
+ long layer = nCreateDisplayListLayer(mNativeProxy, width, height);
+ return HardwareLayer.adoptDisplayListLayer(this, layer);
+ }
+
+ @Override
+ HardwareLayer createTextureLayer() {
+ long layer = nCreateTextureLayer(mNativeProxy);
+ return HardwareLayer.adoptTextureLayer(this, layer);
+ }
+
+ @Override
+ SurfaceTexture createSurfaceTexture(final HardwareLayer layer) {
+ final SurfaceTexture[] ret = new SurfaceTexture[1];
+ nRunWithGlContext(mNativeProxy, new Runnable() {
+ @Override
+ public void run() {
+ ret[0] = layer.createSurfaceTexture();
+ }
+ });
+ return ret[0];
+ }
+
+ @Override
+ boolean copyLayerInto(final HardwareLayer layer, final Bitmap bitmap) {
+ return nCopyLayerInto(mNativeProxy,
+ layer.getDeferredLayerUpdater(), bitmap.mNativeBitmap);
+ }
+
+ @Override
+ void pushLayerUpdate(HardwareLayer layer) {
+ // TODO: Remove this, it's not needed outside of GLRenderer
+ }
+
+ @Override
+ void onLayerCreated(HardwareLayer layer) {
+ // TODO: Is this actually useful?
+ }
+
+ @Override
+ void flushLayerUpdates() {
+ // TODO: Figure out what this should do or remove it
+ }
+
+ @Override
+ void onLayerDestroyed(HardwareLayer layer) {
+ nDestroyLayer(mNativeProxy, layer.getDeferredLayerUpdater());
+ }
+
+ @Override
+ void setName(String name) {
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nDeleteProxy(mNativeProxy);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /** @hide */
+ public static native void postToRenderThread(Runnable runnable);
+
+ private static native long nCreateProxy(boolean translucent);
+ private static native void nDeleteProxy(long nativeProxy);
+
+ private static native boolean nInitialize(long nativeProxy, Surface window);
+ private static native void nUpdateSurface(long nativeProxy, Surface window);
+ private static native void nSetup(long nativeProxy, int width, int height);
+ private static native void nSetDisplayListData(long nativeProxy, long displayList,
+ long newData);
+ private static native void nDrawDisplayList(long nativeProxy, long displayList,
+ int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
+ private static native void nRunWithGlContext(long nativeProxy, Runnable runnable);
+ private static native void nDestroyCanvas(long nativeProxy);
+
+ private static native void nAttachFunctor(long nativeProxy, long functor);
+ private static native void nDetachFunctor(long nativeProxy, long functor);
+
+ private static native long nCreateDisplayListLayer(long nativeProxy, int width, int height);
+ private static native long nCreateTextureLayer(long nativeProxy);
+ private static native boolean nCopyLayerInto(long nativeProxy, long layer, long bitmap);
+ private static native void nDestroyLayer(long nativeProxy, long layer);
+}
diff --git a/core/java/android/view/VideoPlaneView.java b/core/java/android/view/VideoPlaneView.java
new file mode 100644
index 0000000..81dcf9d
--- /dev/null
+++ b/core/java/android/view/VideoPlaneView.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * Provides a dedicated surface embedded inside of a view hierarchy much like a
+ * {@link SurfaceView}, but the surface is actually backed by a hardware video
+ * plane.
+ *
+ * TODO: Eventually this should be separate from SurfaceView.
+ *
+ * @hide
+ */
+public class VideoPlaneView extends SurfaceView {
+ public VideoPlaneView(Context context) {
+ super(context);
+ }
+
+ public VideoPlaneView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public VideoPlaneView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public VideoPlaneView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void updateWindow(boolean force, boolean redrawNeeded) {
+ mLayout.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_VIDEO_PLANE;
+ super.updateWindow(force, redrawNeeded);
+ }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0b8a40f..7a58d06 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;
@@ -29,6 +32,7 @@ import android.graphics.Interpolator;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
+import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PorterDuff;
@@ -86,6 +90,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;
@@ -95,7 +101,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
@@ -658,6 +666,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* @attr ref android.R.styleable#View_scrollbarTrackVertical
* @attr ref android.R.styleable#View_scrollbarAlwaysDrawHorizontalTrack
* @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack
+ * @attr ref android.R.styleable#View_sharedElementName
* @attr ref android.R.styleable#View_soundEffectsEnabled
* @attr ref android.R.styleable#View_tag
* @attr ref android.R.styleable#View_textAlignment
@@ -666,6 +675,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* @attr ref android.R.styleable#View_transformPivotY
* @attr ref android.R.styleable#View_translationX
* @attr ref android.R.styleable#View_translationY
+ * @attr ref android.R.styleable#View_translationZ
* @attr ref android.R.styleable#View_visibility
*
* @see android.view.ViewGroup
@@ -729,6 +739,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 +911,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 +960,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 +1050,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 +1071,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.
@@ -1587,7 +1648,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #setTag(Object)
* @see #getTag()
*/
- protected Object mTag;
+ protected Object mTag = null;
// for mPrivateFlags:
/** {@hide} */
@@ -1804,6 +1865,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 +2062,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)}
*/
@@ -2234,7 +2327,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/* End of masks for mPrivateFlags2 */
- /* Masks for mPrivateFlags3 */
+ /**
+ * Masks for mPrivateFlags3, as generated by dumpFlags():
+ *
+ * |-------|-------|-------|-------|
+ * 1 PFLAG3_VIEW_IS_ANIMATING_TRANSFORM
+ * 1 PFLAG3_VIEW_IS_ANIMATING_ALPHA
+ * 1 PFLAG3_IS_LAID_OUT
+ * 1 PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT
+ * 1 PFLAG3_CALLED_SUPER
+ * |-------|-------|-------|-------|
+ */
/**
* Flag indicating that view has a transform animation set on it. This is used to track whether
@@ -2268,6 +2371,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
static final int PFLAG3_CALLED_SUPER = 0x10;
+ /**
+ * Flag indicating that an view will be clipped to its outline.
+ */
+ static final int PFLAG3_CLIP_TO_OUTLINE = 0x20;
+
+ /**
+ * Flag indicating that we're in the process of applying window insets.
+ */
+ static final int PFLAG3_APPLYING_INSETS = 0x40;
+
+ /**
+ * Flag indicating that we're in the process of fitting system windows using the old method.
+ */
+ static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x80;
+
+ /**
+ * Flag indicating that an view will cast a shadow onto the Z=0 plane if elevated.
+ */
+ static final int PFLAG3_CASTS_SHADOW = 0x100;
+
+ /**
+ * Flag indicating that view will be transformed by the global camera if rotated in 3d, or given
+ * a non-0 Z translation.
+ */
+ static final int PFLAG3_USES_GLOBAL_CAMERA = 0x200;
/* End of masks for mPrivateFlags3 */
@@ -2664,6 +2792,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 +2819,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)
*
@@ -2887,6 +3021,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@ViewDebug.ExportedProperty
float mTranslationY = 0f;
+ @ViewDebug.ExportedProperty
+ float mTranslationZ = 0f;
+
/**
* The amount of scale in the x direction around the pivot point. A
* value of 1 means no scaling is applied.
@@ -3125,6 +3262,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@ViewDebug.ExportedProperty(deepExport = true, prefix = "bg_")
private Drawable mBackground;
+ /**
+ * Display list used for backgrounds.
+ * <p>
+ * When non-null and valid, this is expected to contain an up-to-date copy
+ * of the background drawable. It is cleared on temporary detach and reset
+ * on cleanup.
+ */
+ private DisplayList mBackgroundDisplayList;
+
private int mBackgroundResource;
private boolean mBackgroundSizeChanged;
@@ -3178,6 +3324,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private OnDragListener mOnDragListener;
private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
+
+ OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
}
ListenerInfo mListenerInfo;
@@ -3196,6 +3344,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private int[] mDrawableState = null;
/**
+ * Stores the outline of the view, passed down to the DisplayList level for
+ * defining shadow shape and clipping.
+ */
+ private Path mOutline;
+
+ /**
* When this view has focus and the next focus is {@link #FOCUS_LEFT},
* the user may specify which view to go to next.
*/
@@ -3398,6 +3552,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private Bitmap mDrawingCache;
private Bitmap mUnscaledDrawingCache;
+ /**
+ * Display list used for the View content.
+ * <p>
+ * When non-null and valid, this is expected to contain an up-to-date copy
+ * of the View content. It is cleared on temporary detach and reset on
+ * cleanup.
+ */
DisplayList mDisplayList;
/**
@@ -3485,27 +3646,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;
@@ -3528,6 +3726,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
float tx = 0;
float ty = 0;
+ float tz = 0;
float rotation = 0;
float rotationX = 0;
float rotationY = 0;
@@ -3607,6 +3806,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
ty = a.getDimensionPixelOffset(attr, 0);
transformSet = true;
break;
+ case com.android.internal.R.styleable.View_translationZ:
+ tz = a.getDimensionPixelOffset(attr, 0);
+ transformSet = true;
+ break;
case com.android.internal.R.styleable.View_rotation:
rotation = a.getFloat(attr, 0);
transformSet = true;
@@ -3836,6 +4039,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
case R.styleable.View_layerType:
setLayerType(a.getInt(attr, LAYER_TYPE_NONE), null);
break;
+ case R.styleable.View_castsShadow:
+ if (a.getBoolean(attr, false)) {
+ mPrivateFlags3 |= PFLAG3_CASTS_SHADOW;
+ }
+ break;
case R.styleable.View_textDirection:
// Clear any text direction flag already set
mPrivateFlags2 &= ~PFLAG2_TEXT_DIRECTION_MASK;
@@ -3859,6 +4067,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
case R.styleable.View_accessibilityLiveRegion:
setAccessibilityLiveRegion(a.getInt(attr, ACCESSIBILITY_LIVE_REGION_DEFAULT));
break;
+ case R.styleable.View_sharedElementName:
+ setSharedElementName(a.getString(attr));
+ break;
}
}
@@ -3948,6 +4159,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (transformSet) {
setTranslationX(tx);
setTranslationY(ty);
+ setTranslationZ(tz);
setRotation(rotation);
setRotationX(rotationX);
setRotationY(rotationY);
@@ -4596,7 +4808,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()");
}
@@ -4614,12 +4826,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
}
+ manageFocusHotspot(true, oldFocus);
onFocusChanged(true, direction, previouslyFocusedRect);
refreshDrawableState();
}
}
/**
+ * Forwards focus information to the background drawable, if necessary. When
+ * the view is gaining focus, <code>v</code> is the previous focus holder.
+ * When the view is losing focus, <code>v</code> is the next focus holder.
+ *
+ * @param focused whether this view is focused
+ * @param v previous or the next focus holder, or null if none
+ */
+ private void manageFocusHotspot(boolean focused, View v) {
+ if (mBackground != null && mBackground.supportsHotspots()) {
+ final Rect r = new Rect();
+ if (v != null) {
+ v.getBoundsOnScreen(r);
+ final int[] location = new int[2];
+ getLocationOnScreen(location);
+ r.offset(-location[0], -location[1]);
+ } else {
+ r.set(mLeft, mTop, mRight, mBottom);
+ }
+
+ final float x = r.exactCenterX();
+ final float y = r.exactCenterY();
+ mBackground.setHotspot(Drawable.HOTSPOT_FOCUS, x, y);
+
+ if (!focused) {
+ mBackground.removeHotspot(Drawable.HOTSPOT_FOCUS);
+ }
+ }
+ }
+
+ /**
* Request that a rectangle of this view be visible on the screen,
* scrolling if necessary just enough.
*
@@ -4705,7 +4948,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
System.out.println(this + " clearFocus()");
}
- clearFocusInternal(true, true);
+ clearFocusInternal(null, true, true);
}
/**
@@ -4717,10 +4960,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param refocus when propagate is true, specifies whether to request the
* root view place new focus
*/
- void clearFocusInternal(boolean propagate, boolean refocus) {
+ void clearFocusInternal(View focused, boolean propagate, boolean refocus) {
if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
mPrivateFlags &= ~PFLAG_FOCUSED;
+ if (hasFocus()) {
+ manageFocusHotspot(false, focused);
+ }
+
if (propagate && mParent != null) {
mParent.clearChildFocus(this);
}
@@ -4754,12 +5001,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* after calling this method. Otherwise, the view hierarchy may be left in
* an inconstent state.
*/
- void unFocus() {
+ void unFocus(View focused) {
if (DBG) {
System.out.println(this + " unFocus()");
}
- clearFocusInternal(false, false);
+ clearFocusInternal(focused, false, false);
}
/**
@@ -4807,7 +5054,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 +5915,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 +5933,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);
}
@@ -5901,10 +6150,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return {@code true} if this view applied the insets and it should not
* continue propagating further down the hierarchy, {@code false} otherwise.
* @see #getFitsSystemWindows()
- * @see #setFitsSystemWindows(boolean)
+ * @see #setFitsSystemWindows(boolean)
* @see #setSystemUiVisibility(int)
+ *
+ * @deprecated As of API XX use {@link #dispatchApplyWindowInsets(WindowInsets)} to apply
+ * insets to views. Views should override {@link #onApplyWindowInsets(WindowInsets)} or use
+ * {@link #setOnApplyWindowInsetsListener(android.view.View.OnApplyWindowInsetsListener)}
+ * to implement handling their own insets.
*/
protected boolean fitSystemWindows(Rect insets) {
+ if ((mPrivateFlags3 & PFLAG3_APPLYING_INSETS) == 0) {
+ // If we're not in the process of dispatching the newer apply insets call,
+ // that means we're not in the compatibility path. Dispatch into the newer
+ // apply insets path and take things from there.
+ try {
+ mPrivateFlags3 |= PFLAG3_FITTING_SYSTEM_WINDOWS;
+ return !dispatchApplyWindowInsets(new WindowInsets(insets)).hasInsets();
+ } finally {
+ mPrivateFlags3 &= ~PFLAG3_FITTING_SYSTEM_WINDOWS;
+ }
+ } else {
+ // We're being called from the newer apply insets path.
+ // Perform the standard fallback behavior.
+ return fitSystemWindowsInt(insets);
+ }
+ }
+
+ private boolean fitSystemWindowsInt(Rect insets) {
if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
mUserPaddingStart = UNDEFINED_PADDING;
mUserPaddingEnd = UNDEFINED_PADDING;
@@ -5924,6 +6196,97 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Called when the view should apply {@link WindowInsets} according to its internal policy.
+ *
+ * <p>This method should be overridden by views that wish to apply a policy different from or
+ * in addition to the default behavior. Clients that wish to force a view subtree
+ * to apply insets should call {@link #dispatchApplyWindowInsets(WindowInsets)}.</p>
+ *
+ * <p>Clients may supply an {@link OnApplyWindowInsetsListener} to a view. If one is set
+ * it will be called during dispatch instead of this method. The listener may optionally
+ * call this method from its own implementation if it wishes to apply the view's default
+ * insets policy in addition to its own.</p>
+ *
+ * <p>Implementations of this method should either return the insets parameter unchanged
+ * or a new {@link WindowInsets} cloned from the supplied insets with any insets consumed
+ * that this view applied itself. This allows new inset types added in future platform
+ * versions to pass through existing implementations unchanged without being erroneously
+ * consumed.</p>
+ *
+ * <p>By default if a view's {@link #setFitsSystemWindows(boolean) fitsSystemWindows}
+ * property is set then the view will consume the system window insets and apply them
+ * as padding for the view.</p>
+ *
+ * @param insets Insets to apply
+ * @return The supplied insets with any applied insets consumed
+ */
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
+ // We weren't called from within a direct call to fitSystemWindows,
+ // call into it as a fallback in case we're in a class that overrides it
+ // and has logic to perform.
+ if (fitSystemWindows(insets.getSystemWindowInsets())) {
+ return insets.cloneWithSystemWindowInsetsConsumed();
+ }
+ } else {
+ // We were called from within a direct call to fitSystemWindows.
+ if (fitSystemWindowsInt(insets.getSystemWindowInsets())) {
+ return insets.cloneWithSystemWindowInsetsConsumed();
+ }
+ }
+ return insets;
+ }
+
+ /**
+ * Set an {@link OnApplyWindowInsetsListener} to take over the policy for applying
+ * window insets to this view. The listener's
+ * {@link OnApplyWindowInsetsListener#onApplyWindowInsets(View, WindowInsets) onApplyWindowInsets}
+ * method will be called instead of the view's
+ * {@link #onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method.
+ *
+ * @param listener Listener to set
+ *
+ * @see #onApplyWindowInsets(WindowInsets)
+ */
+ public void setOnApplyWindowInsetsListener(OnApplyWindowInsetsListener listener) {
+ getListenerInfo().mOnApplyWindowInsetsListener = listener;
+ }
+
+ /**
+ * Request to apply the given window insets to this view or another view in its subtree.
+ *
+ * <p>This method should be called by clients wishing to apply insets corresponding to areas
+ * obscured by window decorations or overlays. This can include the status and navigation bars,
+ * action bars, input methods and more. New inset categories may be added in the future.
+ * The method returns the insets provided minus any that were applied by this view or its
+ * children.</p>
+ *
+ * <p>Clients wishing to provide custom behavior should override the
+ * {@link #onApplyWindowInsets(WindowInsets)} method or alternatively provide a
+ * {@link OnApplyWindowInsetsListener} via the
+ * {@link #setOnApplyWindowInsetsListener(View.OnApplyWindowInsetsListener) setOnApplyWindowInsetsListener}
+ * method.</p>
+ *
+ * <p>This method replaces the older {@link #fitSystemWindows(Rect) fitSystemWindows} method.
+ * </p>
+ *
+ * @param insets Insets to apply
+ * @return The provided insets minus the insets that were consumed
+ */
+ public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
+ try {
+ mPrivateFlags3 |= PFLAG3_APPLYING_INSETS;
+ if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
+ return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this, insets);
+ } else {
+ return onApplyWindowInsets(insets);
+ }
+ } finally {
+ mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS;
+ }
+ }
+
+ /**
* @hide Compute the insets that should be consumed by this view and the ones
* that should propagate to those under it.
*/
@@ -5995,6 +6358,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Ask that a new dispatch of {@link #fitSystemWindows(Rect)} be performed.
+ * @deprecated Use {@link #requestApplyInsets()} for newer platform versions.
*/
public void requestFitSystemWindows() {
if (mParent != null) {
@@ -6003,6 +6367,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Ask that a new dispatch of {@link #onApplyWindowInsets(WindowInsets)} be performed.
+ */
+ public void requestApplyInsets() {
+ requestFitSystemWindows();
+ }
+
+ /**
* For use by PhoneWindow to make its own system window fitting optional.
* @hide
*/
@@ -6021,6 +6392,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 +6404,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 +6563,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 +6586,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 +6616,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 +6986,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 +7005,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 +7017,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 +7067,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 +7081,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 +7101,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 +7131,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);
@@ -6803,7 +7179,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Returns whether this View is accessibility focused.
*
* @return True if this View is accessibility focused.
- * @hide
*/
public boolean isAccessibilityFocused() {
return (mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0;
@@ -7151,11 +7526,38 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Gets whether this view should be exposed for accessibility.
+ * Computes whether this view should be exposed for accessibility. In
+ * general, views that are interactive or provide information are exposed
+ * while views that serve only as containers are hidden.
+ * <p>
+ * If an ancestor of this view has importance
+ * {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS}, this method
+ * returns <code>false</code>.
+ * <p>
+ * Otherwise, the value is computed according to the view's
+ * {@link #getImportantForAccessibility()} value:
+ * <ol>
+ * <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_NO} or
+ * {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS}, return <code>false
+ * </code>
+ * <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_YES}, return <code>true</code>
+ * <li>{@link #IMPORTANT_FOR_ACCESSIBILITY_AUTO}, return <code>true</code> if
+ * view satisfies any of the following:
+ * <ul>
+ * <li>Is actionable, e.g. {@link #isClickable()},
+ * {@link #isLongClickable()}, or {@link #isFocusable()}
+ * <li>Has an {@link AccessibilityDelegate}
+ * <li>Has an interaction listener, e.g. {@link OnTouchListener},
+ * {@link OnKeyListener}, etc.
+ * <li>Is an accessibility live region, e.g.
+ * {@link #getAccessibilityLiveRegion()} is not
+ * {@link #ACCESSIBILITY_LIVE_REGION_NONE}.
+ * </ul>
+ * </ol>
*
* @return Whether the view is exposed for accessibility.
- *
- * @hide
+ * @see #setImportantForAccessibility(int)
+ * @see #getImportantForAccessibility()
*/
public boolean isImportantForAccessibility() {
final int mode = (mPrivateFlags2 & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK)
@@ -7208,9 +7610,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param children The list of children for accessibility.
*/
public void addChildrenForAccessibility(ArrayList<View> children) {
- if (includeForAccessibility()) {
- children.add(this);
- }
+
}
/**
@@ -7224,7 +7624,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
public boolean includeForAccessibility() {
- //noinspection SimplifiableIfStatement
if (mAttachInfo != null) {
return (mAttachInfo.mAccessibilityFetchFlags
& AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
@@ -7247,7 +7646,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 +7665,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
*/
@@ -7584,8 +7983,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
public void dispatchStartTemporaryDetach() {
- clearDisplayList();
-
onStartTemporaryDetach();
}
@@ -7946,7 +8343,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 +8355,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 +8374,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 +8387,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 +8398,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 +8412,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 +8424,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;
}
@@ -8306,7 +8705,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* <p>When implementing this, you probably also want to implement
* {@link #onCheckIsTextEditor()} to indicate you will return a
- * non-null InputConnection.
+ * non-null InputConnection.</p>
+ *
+ * <p>Also, take good care to fill in the {@link android.view.inputmethod.EditorInfo}
+ * object correctly and in its entirety, so that the connected IME can rely
+ * on its values. For example, {@link android.view.inputmethod.EditorInfo#initialSelStart}
+ * and {@link android.view.inputmethod.EditorInfo#initialSelEnd} members
+ * must be filled in with the correct cursor position for IMEs to work correctly
+ * with your application.</p>
*
* @param outAttrs Fill in with attribute information about the connection.
*/
@@ -8742,12 +9148,49 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
break;
}
+
+ if (mBackground != null && mBackground.supportsHotspots()) {
+ manageTouchHotspot(event);
+ }
+
return true;
}
return false;
}
+ private void manageTouchHotspot(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ final int index = event.getActionIndex();
+ setPointerHotspot(event, index);
+ } break;
+ case MotionEvent.ACTION_MOVE: {
+ final int count = event.getPointerCount();
+ for (int index = 0; index < count; index++) {
+ setPointerHotspot(event, index);
+ }
+ } break;
+ case MotionEvent.ACTION_POINTER_UP: {
+ final int actionIndex = event.getActionIndex();
+ final int pointerId = event.getPointerId(actionIndex);
+ mBackground.removeHotspot(pointerId);
+ } break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mBackground.clearHotspots();
+ break;
+ }
+ }
+
+ private void setPointerHotspot(MotionEvent event, int index) {
+ final int id = event.getPointerId(index);
+ final float x = event.getX(index);
+ final float y = event.getY(index);
+ mBackground.setHotspot(id, x, y);
+ }
+
/**
* @hide
*/
@@ -8947,7 +9390,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if ((changed & VISIBILITY_MASK) != 0) {
// If the view is invisible, cleanup its display list to free up resources
- if (newVisibility != VISIBLE) {
+ if (newVisibility != VISIBLE && mAttachInfo != null) {
cleanupDraw();
}
@@ -10344,6 +10787,224 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * The depth location of this view relative to its parent.
+ *
+ * @return The depth of this view relative to its parent.
+ */
+ @ViewDebug.ExportedProperty(category = "drawing")
+ public float getTranslationZ() {
+ return mTransformationInfo != null ? mTransformationInfo.mTranslationZ : 0;
+ }
+
+ /**
+ * Sets the depth location of this view relative to its parent.
+ *
+ * @attr ref android.R.styleable#View_translationZ
+ */
+ public void setTranslationZ(float translationZ) {
+ ensureTransformationInfo();
+ final TransformationInfo info = mTransformationInfo;
+ if (info.mTranslationZ != translationZ) {
+ invalidateViewProperty(true, false);
+ info.mTranslationZ = translationZ;
+ info.mMatrixDirty = true;
+ invalidateViewProperty(false, true);
+ if (mDisplayList != null) {
+ mDisplayList.setTranslationZ(translationZ);
+ }
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
+ // View was rejected last time it was drawn by its parent; this may have changed
+ invalidateParentIfNeeded();
+ }
+ }
+ }
+
+ /**
+ * Copies the Outline of the View into the Path parameter.
+ * <p>
+ * If the outline is not set, the parameter Path is set to empty.
+ *
+ * @param outline Path into which View's outline will be copied. Must be non-null.
+ *
+ * @see #setOutline(Path)
+ * @see #getClipToOutline()
+ * @see #setClipToOutline(boolean)
+ */
+ public final void getOutline(@NonNull Path outline) {
+ if (outline == null) {
+ throw new IllegalArgumentException("Path must be non-null");
+ }
+ if (mOutline == null) {
+ outline.reset();
+ } else {
+ outline.set(mOutline);
+ }
+ }
+
+ /**
+ * Sets the outline of the view, which defines the shape of the shadow it
+ * casts, and can used for clipping.
+ * <p>
+ * The outline path of a View must be {@link android.graphics.Path#isConvex() convex}.
+ * <p>
+ * If the outline is not set, or {@link Path#isEmpty()}, shadows will be
+ * cast from the bounds of the View, and clipToOutline will be ignored.
+ *
+ * @param outline The new outline of the view. Must be non-null, and convex.
+ *
+ * @see #setCastsShadow(boolean)
+ * @see #getOutline(Path)
+ * @see #getClipToOutline()
+ * @see #setClipToOutline(boolean)
+ */
+ public void setOutline(@NonNull Path outline) {
+ if (outline == null) {
+ throw new IllegalArgumentException("Path must be non-null");
+ }
+ if (!outline.isConvex()) {
+ throw new IllegalArgumentException("Path must be convex");
+ }
+ // always copy the path since caller may reuse
+ if (mOutline == null) {
+ mOutline = new Path(outline);
+ } else {
+ mOutline.set(outline);
+ }
+
+ if (mDisplayList != null) {
+ mDisplayList.setOutline(outline);
+ }
+ }
+
+ /**
+ * Returns whether the outline of the View will be used for clipping.
+ *
+ * @see #getOutline(Path)
+ * @see #setOutline(Path)
+ */
+ public final boolean getClipToOutline() {
+ return ((mPrivateFlags3 & PFLAG3_CLIP_TO_OUTLINE) != 0);
+ }
+
+ /**
+ * Sets whether the outline of the View will be used for clipping.
+ * <p>
+ * The current implementation of outline clipping uses Canvas#clipPath(),
+ * and thus does not support anti-aliasing, and is expensive in terms of
+ * graphics performance. Therefore, it is strongly recommended that this
+ * property only be set temporarily, as in an animation. For the same
+ * reasons, there is no parallel XML attribute for this property.
+ * <p>
+ * If the outline of the view is not set or is empty, no clipping will be
+ * performed.
+ *
+ * @see #getOutline(Path)
+ * @see #setOutline(Path)
+ */
+ public void setClipToOutline(boolean clipToOutline) {
+ // TODO : Add a fast invalidation here.
+ if (getClipToOutline() != clipToOutline) {
+ if (clipToOutline) {
+ mPrivateFlags3 |= PFLAG3_CLIP_TO_OUTLINE;
+ } else {
+ mPrivateFlags3 &= ~PFLAG3_CLIP_TO_OUTLINE;
+ }
+ if (mDisplayList != null) {
+ mDisplayList.setClipToOutline(clipToOutline);
+ }
+ }
+ }
+
+ /**
+ * Returns whether the View will cast shadows when its
+ * {@link #setTranslationZ(float) z translation} is greater than 0, or it is
+ * rotated in 3D.
+ *
+ * @see #setTranslationZ(float)
+ * @see #setRotationX(float)
+ * @see #setRotationY(float)
+ * @see #setCastsShadow(boolean)
+ * @attr ref android.R.styleable#View_castsShadow
+ */
+ public final boolean getCastsShadow() {
+ return ((mPrivateFlags3 & PFLAG3_CASTS_SHADOW) != 0);
+ }
+
+ /**
+ * Set to true to enable this View to cast shadows.
+ * <p>
+ * If enabled, and the View has a z translation greater than 0, or is
+ * rotated in 3D, the shadow will be cast onto its parent at the z = 0
+ * plane.
+ * <p>
+ * The shape of the shadow being cast is defined by the
+ * {@link #setOutline(Path) outline} of the view, or the rectangular bounds
+ * of the view if the outline is not set or is empty.
+ *
+ * @see #setTranslationZ(float)
+ * @see #getCastsShadow()
+ * @attr ref android.R.styleable#View_castsShadow
+ */
+ public void setCastsShadow(boolean castsShadow) {
+ // TODO : Add a fast invalidation here.
+ if (getCastsShadow() != castsShadow) {
+ if (castsShadow) {
+ mPrivateFlags3 |= PFLAG3_CASTS_SHADOW;
+ } else {
+ mPrivateFlags3 &= ~PFLAG3_CASTS_SHADOW;
+ }
+ if (mDisplayList != null) {
+ mDisplayList.setCastsShadow(castsShadow);
+ }
+ }
+ }
+
+ /**
+ * Returns whether the View will be transformed by the global camera.
+ *
+ * @see #setUsesGlobalCamera(boolean)
+ *
+ * @hide
+ */
+ public final boolean getUsesGlobalCamera() {
+ return ((mPrivateFlags3 & PFLAG3_USES_GLOBAL_CAMERA) != 0);
+ }
+
+ /**
+ * Sets whether the View should be transformed by the global camera.
+ * <p>
+ * If the view has a Z translation or 3D rotation, perspective from the
+ * global camera will be applied. This enables an app to transform multiple
+ * views in 3D with coherent perspective projection among them all.
+ * <p>
+ * Setting this to true will cause {@link #setCameraDistance() camera distance}
+ * to be ignored, as the global camera's position will dictate perspective
+ * transform.
+ * <p>
+ * This should not be used in conjunction with {@link android.graphics.Camera}.
+ *
+ * @see #getUsesGlobalCamera()
+ * @see #setTranslationZ(float)
+ * @see #setRotationX(float)
+ * @see #setRotationY(float)
+ *
+ * @hide
+ */
+ public void setUsesGlobalCamera(boolean usesGlobalCamera) {
+ // TODO : Add a fast invalidation here.
+ if (getUsesGlobalCamera() != usesGlobalCamera) {
+ if (usesGlobalCamera) {
+ mPrivateFlags3 |= PFLAG3_USES_GLOBAL_CAMERA;
+ } else {
+ mPrivateFlags3 &= ~PFLAG3_USES_GLOBAL_CAMERA;
+ }
+ if (mDisplayList != null) {
+ mDisplayList.setUsesGlobalCamera(usesGlobalCamera);
+ }
+ }
+ }
+
+ /**
* Hit rectangle in parent's coordinates
*
* @param outRect The hit rectangle of the view.
@@ -10797,94 +11458,54 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
(!(mParent instanceof ViewGroup) ||
!((ViewGroup) mParent).isViewTransitioning(this));
}
+
/**
* Mark the area defined by dirty as needing to be drawn. If the view is
- * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some point
- * in the future. This must be called from a UI thread. To call from a non-UI
- * thread, call {@link #postInvalidate()}.
+ * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some
+ * point in the future.
+ * <p>
+ * This must be called from a UI thread. To call from a non-UI thread, call
+ * {@link #postInvalidate()}.
+ * <p>
+ * <b>WARNING:</b> In API 19 and below, this method may be destructive to
+ * {@code dirty}.
*
- * WARNING: This method is destructive to dirty.
* @param dirty the rectangle representing the bounds of the dirty region
*/
public void invalidate(Rect dirty) {
- if (skipInvalidate()) {
- return;
- }
- if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||
- (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID ||
- (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED) {
- mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
- mPrivateFlags |= PFLAG_INVALIDATED;
- 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;
- final Rect r = ai.mTmpInvalRect;
- r.set(dirty.left - scrollX, dirty.top - scrollY,
- dirty.right - scrollX, dirty.bottom - scrollY);
- mParent.invalidateChild(this, r);
- }
- }
+ final int scrollX = mScrollX;
+ final int scrollY = mScrollY;
+ invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,
+ dirty.right - scrollX, dirty.bottom - scrollY, true, false);
}
/**
- * Mark the area defined by the rect (l,t,r,b) as needing to be drawn.
- * The coordinates of the dirty rect are relative to the view.
- * If the view is visible, {@link #onDraw(android.graphics.Canvas)}
- * will be called at some point in the future. This must be called from
- * a UI thread. To call from a non-UI thread, call {@link #postInvalidate()}.
+ * Mark the area defined by the rect (l,t,r,b) as needing to be drawn. The
+ * coordinates of the dirty rect are relative to the view. If the view is
+ * visible, {@link #onDraw(android.graphics.Canvas)} will be called at some
+ * point in the future.
+ * <p>
+ * This must be called from a UI thread. To call from a non-UI thread, call
+ * {@link #postInvalidate()}.
+ *
* @param l the left position of the dirty region
* @param t the top position of the dirty region
* @param r the right position of the dirty region
* @param b the bottom position of the dirty region
*/
public void invalidate(int l, int t, int r, int b) {
- if (skipInvalidate()) {
- return;
- }
- if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||
- (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID ||
- (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED) {
- mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
- mPrivateFlags |= PFLAG_INVALIDATED;
- 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;
- final Rect tmpr = ai.mTmpInvalRect;
- tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY);
- p.invalidateChild(this, tmpr);
- }
- }
+ final int scrollX = mScrollX;
+ final int scrollY = mScrollY;
+ invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
}
/**
* Invalidate the whole view. If the view is visible,
* {@link #onDraw(android.graphics.Canvas)} will be called at some point in
- * the future. This must be called from a UI thread. To call from a non-UI thread,
- * call {@link #postInvalidate()}.
+ * the future.
+ * <p>
+ * This must be called from a UI thread. To call from a non-UI thread, call
+ * {@link #postInvalidate()}.
*/
public void invalidate() {
invalidate(true);
@@ -10892,47 +11513,108 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* This is where the invalidate() work actually happens. A full invalidate()
- * causes the drawing cache to be invalidated, but this function can be called with
- * invalidateCache set to false to skip that invalidation step for cases that do not
- * need it (for example, a component that remains at the same dimensions with the same
- * content).
+ * causes the drawing cache to be invalidated, but this function can be
+ * called with invalidateCache set to false to skip that invalidation step
+ * for cases that do not need it (for example, a component that remains at
+ * the same dimensions with the same content).
*
- * @param invalidateCache Whether the drawing cache for this view should be invalidated as
- * well. This is usually true for a full invalidate, but may be set to false if the
- * View's contents or dimensions have not changed.
+ * @param invalidateCache Whether the drawing cache for this view should be
+ * invalidated as well. This is usually true for a full
+ * invalidate, but may be set to false if the View's contents or
+ * dimensions have not changed.
*/
void invalidate(boolean invalidateCache) {
+ invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
+ }
+
+ void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
+ boolean fullInvalidate) {
if (skipInvalidate()) {
return;
}
- if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||
- (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) ||
- (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || isOpaque() != mLastIsOpaque) {
- mLastIsOpaque = isOpaque();
- mPrivateFlags &= ~PFLAG_DRAWN;
+
+ if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
+ || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
+ || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
+ || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
+ if (fullInvalidate) {
+ mLastIsOpaque = isOpaque();
+ mPrivateFlags &= ~PFLAG_DRAWN;
+ }
+
mPrivateFlags |= PFLAG_DIRTY;
+
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
+
+ // Propagate the damage rectangle to the parent view.
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 && l < r && t < b) {
+ final Rect damage = ai.mTmpInvalRect;
+ damage.set(l, t, r, b);
+ p.invalidateChild(this, damage);
+ }
+
+ // Damage the entire projection receiver, if necessary.
+ if (mBackground != null && mBackground.isProjected()) {
+ final View receiver = getProjectionReceiver();
+ if (receiver != null) {
+ receiver.damageInParent();
}
}
- if (p != null && ai != null) {
- final Rect r = ai.mTmpInvalRect;
- r.set(0, 0, mRight - mLeft, mBottom - mTop);
- // Don't call invalidate -- we don't want to internally scroll
- // our own bounds
- p.invalidateChild(this, r);
+ // Damage the entire IsolatedZVolume recieving this view's shadow.
+ if (getCastsShadow() && getTranslationZ() != 0) {
+ damageIsolatedZVolume();
+ }
+ }
+ }
+
+ /**
+ * @return this view's projection receiver, or {@code null} if none exists
+ */
+ private View getProjectionReceiver() {
+ ViewParent p = getParent();
+ while (p != null && p instanceof View) {
+ final View v = (View) p;
+ if (v.isProjectionReceiver()) {
+ return v;
+ }
+ p = p.getParent();
+ }
+
+ return null;
+ }
+
+ /**
+ * @return whether the view is a projection receiver
+ */
+ private boolean isProjectionReceiver() {
+ return mBackground != null;
+ }
+
+ /**
+ * Damage area of the screen covered by the current isolated Z volume
+ *
+ * This method will guarantee that any changes to shadows cast by a View
+ * are damaged on the screen for future redraw.
+ */
+ private void damageIsolatedZVolume() {
+ final AttachInfo ai = mAttachInfo;
+ if (ai != null) {
+ ViewParent p = getParent();
+ while (p != null) {
+ if (p instanceof ViewGroup) {
+ final ViewGroup vg = (ViewGroup) p;
+ if (vg.hasIsolatedZVolume()) {
+ vg.damageInParent();
+ return;
+ }
+ }
+ p = p.getParent();
}
}
}
@@ -10963,16 +11645,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
invalidate(false);
} else {
- final AttachInfo ai = mAttachInfo;
- final ViewParent p = mParent;
- if (p != null && ai != null) {
- final Rect r = ai.mTmpInvalRect;
- r.set(0, 0, mRight - mLeft, mBottom - mTop);
- if (mParent instanceof ViewGroup) {
- ((ViewGroup) mParent).invalidateChildFast(this, r);
- } else {
- mParent.invalidateChild(this, r);
- }
+ damageInParent();
+ }
+ if (invalidateParent && getCastsShadow() && getTranslationZ() != 0) {
+ damageIsolatedZVolume();
+ }
+ }
+
+ /**
+ * Tells the parent view to damage this view's bounds.
+ *
+ * @hide
+ */
+ protected void damageInParent() {
+ final AttachInfo ai = mAttachInfo;
+ final ViewParent p = mParent;
+ if (p != null && ai != null) {
+ final Rect r = ai.mTmpInvalRect;
+ r.set(0, 0, mRight - mLeft, mBottom - mTop);
+ if (mParent instanceof ViewGroup) {
+ ((ViewGroup) mParent).invalidateChildFast(this, r);
+ } else {
+ mParent.invalidateChild(this, r);
}
}
}
@@ -11096,6 +11790,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * @hide
+ */
+ public HardwareRenderer getHardwareRenderer() {
+ return mAttachInfo != null ? mAttachInfo.mHardwareRenderer : null;
+ }
+
+ /**
* <p>Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.</p>
*
@@ -11212,10 +11913,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 +12406,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 +12430,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;
}
@@ -12121,10 +12822,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
InputMethodManager imm = InputMethodManager.peekInstance();
imm.focusIn(this);
}
-
- if (mDisplayList != null) {
- mDisplayList.clearDirty();
- }
}
/**
@@ -12231,7 +12928,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) {
}
/**
@@ -12425,6 +13122,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #onAttachedToWindow()
*/
protected void onDetachedFromWindow() {
+ }
+
+ /**
+ * This is a framework-internal mirror of onDetachedFromWindow() that's called
+ * after onDetachedFromWindow().
+ *
+ * If you override this you *MUST* call super.onDetachedFromWindowInternal()!
+ * The super method should be called at the end of the overriden method to ensure
+ * subclasses are destroyed first
+ *
+ * @hide
+ */
+ protected void onDetachedFromWindowInternal() {
mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
@@ -12442,15 +13152,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
private void cleanupDraw() {
+ resetDisplayList();
if (mAttachInfo != null) {
- if (mDisplayList != null) {
- mDisplayList.markDirty();
- mAttachInfo.mViewRootImpl.enqueueDisplayList(mDisplayList);
- }
mAttachInfo.mViewRootImpl.cancelInvalidate(this);
- } else {
- // Should never happen
- resetDisplayList();
}
}
@@ -12618,6 +13322,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
onDetachedFromWindow();
+ onDetachedFromWindowInternal();
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
@@ -12919,11 +13624,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
if (layerType == mLayerType) {
- if (layerType != LAYER_TYPE_NONE && paint != mLayerPaint) {
- mLayerPaint = paint == null ? new Paint() : paint;
- invalidateParentCaches();
- invalidate(true);
- }
+ setLayerPaint(paint);
return;
}
@@ -12980,7 +13681,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (layerType == LAYER_TYPE_HARDWARE) {
HardwareLayer layer = getHardwareLayer();
if (layer != null) {
- layer.setLayerPaint(paint);
+ layer.setLayerPaint(mLayerPaint);
}
invalidateViewProperty(false, false);
} else {
@@ -13040,19 +13741,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
switch (mLayerType) {
case LAYER_TYPE_HARDWARE:
- if (attachInfo.mHardwareRenderer != null &&
- attachInfo.mHardwareRenderer.isEnabled() &&
- attachInfo.mHardwareRenderer.validate()) {
- getHardwareLayer();
- // TODO: We need a better way to handle this case
- // If views have registered pre-draw listeners they need
- // to be notified before we build the layer. Those listeners
- // may however rely on other events to happen first so we
- // cannot just invoke them here until they don't cancel the
- // current frame
- if (!attachInfo.mTreeObserver.hasOnPreDrawListeners()) {
- attachInfo.mViewRootImpl.dispatchFlushHardwareLayerUpdates();
- }
+ getHardwareLayer();
+ // TODO: We need a better way to handle this case
+ // If views have registered pre-draw listeners they need
+ // to be notified before we build the layer. Those listeners
+ // may however rely on other events to happen first so we
+ // cannot just invoke them here until they don't cancel the
+ // current frame
+ if (!attachInfo.mTreeObserver.hasOnPreDrawListeners()) {
+ attachInfo.mViewRootImpl.dispatchFlushHardwareLayerUpdates();
}
break;
case LAYER_TYPE_SOFTWARE:
@@ -13073,8 +13770,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return null;
}
- if (!mAttachInfo.mHardwareRenderer.validate()) return null;
-
final int width = mRight - mLeft;
final int height = mBottom - mTop;
@@ -13084,16 +13779,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || mHardwareLayer == null) {
if (mHardwareLayer == null) {
- mHardwareLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(
- width, height, isOpaque());
+ mHardwareLayer = mAttachInfo.mHardwareRenderer.createDisplayListLayer(
+ width, height);
mLocalDirtyRect.set(0, 0, width, height);
- } else {
- if (mHardwareLayer.getWidth() != width || mHardwareLayer.getHeight() != height) {
- if (mHardwareLayer.resize(width, height)) {
- mLocalDirtyRect.set(0, 0, width, height);
- }
- }
-
+ } else if (mHardwareLayer.isValid()) {
// This should not be necessary but applications that change
// the parameters of their background drawable without calling
// this.setBackground(Drawable) can leave the view in a bad state
@@ -13101,23 +13790,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// not opaque.)
computeOpaqueFlags();
- final boolean opaque = isOpaque();
- if (mHardwareLayer.isValid() && mHardwareLayer.isOpaque() != opaque) {
- mHardwareLayer.setOpaque(opaque);
+ if (mHardwareLayer.prepare(width, height, isOpaque())) {
mLocalDirtyRect.set(0, 0, width, height);
}
}
// The layer is not valid if the underlying GPU resources cannot be allocated
+ mHardwareLayer.flushChanges();
if (!mHardwareLayer.isValid()) {
return null;
}
mHardwareLayer.setLayerPaint(mLayerPaint);
- mHardwareLayer.redrawLater(getHardwareLayerDisplayList(mHardwareLayer), mLocalDirtyRect);
- ViewRootImpl viewRoot = getViewRootImpl();
- if (viewRoot != null) viewRoot.pushHardwareLayerUpdate(mHardwareLayer);
-
+ DisplayList displayList = mHardwareLayer.startRecording();
+ if (getDisplayList(displayList, true) != displayList) {
+ throw new IllegalStateException("getDisplayList() didn't return"
+ + " the input displaylist for a hardware layer!");
+ }
+ mHardwareLayer.endRecording(mLocalDirtyRect);
mLocalDirtyRect.setEmpty();
}
@@ -13134,18 +13824,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
boolean destroyLayer(boolean valid) {
if (mHardwareLayer != null) {
- AttachInfo info = mAttachInfo;
- if (info != null && info.mHardwareRenderer != null &&
- info.mHardwareRenderer.isEnabled() &&
- (valid || info.mHardwareRenderer.validate())) {
-
- info.mHardwareRenderer.cancelLayerUpdate(mHardwareLayer);
- mHardwareLayer.destroy();
- mHardwareLayer = null;
+ mHardwareLayer.destroy();
+ mHardwareLayer = null;
- invalidate(true);
- invalidateParentCaches();
- }
+ invalidate(true);
+ invalidateParentCaches();
return true;
}
return false;
@@ -13260,20 +13943,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).
@@ -13284,7 +13953,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return A new or reused DisplayList object.
*/
private DisplayList getDisplayList(DisplayList displayList, boolean isLayer) {
- if (!canHaveDisplayList()) {
+ final HardwareRenderer renderer = getHardwareRenderer();
+ if (renderer == null || !canHaveDisplayList()) {
return null;
}
@@ -13308,7 +13978,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.
@@ -13363,13 +14033,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
} finally {
- displayList.end();
+ displayList.end(renderer, canvas);
displayList.setCaching(caching);
if (isLayer) {
displayList.setLeftTopRightBottom(0, 0, width, height);
} else {
setDisplayListProperties(displayList);
}
+
+ if (renderer != getHardwareRenderer()) {
+ Log.w(VIEW_LOG_TAG, "View was detached during a draw() call!");
+ // TODO: Should this be elevated to a crash?
+ // For now have it behaves the same as it previously did, it
+ // will result in the DisplayListData being destroyed later
+ // than it could be but oh well...
+ }
}
} else if (!isLayer) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
@@ -13380,19 +14058,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Get the DisplayList for the HardwareLayer
- *
- * @param layer The HardwareLayer whose DisplayList we want
- * @return A DisplayList fopr the specified HardwareLayer
- */
- private DisplayList getHardwareLayerDisplayList(HardwareLayer layer) {
- DisplayList displayList = getDisplayList(layer.getDisplayList(), true);
- layer.setDisplayList(displayList);
- return displayList;
- }
-
-
- /**
* <p>Returns a display list that can be used to draw this view again
* without executing its draw method.</p>
*
@@ -13405,15 +14070,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return mDisplayList;
}
- private void clearDisplayList() {
- if (mDisplayList != null) {
- mDisplayList.clear();
+ private void resetDisplayList() {
+ if (mDisplayList != null && mDisplayList.isValid()) {
+ mDisplayList.destroyDisplayListData();
}
- }
- private void resetDisplayList() {
- if (mDisplayList != null) {
- mDisplayList.reset();
+ if (mBackgroundDisplayList != null && mBackgroundDisplayList.isValid()) {
+ mBackgroundDisplayList.destroyDisplayListData();
}
}
@@ -14016,6 +14679,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
displayList.setClipToBounds(
(((ViewGroup) mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0);
}
+ if (this instanceof ViewGroup) {
+ displayList.setIsolatedZVolume(
+ (((ViewGroup) this).mGroupFlags & ViewGroup.FLAG_ISOLATED_Z_VOLUME) != 0);
+ }
+ displayList.setOutline(mOutline);
+ displayList.setClipToOutline(getClipToOutline());
+ displayList.setCastsShadow(getCastsShadow());
+ displayList.setUsesGlobalCamera(getUsesGlobalCamera());
float alpha = 1;
if (mParent instanceof ViewGroup && (((ViewGroup) mParent).mGroupFlags &
ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
@@ -14028,7 +14699,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
alpha = t.getAlpha();
}
if ((transformType & Transformation.TYPE_MATRIX) != 0) {
- displayList.setMatrix(t.getMatrix());
+ displayList.setStaticMatrix(t.getMatrix());
}
}
}
@@ -14043,6 +14714,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
displayList.setTransformationInfo(alpha,
mTransformationInfo.mTranslationX, mTransformationInfo.mTranslationY,
+ mTransformationInfo.mTranslationZ,
mTransformationInfo.mRotation, mTransformationInfo.mRotationX,
mTransformationInfo.mRotationY, mTransformationInfo.mScaleX,
mTransformationInfo.mScaleY);
@@ -14436,24 +15108,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int saveCount;
if (!dirtyOpaque) {
- final Drawable background = mBackground;
- if (background != null) {
- final int scrollX = mScrollX;
- final int scrollY = mScrollY;
-
- if (mBackgroundSizeChanged) {
- background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
- mBackgroundSizeChanged = false;
- }
-
- if ((scrollX | scrollY) == 0) {
- background.draw(canvas);
- } else {
- canvas.translate(scrollX, scrollY);
- background.draw(canvas);
- canvas.translate(-scrollX, -scrollY);
- }
- }
+ drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
@@ -14620,6 +15275,84 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Draws the background onto the specified canvas.
+ *
+ * @param canvas Canvas on which to draw the background
+ */
+ private void drawBackground(Canvas canvas) {
+ final Drawable background = mBackground;
+ if (background == null) {
+ return;
+ }
+
+ if (mBackgroundSizeChanged) {
+ background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
+ mBackgroundSizeChanged = false;
+ }
+
+ // Attempt to use a display list if requested.
+ if (canvas.isHardwareAccelerated() && mAttachInfo != null
+ && mAttachInfo.mHardwareRenderer != null) {
+ mBackgroundDisplayList = getDrawableDisplayList(background, mBackgroundDisplayList);
+
+ final DisplayList displayList = mBackgroundDisplayList;
+ if (displayList != null && displayList.isValid()) {
+ setBackgroundDisplayListProperties(displayList);
+ ((HardwareCanvas) canvas).drawDisplayList(displayList);
+ return;
+ }
+ }
+
+ final int scrollX = mScrollX;
+ final int scrollY = mScrollY;
+ if ((scrollX | scrollY) == 0) {
+ background.draw(canvas);
+ } else {
+ canvas.translate(scrollX, scrollY);
+ background.draw(canvas);
+ canvas.translate(-scrollX, -scrollY);
+ }
+ }
+
+ /**
+ * Set up background drawable display list properties.
+ *
+ * @param displayList Valid display list for the background drawable
+ */
+ private void setBackgroundDisplayListProperties(DisplayList displayList) {
+ displayList.setTranslationX(mScrollX);
+ displayList.setTranslationY(mScrollY);
+ }
+
+ /**
+ * Creates a new display list or updates the existing display list for the
+ * specified Drawable.
+ *
+ * @param drawable Drawable for which to create a display list
+ * @param displayList Existing display list, or {@code null}
+ * @return A valid display list for the specified drawable
+ */
+ private DisplayList getDrawableDisplayList(Drawable drawable, DisplayList displayList) {
+ if (displayList == null) {
+ displayList = DisplayList.create(drawable.getClass().getName());
+ }
+
+ final Rect bounds = drawable.getBounds();
+ final int width = bounds.width();
+ final int height = bounds.height();
+ final HardwareCanvas canvas = displayList.start(width, height);
+ drawable.draw(canvas);
+ displayList.end(getHardwareRenderer(), canvas);
+
+ // Set up drawable properties that are view-independent.
+ displayList.setLeftTopRightBottom(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ displayList.setProjectBackwards(drawable.isProjected());
+ displayList.setProjectionReceiver(true);
+ displayList.setClipToBounds(false);
+ return displayList;
+ }
+
+ /**
* Returns the overlay for this view, creating it if it does not yet exist.
* Adding drawables to the overlay will cause them to be displayed whenever
* the view itself is redrawn. Objects in the overlay should be actively
@@ -14960,9 +15693,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @param drawable the drawable to invalidate
*/
+ @Override
public void invalidateDrawable(Drawable drawable) {
if (verifyDrawable(drawable)) {
- final Rect dirty = drawable.getBounds();
+ final Rect dirty = drawable.getDirtyBounds();
final int scrollX = mScrollX;
final int scrollY = mScrollY;
@@ -14979,6 +15713,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param when the time at which the action must occur. Uses the
* {@link SystemClock#uptimeMillis} timebase.
*/
+ @Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
if (verifyDrawable(who) && what != null) {
final long delay = when - SystemClock.uptimeMillis();
@@ -14998,14 +15733,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param who the recipient of the action
* @param what the action to cancel
*/
+ @Override
public void unscheduleDrawable(Drawable who, Runnable what) {
if (verifyDrawable(who) && what != null) {
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
Choreographer.CALLBACK_ANIMATION, what, who);
- } else {
- ViewRootImpl.getRunQueue().removeCallbacks(what);
}
+ ViewRootImpl.getRunQueue().removeCallbacks(what);
}
}
@@ -15068,7 +15803,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @hide
*/
- public void onResolveDrawables(int layoutDirection) {
+ public void onResolveDrawables(@ResolvedLayoutDir int layoutDirection) {
}
/**
@@ -15115,7 +15850,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see Drawable#setState(int[])
*/
protected void drawableStateChanged() {
- Drawable d = mBackground;
+ final Drawable d = mBackground;
if (d != null && d.isStateful()) {
d.setState(getDrawableState());
}
@@ -15300,7 +16035,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
Drawable d= null;
if (resid != 0) {
- d = mResources.getDrawable(resid);
+ d = mContext.getDrawable(resid);
}
setBackground(d);
@@ -15835,8 +16570,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return false;
}
- transformMotionEventToGlobal(ev);
- ev.offsetLocation(info.mWindowLeft, info.mWindowTop);
+ final Matrix m = info.mTmpMatrix;
+ m.set(Matrix.IDENTITY_MATRIX);
+ transformMatrixToGlobal(m);
+ ev.transform(m);
return true;
}
@@ -15854,54 +16591,60 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return false;
}
- ev.offsetLocation(-info.mWindowLeft, -info.mWindowTop);
- transformMotionEventToLocal(ev);
+ final Matrix m = info.mTmpMatrix;
+ m.set(Matrix.IDENTITY_MATRIX);
+ transformMatrixToLocal(m);
+ ev.transform(m);
return true;
}
/**
- * Recursive helper method that applies transformations in post-order.
+ * Modifies the input matrix such that it maps view-local coordinates to
+ * on-screen coordinates.
*
- * @param ev the on-screen motion event
+ * @param m input matrix to modify
*/
- private void transformMotionEventToLocal(MotionEvent ev) {
+ void transformMatrixToGlobal(Matrix m) {
final ViewParent parent = mParent;
if (parent instanceof View) {
final View vp = (View) parent;
- vp.transformMotionEventToLocal(ev);
- ev.offsetLocation(vp.mScrollX, vp.mScrollY);
+ vp.transformMatrixToGlobal(m);
+ m.postTranslate(-vp.mScrollX, -vp.mScrollY);
} else if (parent instanceof ViewRootImpl) {
final ViewRootImpl vr = (ViewRootImpl) parent;
- ev.offsetLocation(0, vr.mCurScrollY);
+ vr.transformMatrixToGlobal(m);
+ m.postTranslate(0, -vr.mCurScrollY);
}
- ev.offsetLocation(-mLeft, -mTop);
+ m.postTranslate(mLeft, mTop);
if (!hasIdentityMatrix()) {
- ev.transform(getInverseMatrix());
+ m.postConcat(getMatrix());
}
}
/**
- * Recursive helper method that applies transformations in pre-order.
+ * Modifies the input matrix such that it maps on-screen coordinates to
+ * view-local coordinates.
*
- * @param ev the on-screen motion event
+ * @param m input matrix to modify
*/
- private void transformMotionEventToGlobal(MotionEvent ev) {
- if (!hasIdentityMatrix()) {
- ev.transform(getMatrix());
- }
-
- ev.offsetLocation(mLeft, mTop);
-
+ void transformMatrixToLocal(Matrix m) {
final ViewParent parent = mParent;
if (parent instanceof View) {
final View vp = (View) parent;
- ev.offsetLocation(-vp.mScrollX, -vp.mScrollY);
- vp.transformMotionEventToGlobal(ev);
+ vp.transformMatrixToLocal(m);
+ m.preTranslate(vp.mScrollX, vp.mScrollY);
} else if (parent instanceof ViewRootImpl) {
final ViewRootImpl vr = (ViewRootImpl) parent;
- ev.offsetLocation(0, -vr.mCurScrollY);
+ vr.transformMatrixToLocal(m);
+ m.preTranslate(0, vr.mCurScrollY);
+ }
+
+ m.preTranslate(-mLeft, -mTop);
+
+ if (!hasIdentityMatrix()) {
+ m.preConcat(getInverseMatrix());
}
}
@@ -16184,7 +16927,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Returns this view's tag.
*
- * @return the Object stored in this view as a tag
+ * @return the Object stored in this view as a tag, or {@code null} if not
+ * set
*
* @see #setTag(Object)
* @see #getTag(int)
@@ -16214,7 +16958,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @param key The key identifying the tag
*
- * @return the Object stored in this view as a tag
+ * @return the Object stored in this view as a tag, or {@code null} if not
+ * set
*
* @see #setTag(int, Object)
* @see #getTag()
@@ -17886,6 +18631,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;
}
@@ -17909,7 +18655,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;
@@ -17950,6 +18696,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;
@@ -18112,6 +18859,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+ /**
+ * Gets the Views in the hierarchy affected by entering and exiting Activity Scene transitions.
+ * @param transitioningViews This View will be added to transitioningViews if it is VISIBLE and
+ * a normal View or a ViewGroup with
+ * {@link android.view.ViewGroup#isTransitionGroup()} true.
+ * @hide
+ */
+ public void captureTransitioningViews(List<View> transitioningViews) {
+ if (getVisibility() == View.VISIBLE) {
+ transitioningViews.add(this);
+ }
+ }
+
+ /**
+ * Adds all Views that have {@link #getSharedElementName()} non-null to sharedElements.
+ * @param sharedElements Will contain all Views in the hierarchy having a shared element name.
+ * @hide
+ */
+ public void findSharedElements(Map<String, View> sharedElements) {
+ if (getVisibility() == VISIBLE) {
+ String sharedElementName = getSharedElementName();
+ if (sharedElementName != null) {
+ sharedElements.put(sharedElementName, this);
+ }
+ }
+ }
+
//
// Properties
//
@@ -18164,6 +18938,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
};
/**
+ * A Property wrapper around the <code>translationZ</code> functionality handled by the
+ * {@link View#setTranslationZ(float)} and {@link View#getTranslationZ()} methods.
+ */
+ public static final Property<View, Float> TRANSLATION_Z = new FloatProperty<View>("translationZ") {
+ @Override
+ public void setValue(View object, float value) {
+ object.setTranslationZ(value);
+ }
+
+ @Override
+ public Float get(View object) {
+ return object.getTranslationZ();
+ }
+ };
+
+ /**
* A Property wrapper around the <code>x</code> functionality handled by the
* {@link View#setX(float)} and {@link View#getX()} methods.
*/
@@ -18469,6 +19259,35 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Specifies that the shared name of the View to be shared with another Activity.
+ * When transitioning between Activities, the name links a UI element in the starting
+ * Activity to UI element in the called Activity. Names should be unique in the
+ * View hierarchy.
+ *
+ * @param sharedElementName The cross-Activity View identifier. The called Activity will use
+ * the name to match the location with a View in its layout.
+ * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.os.Bundle)
+ */
+ public void setSharedElementName(String sharedElementName) {
+ setTagInternal(com.android.internal.R.id.shared_element_name, sharedElementName);
+ }
+
+ /**
+ * Returns the shared name of the View to be shared with another Activity.
+ * When transitioning between Activities, the name links a UI element in the starting
+ * Activity to UI element in the called Activity. Names should be unique in the
+ * View hierarchy.
+ *
+ * <p>This returns null if the View is not a shared element or the name if it is.</p>
+ *
+ * @return The name used for this View for cross-Activity transitions or null if
+ * this View has not been identified as shared.
+ */
+ public String getSharedElementName() {
+ return (String) getTag(com.android.internal.R.id.shared_element_name);
+ }
+
+ /**
* Interface definition for a callback to be invoked when a hardware key event is
* dispatched to this view. The callback will be invoked before the key event is
* given to the view. This is only useful for hardware keyboards; a software input
@@ -18668,6 +19487,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public void onViewDetachedFromWindow(View v);
}
+ /**
+ * Listener for applying window insets on a view in a custom way.
+ *
+ * <p>Apps may choose to implement this interface if they want to apply custom policy
+ * to the way that window insets are treated for a view. If an OnApplyWindowInsetsListener
+ * is set, its
+ * {@link OnApplyWindowInsetsListener#onApplyWindowInsets(View, WindowInsets) onApplyWindowInsets}
+ * method will be called instead of the View's own
+ * {@link #onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method. The listener
+ * may optionally call the parameter View's <code>onApplyWindowInsets</code> method to apply
+ * the View's normal behavior as part of its own.</p>
+ */
+ public interface OnApplyWindowInsetsListener {
+ /**
+ * When {@link View#setOnApplyWindowInsetsListener(View.OnApplyWindowInsetsListener) set}
+ * on a View, this listener method will be called instead of the view's own
+ * {@link View#onApplyWindowInsets(WindowInsets) onApplyWindowInsets} method.
+ *
+ * @param v The view applying window insets
+ * @param insets The insets to apply
+ * @return The insets supplied, minus any insets that were consumed
+ */
+ public WindowInsets onApplyWindowInsets(View v, WindowInsets insets);
+ }
+
private final class UnsetPressedState implements Runnable {
public void run() {
setPressed(false);
@@ -18762,8 +19606,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final Callbacks mRootCallbacks;
- HardwareCanvas mHardwareCanvas;
-
IWindowId mIWindowId;
WindowId mWindowId;
@@ -18773,7 +19615,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/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index c3f064f..e67659c 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -212,6 +212,14 @@ public class ViewConfiguration {
*/
private static final int OVERFLING_DISTANCE = 6;
+ /**
+ * Configuration values for overriding {@link #hasPermanentMenuKey()} behavior.
+ * These constants must match the definition in res/values/config.xml.
+ */
+ private static final int HAS_PERMANENT_MENU_KEY_AUTODETECT = 0;
+ private static final int HAS_PERMANENT_MENU_KEY_TRUE = 1;
+ private static final int HAS_PERMANENT_MENU_KEY_FALSE = 2;
+
private final int mEdgeSlop;
private final int mFadingEdgeLength;
private final int mMinimumFlingVelocity;
@@ -296,12 +304,31 @@ public class ViewConfiguration {
mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f);
if (!sHasPermanentMenuKeySet) {
- IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- try {
- sHasPermanentMenuKey = !wm.hasNavigationBar();
- sHasPermanentMenuKeySet = true;
- } catch (RemoteException ex) {
- sHasPermanentMenuKey = false;
+ final int configVal = res.getInteger(
+ com.android.internal.R.integer.config_overrideHasPermanentMenuKey);
+
+ switch (configVal) {
+ default:
+ case HAS_PERMANENT_MENU_KEY_AUTODETECT: {
+ IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ try {
+ sHasPermanentMenuKey = !wm.hasNavigationBar();
+ sHasPermanentMenuKeySet = true;
+ } catch (RemoteException ex) {
+ sHasPermanentMenuKey = false;
+ }
+ }
+ break;
+
+ case HAS_PERMANENT_MENU_KEY_TRUE:
+ sHasPermanentMenuKey = true;
+ sHasPermanentMenuKeySet = true;
+ break;
+
+ case HAS_PERMANENT_MENU_KEY_FALSE:
+ sHasPermanentMenuKey = false;
+ sHasPermanentMenuKeySet = true;
+ break;
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 5763e72..f9b9401 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -31,6 +31,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Build;
+import android.os.Bundle;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
@@ -38,7 +39,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;
@@ -51,6 +51,8 @@ import com.android.internal.util.Predicate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
@@ -356,6 +358,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private static final int FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET = 0x800000;
/**
+ * When true, indicates that all 3d composited descendents are contained within this group, and
+ * will not be interleaved with other 3d composited content.
+ */
+ static final int FLAG_ISOLATED_Z_VOLUME = 0x1000000;
+
+ static final int FLAG_IS_TRANSITION_GROUP = 0x2000000;
+
+ static final int FLAG_IS_TRANSITION_GROUP_SET = 0x4000000;
+
+ /**
* Indicates which types of drawing caches are to be kept in memory.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
@@ -456,20 +468,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, 0);
+ this(context, attrs, 0);
}
- public ViewGroup(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
initViewGroup();
- initFromAttributes(context, attrs, defStyle);
+ initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
}
private boolean debugDraw() {
@@ -486,6 +499,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mGroupFlags |= FLAG_ANIMATION_DONE;
mGroupFlags |= FLAG_ANIMATION_CACHE;
mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;
+ mGroupFlags |= FLAG_ISOLATED_Z_VOLUME;
if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
@@ -499,8 +513,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
}
- private void initFromAttributes(Context context, AttributeSet attrs, int defStyle) {
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewGroup, defStyle, 0);
+ private void initFromAttributes(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewGroup, defStyleAttr,
+ defStyleRes);
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
@@ -512,6 +528,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
case R.styleable.ViewGroup_clipToPadding:
setClipToPadding(a.getBoolean(attr, true));
break;
+ case R.styleable.ViewGroup_isolatedZVolume:
+ setIsolatedZVolume(a.getBoolean(attr, true));
+ break;
case R.styleable.ViewGroup_animationCache:
setAnimationCacheEnabled(a.getBoolean(attr, true));
break;
@@ -545,6 +564,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
case R.styleable.ViewGroup_layoutMode:
setLayoutMode(a.getInt(attr, LAYOUT_MODE_UNDEFINED));
break;
+ case R.styleable.ViewGroup_transitionGroup:
+ setTransitionGroup(a.getBoolean(attr, false));
+ break;
}
}
@@ -597,7 +619,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@Override
void handleFocusGainInternal(int direction, Rect previouslyFocusedRect) {
if (mFocused != null) {
- mFocused.unFocus();
+ mFocused.unFocus(this);
mFocused = null;
}
super.handleFocusGainInternal(direction, previouslyFocusedRect);
@@ -615,12 +637,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
// Unfocus us, if necessary
- super.unFocus();
+ super.unFocus(focused);
// We had a previous notion of who had focus. Clear it.
if (mFocused != child) {
if (mFocused != null) {
- mFocused.unFocus();
+ mFocused.unFocus(focused);
}
mFocused = child;
@@ -811,14 +833,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* {@inheritDoc}
*/
@Override
- void unFocus() {
+ void unFocus(View focused) {
if (DBG) {
System.out.println(this + " unFocus()");
}
if (mFocused == null) {
- super.unFocus();
+ super.unFocus(focused);
} else {
- mFocused.unFocus();
+ mFocused.unFocus(focused);
mFocused = null;
}
}
@@ -2277,6 +2299,39 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * Returns true if this ViewGroup should be considered as a single entity for removal
+ * when executing an Activity transition. If this is false, child elements will move
+ * individually during the transition.
+ * @return True if the ViewGroup should be acted on together during an Activity transition.
+ * The default value is false when the background is null and true when the background
+ * is not null or if {@link #getSharedElementName()} is not null.
+ */
+ public boolean isTransitionGroup() {
+ if ((mGroupFlags & FLAG_IS_TRANSITION_GROUP_SET) != 0) {
+ return ((mGroupFlags & FLAG_IS_TRANSITION_GROUP) != 0);
+ } else {
+ return getBackground() != null || getSharedElementName() != null;
+ }
+ }
+
+ /**
+ * Changes whether or not this ViewGroup should be treated as a single entity during
+ * ActivityTransitions.
+ * @param isTransitionGroup Whether or not the ViewGroup should be treated as a unit
+ * in Activity transitions. If false, the ViewGroup won't transition,
+ * only its children. If true, the entire ViewGroup will transition
+ * together.
+ */
+ public void setTransitionGroup(boolean isTransitionGroup) {
+ mGroupFlags |= FLAG_IS_TRANSITION_GROUP_SET;
+ if (isTransitionGroup) {
+ mGroupFlags |= FLAG_IS_TRANSITION_GROUP;
+ } else {
+ mGroupFlags &= ~FLAG_IS_TRANSITION_GROUP;
+ }
+ }
+
+ /**
* {@inheritDoc}
*/
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
@@ -2509,13 +2564,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();
}
@@ -2583,6 +2638,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (int i = 0; i < count; i++) {
children[i].dispatchDetachedFromWindow();
}
+ clearDisappearingChildren();
super.dispatchDetachedFromWindow();
}
@@ -3103,7 +3159,44 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Returns whether ths group's children are clipped to their bounds before drawing.
+ * Returns whether this group's descendents are drawn in their own
+ * independent Z volume. Views drawn in one contained volume will not
+ * interleave with views in another, even if their Z values are interleaved.
+ * The default value is true.
+ * @see #setIsolatedZVolume(boolean)
+ *
+ * @return True if the ViewGroup has an isolated Z volume.
+ *
+ * @hide
+ */
+ public boolean hasIsolatedZVolume() {
+ return ((mGroupFlags & FLAG_ISOLATED_Z_VOLUME) != 0);
+ }
+
+ /**
+ * By default, only direct children of a group can interleave drawing order
+ * by interleaving Z values. Set to false on individual groups to enable Z
+ * interleaving of views that aren't direct siblings.
+ *
+ * @return True if the group should be an isolated Z volume with its own Z
+ * ordering space, false if its decendents should inhabit the
+ * inherited Z ordering volume.
+ * @attr ref android.R.styleable#ViewGroup_isolatedZVolume
+ *
+ * @hide
+ */
+ public void setIsolatedZVolume(boolean isolateZVolume) {
+ boolean previousValue = (mGroupFlags & FLAG_ISOLATED_Z_VOLUME) != 0;
+ if (isolateZVolume != previousValue) {
+ setBooleanFlag(FLAG_ISOLATED_Z_VOLUME, isolateZVolume);
+ if (mDisplayList != null) {
+ mDisplayList.setIsolatedZVolume(isolateZVolume);
+ }
+ }
+ }
+
+ /**
+ * Returns whether this group's children are clipped to their bounds before drawing.
* The default value is true.
* @see #setClipChildren(boolean)
*
@@ -3826,7 +3919,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
boolean clearChildFocus = false;
if (view == mFocused) {
- view.unFocus();
+ view.unFocus(null);
clearChildFocus = true;
}
@@ -3921,7 +4014,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
if (view == focused) {
- view.unFocus();
+ view.unFocus(null);
clearChildFocus = true;
}
@@ -4008,7 +4101,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
if (view == focused) {
- view.unFocus();
+ view.unFocus(null);
clearChildFocus = true;
}
@@ -5217,8 +5310,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* this if you don't want animations for exiting views to stack up.
*/
public void clearDisappearingChildren() {
- if (mDisappearingChildren != null) {
- mDisappearingChildren.clear();
+ final ArrayList<View> disappearingChildren = mDisappearingChildren;
+ if (disappearingChildren != null) {
+ final int count = disappearingChildren.size();
+ for (int i = 0; i < count; i++) {
+ final View view = disappearingChildren.get(i);
+ if (view.mAttachInfo != null) {
+ view.dispatchDetachedFromWindow();
+ }
+ view.clearAnimation();
+ }
+ disappearingChildren.clear();
invalidate();
}
}
@@ -5429,21 +5531,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
-
@Override
- protected boolean fitSystemWindows(Rect insets) {
- boolean done = super.fitSystemWindows(insets);
- if (!done) {
- final int count = mChildrenCount;
- final View[] children = mChildren;
+ public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
+ insets = super.dispatchApplyWindowInsets(insets);
+ if (insets.hasInsets()) {
+ final int count = getChildCount();
for (int i = 0; i < count; i++) {
- done = children[i].fitSystemWindows(insets);
- if (done) {
+ insets = getChildAt(i).dispatchApplyWindowInsets(insets);
+ if (!insets.hasInsets()) {
break;
}
}
}
- return done;
+ return insets;
}
/**
@@ -5796,6 +5896,37 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
protected void onSetLayoutParams(View child, LayoutParams layoutParams) {
}
+ /** @hide */
+ @Override
+ public void captureTransitioningViews(List<View> transitioningViews) {
+ if (getVisibility() != View.VISIBLE) {
+ return;
+ }
+ if (isTransitionGroup()) {
+ transitioningViews.add(this);
+ } else {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ child.captureTransitioningViews(transitioningViews);
+ }
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void findSharedElements(Map<String, View> sharedElements) {
+ if (getVisibility() != VISIBLE) {
+ return;
+ }
+ super.findSharedElements(sharedElements);
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ child.findSharedElements(sharedElements);
+ }
+ }
+
/**
* LayoutParams are used by views to tell their parents how they want to be
* laid out. See
diff --git a/core/java/android/view/ViewOverlay.java b/core/java/android/view/ViewOverlay.java
index 975931a..47de780 100644
--- a/core/java/android/view/ViewOverlay.java
+++ b/core/java/android/view/ViewOverlay.java
@@ -155,6 +155,11 @@ public class ViewOverlay {
}
}
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || (mDrawables != null && mDrawables.contains(who));
+ }
+
public void add(View child) {
if (child.getParent() instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) child.getParent();
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 67a94be..1892aa7 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -136,17 +136,18 @@ public class ViewPropertyAnimator {
private static final int NONE = 0x0000;
private static final int TRANSLATION_X = 0x0001;
private static final int TRANSLATION_Y = 0x0002;
- private static final int SCALE_X = 0x0004;
- private static final int SCALE_Y = 0x0008;
- private static final int ROTATION = 0x0010;
- private static final int ROTATION_X = 0x0020;
- private static final int ROTATION_Y = 0x0040;
- private static final int X = 0x0080;
- private static final int Y = 0x0100;
- private static final int ALPHA = 0x0200;
-
- private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | SCALE_X | SCALE_Y |
- ROTATION | ROTATION_X | ROTATION_Y | X | Y;
+ private static final int TRANSLATION_Z = 0x0004;
+ private static final int SCALE_X = 0x0008;
+ private static final int SCALE_Y = 0x0010;
+ private static final int ROTATION = 0x0020;
+ private static final int ROTATION_X = 0x0040;
+ private static final int ROTATION_Y = 0x0080;
+ private static final int X = 0x0100;
+ private static final int Y = 0x0200;
+ private static final int ALPHA = 0x0400;
+
+ private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | TRANSLATION_Z |
+ SCALE_X | SCALE_Y | ROTATION | ROTATION_X | ROTATION_Y | X | Y;
/**
* The mechanism by which the user can request several properties that are then animated
@@ -599,6 +600,31 @@ public class ViewPropertyAnimator {
}
/**
+ * This method will cause the View's <code>translationZ</code> property to be animated to the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The value to be animated to.
+ * @see View#setTranslationZ(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator translationZ(float value) {
+ animateProperty(TRANSLATION_Z, value);
+ return this;
+ }
+
+ /**
+ * This method will cause the View's <code>translationZ</code> property to be animated by the
+ * specified value. Animations already running on the property will be canceled.
+ *
+ * @param value The amount to be animated by, as an offset from the current value.
+ * @see View#setTranslationZ(float)
+ * @return This object, allowing calls to methods in this class to be chained.
+ */
+ public ViewPropertyAnimator translationZBy(float value) {
+ animatePropertyBy(TRANSLATION_Z, value);
+ return this;
+ }
+ /**
* This method will cause the View's <code>scaleX</code> property to be animated to the
* specified value. Animations already running on the property will be canceled.
*
@@ -909,6 +935,10 @@ public class ViewPropertyAnimator {
info.mTranslationY = value;
if (displayList != null) displayList.setTranslationY(value);
break;
+ case TRANSLATION_Z:
+ info.mTranslationZ = value;
+ if (displayList != null) displayList.setTranslationZ(value);
+ break;
case ROTATION:
info.mRotation = value;
if (displayList != null) displayList.setRotation(value);
@@ -957,6 +987,8 @@ public class ViewPropertyAnimator {
return info.mTranslationX;
case TRANSLATION_Y:
return info.mTranslationY;
+ case TRANSLATION_Z:
+ return info.mTranslationZ;
case ROTATION:
return info.mRotation;
case ROTATION_X:
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index d779628..18517c5 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -28,6 +28,7 @@ import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Point;
@@ -55,6 +56,7 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
import android.util.TypedValue;
+import android.view.Surface.OutOfResourcesException;
import android.view.View.AttachInfo;
import android.view.View.MeasureSpec;
import android.view.accessibility.AccessibilityEvent;
@@ -68,7 +70,6 @@ import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
-import android.view.Surface.OutOfResourcesException;
import android.widget.Scroller;
import com.android.internal.R;
@@ -108,7 +109,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
@@ -267,6 +267,10 @@ public final class ViewRootImpl implements ViewParent,
HardwareLayer mResizeBuffer;
long mResizeBufferStartTime;
int mResizeBufferDuration;
+ // Used to block the creation of the ResizeBuffer due to invalidations in
+ // the previous DisplayList tree that must prevent re-execution.
+ // Currently this means a functor was detached.
+ boolean mBlockResizeBuffer;
static final Interpolator mResizeInterpolator = new AccelerateDecelerateInterpolator();
private ArrayList<LayoutTransition> mPendingTransitions;
@@ -290,8 +294,6 @@ public final class ViewRootImpl implements ViewParent,
private long mFpsPrevTime = -1;
private int mFpsNumFrames;
- private final ArrayList<DisplayList> mDisplayLists = new ArrayList<DisplayList>();
-
/**
* see {@link #playSoundEffect(int)}
*/
@@ -616,7 +618,6 @@ public final class ViewRootImpl implements ViewParent,
}
void destroyHardwareResources() {
- invalidateDisplayLists();
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.destroyHardwareResources(mView);
mAttachInfo.mHardwareRenderer.destroy(false);
@@ -630,23 +631,25 @@ public final class ViewRootImpl implements ViewParent,
HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_MODERATE);
}
} else {
- invalidateDisplayLists();
- if (mAttachInfo.mHardwareRenderer != null &&
- mAttachInfo.mHardwareRenderer.isEnabled()) {
- mAttachInfo.mHardwareRenderer.destroyLayers(mView);
- }
+ destroyHardwareLayer(mView);
}
}
- void pushHardwareLayerUpdate(HardwareLayer layer) {
- if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
- mAttachInfo.mHardwareRenderer.pushLayerUpdate(layer);
+ 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));
+ }
}
}
void flushHardwareLayerUpdates() {
- if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled() &&
- mAttachInfo.mHardwareRenderer.validate()) {
+ if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
mAttachInfo.mHardwareRenderer.flushLayerUpdates();
}
}
@@ -656,15 +659,15 @@ public final class ViewRootImpl implements ViewParent,
mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(MSG_FLUSH_LAYER_UPDATES));
}
- public boolean attachFunctor(int functor) {
+ public void attachFunctor(int functor) {
//noinspection SimplifiableIfStatement
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
- return mAttachInfo.mHardwareRenderer.attachFunctor(mAttachInfo, functor);
+ mAttachInfo.mHardwareRenderer.attachFunctor(mAttachInfo, functor);
}
- return false;
}
public void detachFunctor(int functor) {
+ mBlockResizeBuffer = true;
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.detachFunctor(functor);
}
@@ -715,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 =
@@ -930,14 +933,9 @@ public final class ViewRootImpl implements ViewParent,
}
void disposeResizeBuffer() {
- if (mResizeBuffer != null && mAttachInfo.mHardwareRenderer != null) {
- mAttachInfo.mHardwareRenderer.safelyRun(new Runnable() {
- @Override
- public void run() {
- mResizeBuffer.destroy();
- mResizeBuffer = null;
- }
- });
+ if (mResizeBuffer != null) {
+ mResizeBuffer.destroy();
+ mResizeBuffer = null;
}
}
@@ -1121,6 +1119,28 @@ public final class ViewRootImpl implements ViewParent,
return windowSizeMayChange;
}
+ /**
+ * Modifies the input matrix such that it maps view-local coordinates to
+ * on-screen coordinates.
+ *
+ * @param m input matrix to modify
+ */
+ void transformMatrixToGlobal(Matrix m) {
+ final View.AttachInfo attachInfo = mAttachInfo;
+ m.postTranslate(attachInfo.mWindowLeft, attachInfo.mWindowTop);
+ }
+
+ /**
+ * Modifies the input matrix such that it maps on-screen coordinates to
+ * view-local coordinates.
+ *
+ * @param m input matrix to modify
+ */
+ void transformMatrixToLocal(Matrix m) {
+ final View.AttachInfo attachInfo = mAttachInfo;
+ m.preTranslate(-attachInfo.mWindowLeft, -attachInfo.mWindowTop);
+ }
+
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
@@ -1191,11 +1211,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
@@ -1440,67 +1455,56 @@ public final class ViewRootImpl implements ViewParent,
!mAttachInfo.mTurnOffWindowResizeAnim &&
mAttachInfo.mHardwareRenderer != null &&
mAttachInfo.mHardwareRenderer.isEnabled() &&
- mAttachInfo.mHardwareRenderer.validate() &&
- lp != null && !PixelFormat.formatHasAlpha(lp.format)) {
+ lp != null && !PixelFormat.formatHasAlpha(lp.format)
+ && !mBlockResizeBuffer) {
disposeResizeBuffer();
- boolean completed = false;
- HardwareCanvas hwRendererCanvas = mAttachInfo.mHardwareRenderer.getCanvas();
- HardwareCanvas layerCanvas = null;
- try {
- if (mResizeBuffer == null) {
- mResizeBuffer = mAttachInfo.mHardwareRenderer.createHardwareLayer(
- mWidth, mHeight, false);
- } else if (mResizeBuffer.getWidth() != mWidth ||
- mResizeBuffer.getHeight() != mHeight) {
- mResizeBuffer.resize(mWidth, mHeight);
- }
- // TODO: should handle create/resize failure
- layerCanvas = mResizeBuffer.start(hwRendererCanvas);
- final int restoreCount = layerCanvas.save();
-
- int yoff;
- final boolean scrolling = mScroller != null
- && mScroller.computeScrollOffset();
- if (scrolling) {
- yoff = mScroller.getCurrY();
- mScroller.abortAnimation();
- } else {
- yoff = mScrollY;
- }
-
- layerCanvas.translate(0, -yoff);
- if (mTranslator != null) {
- mTranslator.translateCanvas(layerCanvas);
- }
+ if (mResizeBuffer == null) {
+ mResizeBuffer = mAttachInfo.mHardwareRenderer.createDisplayListLayer(
+ mWidth, mHeight);
+ }
+ mResizeBuffer.prepare(mWidth, mHeight, false);
+ DisplayList layerDisplayList = mResizeBuffer.startRecording();
+ HardwareCanvas layerCanvas = layerDisplayList.start(mWidth, mHeight);
+ final int restoreCount = layerCanvas.save();
+
+ int yoff;
+ final boolean scrolling = mScroller != null
+ && mScroller.computeScrollOffset();
+ if (scrolling) {
+ yoff = mScroller.getCurrY();
+ mScroller.abortAnimation();
+ } else {
+ yoff = mScrollY;
+ }
- DisplayList displayList = mView.mDisplayList;
- if (displayList != null && displayList.isValid()) {
- layerCanvas.drawDisplayList(displayList, null,
- DisplayList.FLAG_CLIP_CHILDREN);
- } else {
- mView.draw(layerCanvas);
- }
+ layerCanvas.translate(0, -yoff);
+ if (mTranslator != null) {
+ mTranslator.translateCanvas(layerCanvas);
+ }
- drawAccessibilityFocusedDrawableIfNeeded(layerCanvas);
-
- mResizeBufferStartTime = SystemClock.uptimeMillis();
- mResizeBufferDuration = mView.getResources().getInteger(
- com.android.internal.R.integer.config_mediumAnimTime);
- completed = true;
-
- layerCanvas.restoreToCount(restoreCount);
- } catch (OutOfMemoryError e) {
- Log.w(TAG, "Not enough memory for content change anim buffer", e);
- } finally {
- if (mResizeBuffer != null) {
- mResizeBuffer.end(hwRendererCanvas);
- if (!completed) {
- disposeResizeBuffer();
- }
- }
+ DisplayList displayList = mView.mDisplayList;
+ if (displayList != null && displayList.isValid()) {
+ layerCanvas.drawDisplayList(displayList, null,
+ DisplayList.FLAG_CLIP_CHILDREN);
+ } else {
+ mView.draw(layerCanvas);
}
+
+ drawAccessibilityFocusedDrawableIfNeeded(layerCanvas);
+
+ mResizeBufferStartTime = SystemClock.uptimeMillis();
+ mResizeBufferDuration = mView.getResources().getInteger(
+ com.android.internal.R.integer.config_mediumAnimTime);
+
+ layerCanvas.restoreToCount(restoreCount);
+ layerDisplayList.end(mAttachInfo.mHardwareRenderer, layerCanvas);
+ layerDisplayList.setCaching(true);
+ layerDisplayList.setLeftTopRightBottom(0, 0, mWidth, mHeight);
+ mTempRect.set(0, 0, mWidth, mHeight);
+ mResizeBuffer.endRecording(mTempRect);
+ mAttachInfo.mHardwareRenderer.flushLayerUpdates();
}
mAttachInfo.mContentInsets.set(mPendingContentInsets);
if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
@@ -1544,7 +1548,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;
@@ -1571,7 +1575,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;
@@ -1654,7 +1658,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;
}
}
@@ -2164,18 +2168,19 @@ public final class ViewRootImpl implements ViewParent,
mResizePaint.setAlpha(mResizeAlpha);
canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint);
}
- drawAccessibilityFocusedDrawableIfNeeded(canvas);
+ // TODO: this
+ if (!HardwareRenderer.sUseRenderThread) {
+ drawAccessibilityFocusedDrawableIfNeeded(canvas);
+ }
}
/**
* @hide
*/
void outputDisplayList(View view) {
- if (mAttachInfo != null && mAttachInfo.mHardwareCanvas != null) {
- DisplayList displayList = view.getDisplayList();
- if (displayList != null) {
- mAttachInfo.mHardwareCanvas.outputDisplayList(displayList);
- }
+ DisplayList displayList = view.getDisplayList();
+ if (displayList != null) {
+ displayList.output();
}
}
@@ -2360,8 +2365,6 @@ public final class ViewRootImpl implements ViewParent,
appScale + ", width=" + mWidth + ", height=" + mHeight);
}
- invalidateDisplayLists();
-
attachInfo.mTreeObserver.dispatchOnDraw();
if (!dirty.isEmpty() || mIsAnimating) {
@@ -2374,6 +2377,7 @@ public final class ViewRootImpl implements ViewParent,
mCurrentDirty.set(dirty);
dirty.setEmpty();
+ mBlockResizeBuffer = false;
attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
animating ? null : mCurrentDirty);
} else {
@@ -2391,7 +2395,7 @@ public final class ViewRootImpl implements ViewParent,
try {
attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
- mHolder.getSurface());
+ mSurface);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
@@ -2526,28 +2530,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);
@@ -2563,7 +2574,7 @@ public final class ViewRootImpl implements ViewParent,
R.attr.accessibilityFocusedDrawable, value, true);
if (resolved) {
mAttachInfo.mAccessibilityFocusDrawable =
- mView.mContext.getResources().getDrawable(value.resourceId);
+ mView.mContext.getDrawable(value.resourceId);
}
}
return mAttachInfo.mAccessibilityFocusDrawable;
@@ -2571,20 +2582,6 @@ public final class ViewRootImpl implements ViewParent,
return null;
}
- void invalidateDisplayLists() {
- final ArrayList<DisplayList> displayLists = mDisplayLists;
- final int count = displayLists.size();
-
- for (int i = 0; i < count; i++) {
- final DisplayList displayList = displayLists.get(i);
- if (displayList.isDirty()) {
- displayList.reset();
- }
- }
-
- displayLists.clear();
- }
-
/**
* @hide
*/
@@ -2826,10 +2823,6 @@ public final class ViewRootImpl implements ViewParent,
void dispatchDetachedFromWindow() {
if (mView != null && mView.mAttachInfo != null) {
- if (mAttachInfo.mHardwareRenderer != null &&
- mAttachInfo.mHardwareRenderer.isEnabled()) {
- mAttachInfo.mHardwareRenderer.validate();
- }
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
mView.dispatchDetachedFromWindow();
}
@@ -2846,7 +2839,6 @@ public final class ViewRootImpl implements ViewParent,
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
- mAttachInfo.mSurface = null;
mSurface.release();
@@ -3103,7 +3095,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 {
@@ -3152,8 +3144,6 @@ public final class ViewRootImpl implements ViewParent,
mHasHadWindowFocus = true;
}
- setAccessibilityFocus(null, null);
-
if (mView != null && mAccessibilityManager.isEnabled()) {
if (hasWindowFocus) {
mView.sendAccessibilityEvent(
@@ -3301,7 +3291,7 @@ public final class ViewRootImpl implements ViewParent,
} else {
// There's nothing to focus. Clear and propagate through the
// hierarchy, but don't attempt to place new focus.
- focused.clearFocusInternal(true, false);
+ focused.clearFocusInternal(null, true, false);
return true;
}
}
@@ -4493,8 +4483,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;
@@ -4522,9 +4511,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);
}
@@ -4591,7 +4577,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;
@@ -5224,7 +5209,7 @@ public final class ViewRootImpl implements ViewParent,
DisplayList displayList = view.mDisplayList;
info[0]++;
if (displayList != null) {
- info[1] += displayList.getSize();
+ info[1] += 0; /* TODO: Memory used by display lists */
}
if (view instanceof ViewGroup) {
@@ -5272,7 +5257,6 @@ public final class ViewRootImpl implements ViewParent,
}
if (mAdded && !mFirst) {
- invalidateDisplayLists();
destroyHardwareRenderer();
if (mView != null) {
@@ -5318,7 +5302,7 @@ public final class ViewRootImpl implements ViewParent,
// Hardware rendering
if (mAttachInfo.mHardwareRenderer != null) {
- if (mAttachInfo.mHardwareRenderer.loadSystemProperties(mHolder.getSurface())) {
+ if (mAttachInfo.mHardwareRenderer.loadSystemProperties()) {
invalidate();
}
}
@@ -5521,24 +5505,23 @@ public final class ViewRootImpl implements ViewParent,
}
private void deliverInputEvent(QueuedInputEvent q) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent");
- try {
- if (mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
- }
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
+ q.mEvent.getSequenceNumber());
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
+ }
- InputStage stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
- if (stage != null) {
- stage.deliver(q);
- } else {
- finishInputEvent(q);
- }
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ InputStage stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
+ if (stage != null) {
+ stage.deliver(q);
+ } else {
+ finishInputEvent(q);
}
}
private void finishInputEvent(QueuedInputEvent q) {
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
+ q.mEvent.getSequenceNumber());
if (q.mReceiver != null) {
boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;
q.mReceiver.finishInputEvent(q.mEvent, handled);
@@ -5739,10 +5722,6 @@ public final class ViewRootImpl implements ViewParent,
mInvalidateOnAnimationRunnable.addViewRect(info);
}
- public void enqueueDisplayList(DisplayList displayList) {
- mDisplayLists.add(displayList);
- }
-
public void cancelInvalidate(View view) {
mHandler.removeMessages(MSG_INVALIDATE, view);
// fixme: might leak the AttachInfo.InvalidateInfo objects instead of returning
@@ -5765,6 +5744,9 @@ public final class ViewRootImpl implements ViewParent,
public void dispatchUnhandledKey(KeyEvent event) {
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
+ // Some fallback keys are decided by the ViewRoot as they might have special
+ // properties (e.g. are locale aware). These take precedence over fallbacks defined by
+ // the kcm.
final KeyCharacterMap kcm = event.getKeyCharacterMap();
final int keyCode = event.getKeyCode();
final int metaState = event.getMetaState();
@@ -5781,7 +5763,6 @@ public final class ViewRootImpl implements ViewParent,
event.getDeviceId(), event.getScanCode(),
flags, event.getSource(), null);
fallbackAction.recycle();
-
dispatchInputEvent(fallbackEvent);
}
}
@@ -6265,68 +6246,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 c450f3c..0cd6325 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -16,6 +16,9 @@
package android.view;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -25,8 +28,13 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemProperties;
+import android.transition.Scene;
+import android.transition.Transition;
+import android.transition.TransitionManager;
import android.view.accessibility.AccessibilityEvent;
+import java.util.Map;
+
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
@@ -89,17 +97,25 @@ 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 a decoration-free window that is dismissed by swiping from the left.
*/
public static final int FEATURE_SWIPE_TO_DISMISS = 11;
+ /**
+ * 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 = 12;
/**
* Max value used as a feature ID
* @hide
*/
- public static final int FEATURE_MAX = FEATURE_SWIPE_TO_DISMISS;
+ 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;
@@ -245,6 +261,7 @@ public abstract class Window {
*
* @see #onPreparePanel
*/
+ @Nullable
public View onCreatePanelView(int featureId);
/**
@@ -373,6 +390,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);
/**
@@ -980,6 +998,7 @@ public abstract class Window {
*
* @return View The current View with focus or null.
*/
+ @Nullable
public abstract View getCurrentFocus();
/**
@@ -988,10 +1007,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);
@@ -1032,7 +1053,7 @@ public abstract class Window {
*/
public void setBackgroundDrawableResource(int resid)
{
- setBackgroundDrawable(mContext.getResources().getDrawable(resid));
+ setBackgroundDrawable(mContext.getDrawable(resid));
}
/**
@@ -1328,4 +1349,93 @@ public abstract class Window {
* @param event A key or touch event to inject to this window.
*/
public void injectInputEvent(InputEvent event) { }
+
+ /**
+ * Retrieve the {@link TransitionManager} responsible for for default transitions
+ * in this window. Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * <p>This method will return non-null after content has been initialized (e.g. by using
+ * {@link #setContentView}) if {@link #FEATURE_CONTENT_TRANSITIONS} has been granted.</p>
+ *
+ * @return This window's content TransitionManager or null if none is set.
+ */
+ public TransitionManager getTransitionManager() {
+ return null;
+ }
+
+ /**
+ * Set the {@link TransitionManager} to use for default transitions in this window.
+ * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * @param tm The TransitionManager to use for scene changes.
+ */
+ public void setTransitionManager(TransitionManager tm) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Retrieve the {@link Scene} representing this window's current content.
+ * Requires {@link #FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * <p>This method will return null if the current content is not represented by a Scene.</p>
+ *
+ * @return Current Scene being shown or null
+ */
+ public Scene getContentScene() {
+ return null;
+ }
+
+ /**
+ * Set options that can affect the transition behavior within this window.
+ * @param options Options to set or null for none
+ * @hide
+ */
+ public void setTransitionOptions(ActivityOptions options, SceneTransitionListener listener) {
+ }
+
+ /**
+ * A callback for Activity transitions to be told when the shared element is ready to be shown
+ * and start the transition to its target location.
+ * @hide
+ */
+ public interface SceneTransitionListener {
+ void nullPendingTransition();
+ void convertFromTranslucent();
+ void convertToTranslucent();
+ void sharedElementStart(Transition transition);
+ void sharedElementEnd();
+ }
+
+ /**
+ * Controls when the Activity enter scene is triggered and the background is faded in. If
+ * triggerEarly is true, the enter scene will begin as soon as possible and the background
+ * will fade in when all shared elements are ready to begin transitioning. If triggerEarly is
+ * false, the Activity enter scene and background fade will be triggered when the calling
+ * Activity's exit transition completes.
+ *
+ * @param triggerEarly Set to true to have the Activity enter scene transition in as early as
+ * possible or set to false to wait for the calling Activity to exit first.
+ */
+ public void setTriggerEarlyEnterTransition(boolean triggerEarly) {
+ }
+
+ /**
+ * Start the exit transition.
+ * @hide
+ */
+ public Bundle startExitTransition(ActivityOptions options) {
+ return null;
+ }
+
+ /**
+ * On entering Activity Scene transitions, shared element names may be mapped from a
+ * source Activity's specified name to a unique shared element name in the View hierarchy.
+ * Under most circumstances, mapping is not necessary - a single View will have the
+ * shared element name given by the calling Activity. However, if there are several similar
+ * Views (e.g. in a ListView), the correct shared element must be mapped.
+ * @param sharedElementNames A mapping from the calling Activity's assigned shared element
+ * name to a unique shared element name in the View hierarchy.
+ */
+ public void mapTransitionTargets(Map<String, String> sharedElementNames) {
+ }
}
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
new file mode 100644
index 0000000..cdfcb43
--- /dev/null
+++ b/core/java/android/view/WindowInsets.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.view;
+
+import android.graphics.Rect;
+
+/**
+ * Describes a set of insets for window content.
+ *
+ * <p>WindowInsets are immutable and may be expanded to include more inset types in the future.
+ * To adjust insets, use one of the supplied clone methods to obtain a new WindowInsets instance
+ * with the adjusted properties.</p>
+ *
+ * @see View.OnApplyWindowInsetsListener
+ * @see View#onApplyWindowInsets(WindowInsets)
+ */
+public class WindowInsets {
+ private Rect mSystemWindowInsets;
+ private Rect mWindowDecorInsets;
+ private Rect mTempRect;
+
+ private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0);
+
+ /**
+ * Since new insets may be added in the future that existing apps couldn't
+ * know about, this fully empty constant shouldn't be made available to apps
+ * since it would allow them to inadvertently consume unknown insets by returning it.
+ * @hide
+ */
+ public static final WindowInsets EMPTY = new WindowInsets(EMPTY_RECT, EMPTY_RECT);
+
+ /** @hide */
+ public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets) {
+ mSystemWindowInsets = systemWindowInsets;
+ mWindowDecorInsets = windowDecorInsets;
+ }
+
+ /**
+ * Construct a new WindowInsets, copying all values from a source WindowInsets.
+ *
+ * @param src Source to copy insets from
+ */
+ public WindowInsets(WindowInsets src) {
+ mSystemWindowInsets = src.mSystemWindowInsets;
+ mWindowDecorInsets = src.mWindowDecorInsets;
+ }
+
+ /** @hide */
+ public WindowInsets(Rect systemWindowInsets) {
+ mSystemWindowInsets = systemWindowInsets;
+ mWindowDecorInsets = EMPTY_RECT;
+ }
+
+ /**
+ * Used to provide a safe copy of the system window insets to pass through
+ * to the existing fitSystemWindows method and other similar internals.
+ * @hide
+ */
+ public Rect getSystemWindowInsets() {
+ if (mTempRect == null) {
+ mTempRect = new Rect();
+ }
+ mTempRect.set(mSystemWindowInsets);
+ return mTempRect;
+ }
+
+ /**
+ * Returns the left system window inset in pixels.
+ *
+ * <p>The system window inset represents the area of a full-screen window that is
+ * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+ * </p>
+ *
+ * @return The left system window inset
+ */
+ public int getSystemWindowInsetLeft() {
+ return mSystemWindowInsets.left;
+ }
+
+ /**
+ * Returns the top system window inset in pixels.
+ *
+ * <p>The system window inset represents the area of a full-screen window that is
+ * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+ * </p>
+ *
+ * @return The top system window inset
+ */
+ public int getSystemWindowInsetTop() {
+ return mSystemWindowInsets.top;
+ }
+
+ /**
+ * Returns the right system window inset in pixels.
+ *
+ * <p>The system window inset represents the area of a full-screen window that is
+ * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+ * </p>
+ *
+ * @return The right system window inset
+ */
+ public int getSystemWindowInsetRight() {
+ return mSystemWindowInsets.right;
+ }
+
+ /**
+ * Returns the bottom system window inset in pixels.
+ *
+ * <p>The system window inset represents the area of a full-screen window that is
+ * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+ * </p>
+ *
+ * @return The bottom system window inset
+ */
+ public int getSystemWindowInsetBottom() {
+ return mSystemWindowInsets.bottom;
+ }
+
+ /**
+ * Returns the left window decor inset in pixels.
+ *
+ * <p>The window decor inset represents the area of the window content area that is
+ * partially or fully obscured by decorations within the window provided by the framework.
+ * This can include action bars, title bars, toolbars, etc.</p>
+ *
+ * @return The left window decor inset
+ */
+ public int getWindowDecorInsetLeft() {
+ return mWindowDecorInsets.left;
+ }
+
+ /**
+ * Returns the top window decor inset in pixels.
+ *
+ * <p>The window decor inset represents the area of the window content area that is
+ * partially or fully obscured by decorations within the window provided by the framework.
+ * This can include action bars, title bars, toolbars, etc.</p>
+ *
+ * @return The top window decor inset
+ */
+ public int getWindowDecorInsetTop() {
+ return mWindowDecorInsets.top;
+ }
+
+ /**
+ * Returns the right window decor inset in pixels.
+ *
+ * <p>The window decor inset represents the area of the window content area that is
+ * partially or fully obscured by decorations within the window provided by the framework.
+ * This can include action bars, title bars, toolbars, etc.</p>
+ *
+ * @return The right window decor inset
+ */
+ public int getWindowDecorInsetRight() {
+ return mWindowDecorInsets.right;
+ }
+
+ /**
+ * Returns the bottom window decor inset in pixels.
+ *
+ * <p>The window decor inset represents the area of the window content area that is
+ * partially or fully obscured by decorations within the window provided by the framework.
+ * This can include action bars, title bars, toolbars, etc.</p>
+ *
+ * @return The bottom window decor inset
+ */
+ public int getWindowDecorInsetBottom() {
+ return mWindowDecorInsets.bottom;
+ }
+
+ /**
+ * Returns true if this WindowInsets has nonzero system window insets.
+ *
+ * <p>The system window inset represents the area of a full-screen window that is
+ * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
+ * </p>
+ *
+ * @return true if any of the system window inset values are nonzero
+ */
+ public boolean hasSystemWindowInsets() {
+ return mSystemWindowInsets.left != 0 || mSystemWindowInsets.top != 0 ||
+ mSystemWindowInsets.right != 0 || mSystemWindowInsets.bottom != 0;
+ }
+
+ /**
+ * Returns true if this WindowInsets has nonzero window decor insets.
+ *
+ * <p>The window decor inset represents the area of the window content area that is
+ * partially or fully obscured by decorations within the window provided by the framework.
+ * This can include action bars, title bars, toolbars, etc.</p>
+ *
+ * @return true if any of the window decor inset values are nonzero
+ */
+ public boolean hasWindowDecorInsets() {
+ return mWindowDecorInsets.left != 0 || mWindowDecorInsets.top != 0 ||
+ mWindowDecorInsets.right != 0 || mWindowDecorInsets.bottom != 0;
+ }
+
+ /**
+ * Returns true if this WindowInsets has any nonzero insets.
+ *
+ * @return true if any inset values are nonzero
+ */
+ public boolean hasInsets() {
+ return hasSystemWindowInsets() || hasWindowDecorInsets();
+ }
+
+ public WindowInsets cloneWithSystemWindowInsetsConsumed() {
+ final WindowInsets result = new WindowInsets(this);
+ result.mSystemWindowInsets = new Rect(0, 0, 0, 0);
+ return result;
+ }
+
+ public WindowInsets cloneWithSystemWindowInsetsConsumed(boolean left, boolean top,
+ boolean right, boolean bottom) {
+ if (left || top || right || bottom) {
+ final WindowInsets result = new WindowInsets(this);
+ result.mSystemWindowInsets = new Rect(left ? 0 : mSystemWindowInsets.left,
+ top ? 0 : mSystemWindowInsets.top,
+ right ? 0 : mSystemWindowInsets.right,
+ bottom ? 0 : mSystemWindowInsets.bottom);
+ return result;
+ }
+ return this;
+ }
+
+ public WindowInsets cloneWithSystemWindowInsets(int left, int top, int right, int bottom) {
+ final WindowInsets result = new WindowInsets(this);
+ result.mSystemWindowInsets = new Rect(left, top, right, bottom);
+ return result;
+ }
+
+ public WindowInsets cloneWithWindowDecorInsetsConsumed() {
+ final WindowInsets result = new WindowInsets(this);
+ result.mWindowDecorInsets.set(0, 0, 0, 0);
+ return result;
+ }
+
+ public WindowInsets cloneWithWindowDecorInsetsConsumed(boolean left, boolean top,
+ boolean right, boolean bottom) {
+ if (left || top || right || bottom) {
+ final WindowInsets result = new WindowInsets(this);
+ result.mWindowDecorInsets = new Rect(left ? 0 : mWindowDecorInsets.left,
+ top ? 0 : mWindowDecorInsets.top,
+ right ? 0 : mWindowDecorInsets.right,
+ bottom ? 0 : mWindowDecorInsets.bottom);
+ return result;
+ }
+ return this;
+ }
+
+ public WindowInsets cloneWithWindowDecorInsets(int left, int top, int right, int bottom) {
+ final WindowInsets result = new WindowInsets(this);
+ result.mWindowDecorInsets = new Rect(left, top, right, bottom);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets + " windowDecorInsets=" +
+ mWindowDecorInsets + "}";
+ }
+}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 53a4c0d0..55956bf 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -98,7 +98,7 @@ public interface WindowManager extends ViewManager {
* the given view hierarchy's {@link View#onDetachedFromWindow()
* View.onDetachedFromWindow()} methods before returning. This is not
* for normal applications; using it correctly requires great care.
- *
+ *
* @param view The view to be removed.
*/
public void removeViewImmediate(View view);
@@ -112,7 +112,7 @@ public interface WindowManager extends ViewManager {
*/
@ViewDebug.ExportedProperty
public int x;
-
+
/**
* Y position for this window. With the default gravity it is ignored.
* When using {@link Gravity#TOP} or {@link Gravity#BOTTOM} it provides
@@ -161,7 +161,7 @@ public interface WindowManager extends ViewManager {
* be used by applications, and a special permission is required
* to use them.
* </ul>
- *
+ *
* @see #TYPE_BASE_APPLICATION
* @see #TYPE_APPLICATION
* @see #TYPE_APPLICATION_STARTING
@@ -223,12 +223,12 @@ public interface WindowManager extends ViewManager {
@ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION, to = "TYPE_PRIVATE_PRESENTATION")
})
public int type;
-
+
/**
* Start of window types that represent normal application windows.
*/
public static final int FIRST_APPLICATION_WINDOW = 1;
-
+
/**
* Window type: an application window that serves as the "base" window
* of the overall application; all other application windows will
@@ -236,14 +236,14 @@ public interface WindowManager extends ViewManager {
* In multiuser systems shows only on the owning user's window.
*/
public static final int TYPE_BASE_APPLICATION = 1;
-
+
/**
* Window type: a normal application window. The {@link #token} must be
* an Activity token identifying who the window belongs to.
* In multiuser systems shows only on the owning user's window.
*/
public static final int TYPE_APPLICATION = 2;
-
+
/**
* Window type: special application window that is displayed while the
* application is starting. Not for use by applications themselves;
@@ -252,12 +252,12 @@ public interface WindowManager extends ViewManager {
* In multiuser systems shows on all users' windows.
*/
public static final int TYPE_APPLICATION_STARTING = 3;
-
+
/**
* End of types of application windows.
*/
public static final int LAST_APPLICATION_WINDOW = 99;
-
+
/**
* Start of types of sub-windows. The {@link #token} of these windows
* must be set to the window they are attached to. These types of
@@ -265,19 +265,19 @@ public interface WindowManager extends ViewManager {
* coordinate space is relative to their attached window.
*/
public static final int FIRST_SUB_WINDOW = 1000;
-
+
/**
* Window type: a panel on top of an application window. These windows
* appear on top of their attached window.
*/
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
-
+
/**
* Window type: window for showing media (such as video). These windows
* are displayed behind their attached window.
*/
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1;
-
+
/**
* Window type: a sub-panel on top of an application window. These
* windows are displayed on top their attached window and any
@@ -290,7 +290,7 @@ public interface WindowManager extends ViewManager {
* as a child of its container.
*/
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
-
+
/**
* Window type: window for showing overlays on top of media windows.
* These windows are displayed between TYPE_APPLICATION_MEDIA and the
@@ -299,18 +299,18 @@ public interface WindowManager extends ViewManager {
* @hide
*/
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4;
-
+
/**
* End of types of sub-windows.
*/
public static final int LAST_SUB_WINDOW = 1999;
-
+
/**
* Start of system-specific window types. These are not normally
* created by applications.
*/
public static final int FIRST_SYSTEM_WINDOW = 2000;
-
+
/**
* Window type: the status bar. There can be only one status bar
* window; it is placed at the top of the screen, and all other
@@ -318,14 +318,14 @@ public interface WindowManager extends ViewManager {
* In multiuser systems shows on all users' windows.
*/
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
-
+
/**
* Window type: the search bar. There can be only one search bar
* window; it is placed at the top of the screen.
* In multiuser systems shows on all users' windows.
*/
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
-
+
/**
* Window type: phone. These are non-application windows providing
* user interaction with the phone (in particular incoming calls).
@@ -334,26 +334,26 @@ public interface WindowManager extends ViewManager {
* In multiuser systems shows on all users' windows.
*/
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
-
+
/**
* Window type: system window, such as low power alert. These windows
* are always on top of application windows.
* In multiuser systems shows only on the owning user's window.
*/
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
-
+
/**
* Window type: keyguard window.
* In multiuser systems shows on all users' windows.
*/
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
-
+
/**
* Window type: transient notifications.
* In multiuser systems shows only on the owning user's window.
*/
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
-
+
/**
* Window type: system overlay windows, which need to be displayed
* on top of everything else. These windows must not take input
@@ -361,7 +361,7 @@ public interface WindowManager extends ViewManager {
* In multiuser systems shows only on the owning user's window.
*/
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;
-
+
/**
* Window type: priority phone UI, which needs to be displayed even if
* the keyguard is active. These windows must not take input
@@ -369,26 +369,26 @@ public interface WindowManager extends ViewManager {
* In multiuser systems shows on all users' windows.
*/
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;
-
+
/**
* Window type: panel that slides out from the status bar
* In multiuser systems shows on all users' windows.
*/
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;
-
+
/**
* Window type: dialogs that the keyguard shows
* In multiuser systems shows on all users' windows.
*/
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;
-
+
/**
* Window type: internal system error windows, appear on top of
* everything they can.
* In multiuser systems shows only on the owning user's window.
*/
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;
-
+
/**
* Window type: internal input methods windows, which appear above
* the normal UI. Application windows may be resized or panned to keep
@@ -559,16 +559,16 @@ public interface WindowManager extends ViewManager {
/** @deprecated this is ignored, this value is set automatically when needed. */
@Deprecated
public static final int MEMORY_TYPE_PUSH_BUFFERS = 3;
-
+
/**
* @deprecated this is ignored
*/
@Deprecated
public int memoryType;
-
+
/** Window flag: as long as this window is visible to the user, allow
- * the lock screen to activate while the screen is on.
- * This can be used independently, or in combination with
+ * the lock screen to activate while the screen is on.
+ * This can be used independently, or in combination with
* {@link #FLAG_KEEP_SCREEN_ON} and/or {@link #FLAG_SHOW_WHEN_LOCKED} */
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
@@ -586,47 +586,47 @@ public interface WindowManager extends ViewManager {
* instead go to whatever focusable window is behind it. This flag
* will also enable {@link #FLAG_NOT_TOUCH_MODAL} whether or not that
* is explicitly set.
- *
+ *
* <p>Setting this flag also implies that the window will not need to
* interact with
- * a soft input method, so it will be Z-ordered and positioned
+ * a soft input method, so it will be Z-ordered and positioned
* independently of any active input method (typically this means it
* gets Z-ordered on top of the input method, so it can use the full
* screen for its content and cover the input method if needed. You
* can use {@link #FLAG_ALT_FOCUSABLE_IM} to modify this behavior. */
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
-
+
/** Window flag: this window can never receive touch events. */
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
-
+
/** Window flag: even when this window is focusable (its
* {@link #FLAG_NOT_FOCUSABLE} is not set), allow any pointer events
* outside of the window to be sent to the windows behind it. Otherwise
* it will consume all pointer events itself, regardless of whether they
* are inside of the window. */
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
-
+
/** Window flag: when set, if the device is asleep when the touch
* screen is pressed, you will receive this first touch event. Usually
* the first touch event is consumed by the system since the user can
* not see what they are pressing on.
*/
public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
-
+
/** Window flag: as long as this window is visible to the user, keep
* the device's screen turned on and bright. */
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
-
+
/** Window flag: place the window within the entire screen, ignoring
* decorations around the border (such as the status bar). The
* window must correctly position its contents to take the screen
* decoration into account. This flag is normally set for you
* by Window as described in {@link Window#setFlags}. */
public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100;
-
+
/** Window flag: allow window to extend outside of the screen. */
public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;
-
+
/**
* Window flag: hide all screen decorations (such as the status bar) while
* this window is displayed. This allows the window to use the entire
@@ -648,17 +648,17 @@ public interface WindowManager extends ViewManager {
* {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_Fullscreen}.</p>
*/
public static final int FLAG_FULLSCREEN = 0x00000400;
-
+
/** Window flag: override {@link #FLAG_FULLSCREEN} and force the
* screen decorations (such as the status bar) to be shown. */
public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
-
+
/** Window flag: turn on dithering when compositing this window to
* the screen.
* @deprecated This flag is no longer used. */
@Deprecated
public static final int FLAG_DITHER = 0x00001000;
-
+
/** Window flag: treat the content of the window as secure, preventing
* it from appearing in screenshots or from being viewed on non-secure
* displays.
@@ -667,21 +667,21 @@ public interface WindowManager extends ViewManager {
* secure surfaces and secure displays.
*/
public static final int FLAG_SECURE = 0x00002000;
-
+
/** Window flag: a special mode where the layout parameters are used
* to perform scaling of the surface when it is composited to the
* screen. */
public static final int FLAG_SCALED = 0x00004000;
-
+
/** Window flag: intended for windows that will often be used when the user is
* holding the screen against their face, it will aggressively filter the event
* stream to prevent unintended presses in this situation that may not be
- * desired for a particular window, when such an event stream is detected, the
+ * desired for a particular window, when such an event stream is detected, the
* application will receive a CANCEL motion event to indicate this so applications
- * can handle this accordingly by taking no action on the event
+ * can handle this accordingly by taking no action on the event
* until the finger is released. */
public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;
-
+
/** Window flag: a special option only for use in combination with
* {@link #FLAG_LAYOUT_IN_SCREEN}. When requesting layout in the
* screen your window may appear on top of or behind screen decorations
@@ -690,7 +690,7 @@ public interface WindowManager extends ViewManager {
* content is not covered by screen decorations. This flag is normally
* set for you by Window as described in {@link Window#setFlags}.*/
public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
-
+
/** Window flag: invert the state of {@link #FLAG_NOT_FOCUSABLE} with
* respect to how this window interacts with the current method. That
* is, if FLAG_NOT_FOCUSABLE is set and this flag is set, then the
@@ -701,7 +701,7 @@ public interface WindowManager extends ViewManager {
* to use more space and cover the input method.
*/
public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
-
+
/** Window flag: if you have set {@link #FLAG_NOT_TOUCH_MODAL}, you
* can set this flag to receive a single special MotionEvent with
* the action
@@ -711,7 +711,7 @@ public interface WindowManager extends ViewManager {
* first down as an ACTION_OUTSIDE.
*/
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
-
+
/** Window flag: special flag to let windows be shown when the screen
* is locked. This will let application windows take precedence over
* key guard or any other lock screens. Can be used with
@@ -741,13 +741,13 @@ public interface WindowManager extends ViewManager {
* {@link android.R.style#Theme_DeviceDefault_Wallpaper_NoTitleBar}.</p>
*/
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
-
+
/** Window flag: when set as a window is being added or made
* visible, once the window has been shown then the system will
* poke the power manager's user activity (as if the user had woken
* up the device) to turn the screen on. */
public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
-
+
/** Window flag: when set the window will cause the keyguard to
* be dismissed, only if it is not a secure lock keyguard. Because such
* a keyguard is not needed for security, it will never re-appear if
@@ -761,7 +761,7 @@ public interface WindowManager extends ViewManager {
* also been set.
*/
public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
-
+
/** Window flag: when set the window will accept for touch events
* outside of its bounds to be sent to other windows that also
* support split touch. When this flag is not set, the first pointer
@@ -773,7 +773,7 @@ public interface WindowManager extends ViewManager {
* to be split across multiple windows.
*/
public static final int FLAG_SPLIT_TOUCH = 0x00800000;
-
+
/**
* <p>Indicates whether this window should be hardware accelerated.
* Requesting hardware acceleration does not guarantee it will happen.</p>
@@ -916,7 +916,7 @@ public interface WindowManager extends ViewManager {
/**
* Various behavioral options/flags. Default is none.
- *
+ *
* @see #FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
* @see #FLAG_DIM_BEHIND
* @see #FLAG_NOT_FOCUSABLE
@@ -1014,10 +1014,10 @@ public interface WindowManager extends ViewManager {
* as if it was.
* Like {@link #FLAG_HARDWARE_ACCELERATED} except for trusted system windows
* that need hardware acceleration (e.g. LockScreen), where hardware acceleration
- * is generally disabled. This flag must be specified in addition to
+ * is generally disabled. This flag must be specified in addition to
* {@link #FLAG_HARDWARE_ACCELERATED} to enable hardware acceleration for system
* windows.
- *
+ *
* @hide
*/
public static final int PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED = 0x00000001;
@@ -1028,7 +1028,7 @@ public interface WindowManager extends ViewManager {
* If certain parts of the UI that really do want to use hardware
* acceleration, this flag can be set to force it. This is basically
* for the lock screen. Anyone else using it, you are probably wrong.
- *
+ *
* @hide
*/
public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002;
@@ -1086,6 +1086,11 @@ public interface WindowManager extends ViewManager {
* {@hide} */
public static final int PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR = 0x00000200;
+ /** Window flag: the window is backed by a video plane, instead of a
+ * regular surface.
+ * {@hide} */
+ public static final int PRIVATE_FLAG_VIDEO_PLANE = 0x00000400;
+
/**
* Control flags that are private to the platform.
* @hide
@@ -1100,9 +1105,9 @@ public interface WindowManager extends ViewManager {
* flags and returns true if the combination of the two corresponds
* to a window that needs to be behind the input method so that the
* user can type into it.
- *
+ *
* @param flags The current window manager flags.
- *
+ *
* @return Returns true if such a window should be behind/interact
* with an input method, false if not.
*/
@@ -1114,63 +1119,63 @@ public interface WindowManager extends ViewManager {
}
return false;
}
-
+
/**
* Mask for {@link #softInputMode} of the bits that determine the
* desired visibility state of the soft input area for this window.
*/
public static final int SOFT_INPUT_MASK_STATE = 0x0f;
-
+
/**
* Visibility state for {@link #softInputMode}: no state has been specified.
*/
public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
-
+
/**
* Visibility state for {@link #softInputMode}: please don't change the state of
* the soft input area.
*/
public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
-
+
/**
* Visibility state for {@link #softInputMode}: please hide any soft input
* area when normally appropriate (when the user is navigating
* forward to your window).
*/
public static final int SOFT_INPUT_STATE_HIDDEN = 2;
-
+
/**
* Visibility state for {@link #softInputMode}: please always hide any
* soft input area when this window receives focus.
*/
public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
-
+
/**
* Visibility state for {@link #softInputMode}: please show the soft
* input area when normally appropriate (when the user is navigating
* forward to your window).
*/
public static final int SOFT_INPUT_STATE_VISIBLE = 4;
-
+
/**
* Visibility state for {@link #softInputMode}: please always make the
* soft input area visible when this window receives input focus.
*/
public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
-
+
/**
* Mask for {@link #softInputMode} of the bits that determine the
* way that the window should be adjusted to accommodate the soft
* input window.
*/
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
-
+
/** Adjustment option for {@link #softInputMode}: nothing specified.
* The system will try to pick one or
* the other depending on the contents of the window.
*/
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
-
+
/** Adjustment option for {@link #softInputMode}: set to allow the
* window to be resized when an input
* method is shown, so that its contents are not covered by the input
@@ -1183,7 +1188,7 @@ public interface WindowManager extends ViewManager {
* not resize, but will stay fullscreen.
*/
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
-
+
/** Adjustment option for {@link #softInputMode}: set to have a window
* pan when an input method is
* shown, so it doesn't need to deal with resizing but just panned
@@ -1193,7 +1198,7 @@ public interface WindowManager extends ViewManager {
* the other depending on the contents of the window.
*/
public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
-
+
/** Adjustment option for {@link #softInputMode}: set to have a window
* not adjust for a shown input method. The window will not be resized,
* and it will not be panned to make its focus visible.
@@ -1212,7 +1217,7 @@ public interface WindowManager extends ViewManager {
/**
* Desired operating mode for any soft input area. May be any combination
* of:
- *
+ *
* <ul>
* <li> One of the visibility states
* {@link #SOFT_INPUT_STATE_UNSPECIFIED}, {@link #SOFT_INPUT_STATE_UNCHANGED},
@@ -1229,7 +1234,7 @@ public interface WindowManager extends ViewManager {
* {@link android.R.attr#windowSoftInputMode} attribute.</p>
*/
public int softInputMode;
-
+
/**
* Placement of window within the screen as per {@link Gravity}. Both
* {@link Gravity#apply(int, int, int, android.graphics.Rect, int, int,
@@ -1246,7 +1251,7 @@ public interface WindowManager extends ViewManager {
* @see Gravity
*/
public int gravity;
-
+
/**
* The horizontal margin, as a percentage of the container's width,
* between the container and the widget. See
@@ -1255,7 +1260,7 @@ public interface WindowManager extends ViewManager {
* field is added with {@link #x} to supply the <var>xAdj</var> parameter.
*/
public float horizontalMargin;
-
+
/**
* The vertical margin, as a percentage of the container's height,
* between the container and the widget. See
@@ -1264,26 +1269,26 @@ public interface WindowManager extends ViewManager {
* field is added with {@link #y} to supply the <var>yAdj</var> parameter.
*/
public float verticalMargin;
-
+
/**
* The desired bitmap format. May be one of the constants in
* {@link android.graphics.PixelFormat}. Default is OPAQUE.
*/
public int format;
-
+
/**
* A style resource defining the animations to use for this window.
* This must be a system resource; it can not be an application resource
* because the window manager does not have access to applications.
*/
public int windowAnimations;
-
+
/**
* An alpha value to apply to this entire window.
* An alpha of 1.0 means fully opaque and 0.0 means fully transparent
*/
public float alpha = 1.0f;
-
+
/**
* When {@link #FLAG_DIM_BEHIND} is set, this is the amount of dimming
* to apply. Range is from 1.0 for completely opaque to 0.0 for no
@@ -1311,7 +1316,7 @@ public interface WindowManager extends ViewManager {
* to the hightest value when this window is in front.
*/
public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f;
-
+
/**
* This can be used to override the user's preferred brightness of
* the screen. A value of less than 0, the default, means to use the
@@ -1319,7 +1324,7 @@ public interface WindowManager extends ViewManager {
* dark to full bright.
*/
public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE;
-
+
/**
* This can be used to override the standard behavior of the button and
* keyboard backlights. A value of less than 0, the default, means to
@@ -1353,7 +1358,7 @@ public interface WindowManager extends ViewManager {
* opaque windows have the #FLAG_FULLSCREEN bit set and are not covered
* by other windows. All other situations default to the
* {@link #ROTATION_ANIMATION_ROTATE} behavior.
- *
+ *
* @see #ROTATION_ANIMATION_ROTATE
* @see #ROTATION_ANIMATION_CROSSFADE
* @see #ROTATION_ANIMATION_JUMPCUT
@@ -1365,18 +1370,18 @@ public interface WindowManager extends ViewManager {
* you.
*/
public IBinder token = null;
-
+
/**
* Name of the package owning this window.
*/
public String packageName = null;
-
+
/**
* Specific orientation value for a window.
* May be any of the same values allowed
- * for {@link android.content.pm.ActivityInfo#screenOrientation}.
- * If not set, a default value of
- * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}
+ * for {@link android.content.pm.ActivityInfo#screenOrientation}.
+ * If not set, a default value of
+ * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}
* will be used.
*/
public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -1398,7 +1403,7 @@ public interface WindowManager extends ViewManager {
/**
* Get callbacks about the system ui visibility changing.
- *
+ *
* TODO: Maybe there should be a bitfield of optional callbacks that we need.
*
* @hide
@@ -1464,34 +1469,34 @@ public interface WindowManager extends ViewManager {
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
-
+
public LayoutParams(int _type) {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = _type;
format = PixelFormat.OPAQUE;
}
-
+
public LayoutParams(int _type, int _flags) {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = _type;
flags = _flags;
format = PixelFormat.OPAQUE;
}
-
+
public LayoutParams(int _type, int _flags, int _format) {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = _type;
flags = _flags;
format = _format;
}
-
+
public LayoutParams(int w, int h, int _type, int _flags, int _format) {
super(w, h);
type = _type;
flags = _flags;
format = _format;
}
-
+
public LayoutParams(int w, int h, int xpos, int ypos, int _type,
int _flags, int _format) {
super(w, h);
@@ -1501,18 +1506,18 @@ public interface WindowManager extends ViewManager {
flags = _flags;
format = _format;
}
-
+
public final void setTitle(CharSequence title) {
if (null == title)
title = "";
-
+
mTitle = TextUtils.stringOrSpannedString(title);
}
-
+
public final CharSequence getTitle() {
return mTitle;
}
-
+
public int describeContents() {
return 0;
}
@@ -1546,19 +1551,19 @@ public interface WindowManager extends ViewManager {
out.writeInt(inputFeatures);
out.writeLong(userActivityTimeout);
}
-
+
public static final Parcelable.Creator<LayoutParams> CREATOR
= new Parcelable.Creator<LayoutParams>() {
public LayoutParams createFromParcel(Parcel in) {
return new LayoutParams(in);
}
-
+
public LayoutParams[] newArray(int size) {
return new LayoutParams[size];
}
};
-
-
+
+
public LayoutParams(Parcel in) {
width = in.readInt();
height = in.readInt();
@@ -1588,7 +1593,7 @@ public interface WindowManager extends ViewManager {
inputFeatures = in.readInt();
userActivityTimeout = in.readLong();
}
-
+
@SuppressWarnings({"PointlessBitwiseExpression"})
public static final int LAYOUT_CHANGED = 1<<0;
public static final int TYPE_CHANGED = 1<<1;
@@ -1622,10 +1627,10 @@ public interface WindowManager extends ViewManager {
// internal buffer to backup/restore parameters under compatibility mode.
private int[] mCompatibilityParamsBackup = null;
-
+
public final int copyFrom(LayoutParams o) {
int changes = 0;
-
+
if (width != o.width) {
width = o.width;
changes |= LAYOUT_CHANGED;
@@ -1724,7 +1729,7 @@ public interface WindowManager extends ViewManager {
rotationAnimation = o.rotationAnimation;
changes |= ROTATION_ANIMATION_CHANGED;
}
-
+
if (screenOrientation != o.screenOrientation) {
screenOrientation = o.screenOrientation;
changes |= SCREEN_ORIENTATION_CHANGED;
@@ -1754,7 +1759,7 @@ public interface WindowManager extends ViewManager {
return changes;
}
-
+
@Override
public String debug(String output) {
output += "Contents of " + this + ":";
@@ -1765,7 +1770,7 @@ public interface WindowManager extends ViewManager {
Log.d("Debug", "WindowManager.LayoutParams={title=" + mTitle + "}");
return "";
}
-
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder(256);
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 74dda77..75656545 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
@@ -450,6 +454,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;
@@ -1006,7 +1015,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
@@ -1021,7 +1031,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.
@@ -1070,7 +1081,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.
@@ -1097,6 +1108,7 @@ public interface WindowManagerPolicy {
* @see WindowManagerPolicy#USER_ROTATION_LOCKED
* @see WindowManagerPolicy#USER_ROTATION_FREE
*/
+ @UserRotationMode
public int getUserRotationMode();
/**
@@ -1107,12 +1119,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);
@@ -1135,6 +1147,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 879e58f..a711f48 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..560d0c9 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;
@@ -282,6 +282,22 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public static final int ACTION_DISMISS = 0x00100000;
+ /**
+ * Action that sets the text of the node. Performing the action without argument, using <code>
+ * null</code> or empty {@link CharSequence} will clear the text. This action will also put the
+ * cursor at the end of text.
+ * <p>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
+ * "android");
+ * info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
+ * </code></pre></p>
+ */
+ public static final int ACTION_SET_TEXT = 0x00200000;
+
// Action arguments
/**
@@ -351,6 +367,18 @@ public class AccessibilityNodeInfo implements Parcelable {
public static final String ACTION_ARGUMENT_SELECTION_END_INT =
"ACTION_ARGUMENT_SELECTION_END_INT";
+ /**
+ * Argument for specifying the text content to set
+ * <p>
+ * <strong>Type:</strong> CharSequence<br>
+ * <strong>Actions:</strong> {@link #ACTION_SET_TEXT}
+ * </p>
+ *
+ * @see #ACTION_SET_TEXT
+ */
+ public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE =
+ "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
+
// Focus types
/**
@@ -503,7 +531,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 +694,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 +741,9 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public AccessibilityNodeInfo getChild(int index) {
enforceSealed();
+ if (mChildNodeIds == null) {
+ return null;
+ }
if (!canPerformRequestOverConnection(mSourceNodeId)) {
return null;
}
@@ -721,7 +766,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 +812,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 +899,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 +1536,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 +2077,7 @@ public class AccessibilityNodeInfo implements Parcelable {
/**
* {@inheritDoc}
*/
+ @Override
public int describeContents() {
return 0;
}
@@ -2114,6 +2241,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 +2251,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);
@@ -2179,6 +2311,7 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.writeInt(mCollectionInfo.getRowCount());
parcel.writeInt(mCollectionInfo.getColumnCount());
parcel.writeInt(mCollectionInfo.isHierarchical() ? 1 : 0);
+ parcel.writeInt(mCollectionInfo.getSelectionMode());
} else {
parcel.writeInt(0);
}
@@ -2190,6 +2323,7 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.writeInt(mCollectionItemInfo.getRowIndex());
parcel.writeInt(mCollectionItemInfo.getRowSpan());
parcel.writeInt(mCollectionItemInfo.isHeading() ? 1 : 0);
+ parcel.writeInt(mCollectionItemInfo.isSelected() ? 1 : 0);
} else {
parcel.writeInt(0);
}
@@ -2222,10 +2356,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 +2395,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();
@@ -2306,7 +2450,8 @@ public class AccessibilityNodeInfo implements Parcelable {
mCollectionInfo = CollectionInfo.obtain(
parcel.readInt(),
parcel.readInt(),
- parcel.readInt() == 1);
+ parcel.readInt() == 1,
+ parcel.readInt());
}
if (parcel.readInt() == 1) {
@@ -2315,6 +2460,7 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
+ parcel.readInt() == 1,
parcel.readInt() == 1);
}
}
@@ -2331,7 +2477,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 +2641,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("]");
@@ -2668,6 +2818,15 @@ public class AccessibilityNodeInfo implements Parcelable {
* </p>
*/
public static final class CollectionInfo {
+ /** Selection mode where items are not selectable. */
+ public static final int SELECTION_MODE_NONE = 0;
+
+ /** Selection mode where a single item may be selected. */
+ public static final int SELECTION_MODE_SINGLE = 1;
+
+ /** Selection mode where multiple items may be selected. */
+ public static final int SELECTION_MODE_MULTIPLE = 2;
+
private static final int MAX_POOL_SIZE = 20;
private static final SynchronizedPool<CollectionInfo> sPool =
@@ -2676,17 +2835,17 @@ public class AccessibilityNodeInfo implements Parcelable {
private int mRowCount;
private int mColumnCount;
private boolean mHierarchical;
+ private int mSelectionMode;
/**
* Obtains a pooled instance that is a clone of another one.
*
* @param other The instance to clone.
- *
* @hide
*/
public static CollectionInfo obtain(CollectionInfo other) {
- return CollectionInfo.obtain(other.mRowCount, other.mColumnCount,
- other.mHierarchical);
+ return CollectionInfo.obtain(other.mRowCount, other.mColumnCount, other.mHierarchical,
+ other.mSelectionMode);
}
/**
@@ -2698,9 +2857,34 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public static CollectionInfo obtain(int rowCount, int columnCount,
boolean hierarchical) {
- CollectionInfo info = sPool.acquire();
- return (info != null) ? info : new CollectionInfo(rowCount,
- columnCount, hierarchical);
+ return obtain(rowCount, columnCount, hierarchical, SELECTION_MODE_NONE);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * @param rowCount The number of rows.
+ * @param columnCount The number of columns.
+ * @param hierarchical Whether the collection is hierarchical.
+ * @param selectionMode The collection's selection mode, one of:
+ * <ul>
+ * <li>{@link #SELECTION_MODE_NONE}
+ * <li>{@link #SELECTION_MODE_SINGLE}
+ * <li>{@link #SELECTION_MODE_MULTIPLE}
+ * </ul>
+ */
+ public static CollectionInfo obtain(int rowCount, int columnCount,
+ boolean hierarchical, int selectionMode) {
+ final CollectionInfo info = sPool.acquire();
+ if (info == null) {
+ return new CollectionInfo(rowCount, columnCount, hierarchical, selectionMode);
+ }
+
+ info.mRowCount = rowCount;
+ info.mColumnCount = columnCount;
+ info.mHierarchical = hierarchical;
+ info.mSelectionMode = selectionMode;
+ return info;
}
/**
@@ -2709,12 +2893,14 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param rowCount The number of rows.
* @param columnCount The number of columns.
* @param hierarchical Whether the collection is hierarchical.
+ * @param selectionMode The collection's selection mode.
*/
- private CollectionInfo(int rowCount, int columnCount,
- boolean hierarchical) {
+ private CollectionInfo(int rowCount, int columnCount, boolean hierarchical,
+ int selectionMode) {
mRowCount = rowCount;
mColumnCount = columnCount;
mHierarchical = hierarchical;
+ mSelectionMode = selectionMode;
}
/**
@@ -2745,6 +2931,20 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Gets the collection's selection mode.
+ *
+ * @return The collection's selection mode, one of:
+ * <ul>
+ * <li>{@link #SELECTION_MODE_NONE}
+ * <li>{@link #SELECTION_MODE_SINGLE}
+ * <li>{@link #SELECTION_MODE_MULTIPLE}
+ * </ul>
+ */
+ public int getSelectionMode() {
+ return mSelectionMode;
+ }
+
+ /**
* Recycles this instance.
*/
void recycle() {
@@ -2756,6 +2956,7 @@ public class AccessibilityNodeInfo implements Parcelable {
mRowCount = 0;
mColumnCount = 0;
mHierarchical = false;
+ mSelectionMode = SELECTION_MODE_NONE;
}
}
@@ -2781,12 +2982,11 @@ public class AccessibilityNodeInfo implements Parcelable {
* Obtains a pooled instance that is a clone of another one.
*
* @param other The instance to clone.
- *
* @hide
*/
public static CollectionItemInfo obtain(CollectionItemInfo other) {
- return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan,
- other.mColumnIndex, other.mColumnSpan, other.mHeading);
+ return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan, other.mColumnIndex,
+ other.mColumnSpan, other.mHeading, other.mSelected);
}
/**
@@ -2800,9 +3000,34 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
int columnIndex, int columnSpan, boolean heading) {
- CollectionItemInfo info = sPool.acquire();
- return (info != null) ? info : new CollectionItemInfo(rowIndex,
- rowSpan, columnIndex, columnSpan, heading);
+ return obtain(rowIndex, rowSpan, columnIndex, columnSpan, heading, false);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * @param rowIndex The row index at which the item is located.
+ * @param rowSpan The number of rows the item spans.
+ * @param columnIndex The column index at which the item is located.
+ * @param columnSpan The number of columns the item spans.
+ * @param heading Whether the item is a heading.
+ * @param selected Whether the item is selected.
+ */
+ public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
+ int columnIndex, int columnSpan, boolean heading, boolean selected) {
+ final CollectionItemInfo info = sPool.acquire();
+ if (info == null) {
+ return new CollectionItemInfo(
+ rowIndex, rowSpan, columnIndex, columnSpan, heading, selected);
+ }
+
+ info.mRowIndex = rowIndex;
+ info.mRowSpan = rowSpan;
+ info.mColumnIndex = columnIndex;
+ info.mColumnSpan = columnSpan;
+ info.mHeading = heading;
+ info.mSelected = selected;
+ return info;
}
private boolean mHeading;
@@ -2810,6 +3035,7 @@ public class AccessibilityNodeInfo implements Parcelable {
private int mRowIndex;
private int mColumnSpan;
private int mRowSpan;
+ private boolean mSelected;
/**
* Creates a new instance.
@@ -2820,13 +3046,14 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param columnSpan The number of columns the item spans.
* @param heading Whether the item is a heading.
*/
- private CollectionItemInfo(int rowIndex, int rowSpan,
- int columnIndex, int columnSpan, boolean heading) {
+ private CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan,
+ boolean heading, boolean selected) {
mRowIndex = rowIndex;
mRowSpan = rowSpan;
mColumnIndex = columnIndex;
mColumnSpan = columnSpan;
mHeading = heading;
+ mSelected = selected;
}
/**
@@ -2876,6 +3103,15 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Gets if the collection item is selected.
+ *
+ * @return If the item is selected.
+ */
+ public boolean isSelected() {
+ return mSelected;
+ }
+
+ /**
* Recycles this instance.
*/
void recycle() {
@@ -2889,20 +3125,23 @@ public class AccessibilityNodeInfo implements Parcelable {
mRowIndex = 0;
mRowSpan = 0;
mHeading = false;
+ mSelected = false;
}
}
/**
- * @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..b4944be 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,13 +172,15 @@ 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();
- for (int i = 0; i < oldChildCount; i++) {
- final long oldChildId = oldChildrenIds.valueAt(i);
- if (newChildrenIds.indexOfValue(oldChildId) < 0) {
- clearSubTreeLocked(oldChildId);
+ // TODO: Runs in O(n^2), could optimize to O(n + n log n)
+ final LongArray newChildrenIds = info.getChildNodeIds();
+ if (newChildrenIds != null) {
+ final int oldChildCount = oldInfo.getChildCount();
+ for (int i = 0; i < oldChildCount; i++) {
+ final long oldChildId = oldInfo.getChildId(i);
+ if (newChildrenIds.indexOf(oldChildId) < 0) {
+ clearSubTreeLocked(oldChildId);
+ }
}
}
@@ -237,10 +239,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,29 +302,35 @@ 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);
}
}
}
+ int disconnectedNodeCount = 0;
// Check for disconnected nodes or ones from another window.
for (int i = 0; i < mCacheImpl.size(); i++) {
AccessibilityNodeInfo info = mCacheImpl.valueAt(i);
if (!seen.contains(info)) {
if (info.getWindowId() == windowId) {
- Log.e(LOG_TAG, "Disconneced node: " + info);
+ if (DEBUG) {
+ Log.e(LOG_TAG, "Disconnected node: " + info);
+ }
+ disconnectedNodeCount++;
} else {
Log.e(LOG_TAG, "Node from: " + info.getWindowId() + " not from:"
+ windowId + " " + info);
}
}
}
+ if (disconnectedNodeCount > 0) {
+ Log.e(LOG_TAG, String.format("Found %d disconnected nodes", disconnectedNodeCount));
+ }
}
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.aidl b/core/java/android/view/accessibility/AccessibilityRecord.aidl
new file mode 100644
index 0000000..d542af0
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityRecord.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2014, 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.accessibility;
+
+parcelable AccessibilityRecord;
diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java
index 557239f..a0134d6 100644
--- a/core/java/android/view/accessibility/CaptioningManager.java
+++ b/core/java/android/view/accessibility/CaptioningManager.java
@@ -250,6 +250,9 @@ public class CaptioningManager {
* background colors, edge properties, and typeface.
*/
public static final class CaptionStyle {
+ /** Packed value for a color of 'none' and a cached opacity of 100%. */
+ private static final int COLOR_NONE_OPAQUE = 0x000000FF;
+
private static final CaptionStyle WHITE_ON_BLACK;
private static final CaptionStyle BLACK_ON_WHITE;
private static final CaptionStyle YELLOW_ON_BLACK;
@@ -271,6 +274,12 @@ public class CaptioningManager {
/** Edge type value specifying drop-shadowed character edges. */
public static final int EDGE_TYPE_DROP_SHADOW = 2;
+ /** Edge type value specifying raised bevel character edges. */
+ public static final int EDGE_TYPE_RAISED = 3;
+
+ /** Edge type value specifying depressed bevel character edges. */
+ public static final int EDGE_TYPE_DEPRESSED = 4;
+
/** The preferred foreground color for video captions. */
public final int foregroundColor;
@@ -283,6 +292,8 @@ public class CaptioningManager {
* <li>{@link #EDGE_TYPE_NONE}
* <li>{@link #EDGE_TYPE_OUTLINE}
* <li>{@link #EDGE_TYPE_DROP_SHADOW}
+ * <li>{@link #EDGE_TYPE_RAISED}
+ * <li>{@link #EDGE_TYPE_DEPRESSED}
* </ul>
*/
public final int edgeType;
@@ -293,6 +304,9 @@ public class CaptioningManager {
*/
public final int edgeColor;
+ /** The preferred window color for video captions. */
+ public final int windowColor;
+
/**
* @hide
*/
@@ -301,11 +315,12 @@ public class CaptioningManager {
private Typeface mParsedTypeface;
private CaptionStyle(int foregroundColor, int backgroundColor, int edgeType, int edgeColor,
- String rawTypeface) {
+ int windowColor, String rawTypeface) {
this.foregroundColor = foregroundColor;
this.backgroundColor = backgroundColor;
this.edgeType = edgeType;
this.edgeColor = edgeColor;
+ this.windowColor = windowColor;
mRawTypeface = rawTypeface;
}
@@ -334,25 +349,27 @@ public class CaptioningManager {
cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, defStyle.edgeType);
final int edgeColor = Secure.getInt(
cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, defStyle.edgeColor);
+ final int windowColor = Secure.getInt(
+ cr, Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, defStyle.windowColor);
String rawTypeface = Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);
if (rawTypeface == null) {
rawTypeface = defStyle.mRawTypeface;
}
- return new CaptionStyle(
- foregroundColor, backgroundColor, edgeType, edgeColor, rawTypeface);
+ return new CaptionStyle(foregroundColor, backgroundColor, edgeType, edgeColor,
+ windowColor, rawTypeface);
}
static {
- WHITE_ON_BLACK = new CaptionStyle(
- Color.WHITE, Color.BLACK, EDGE_TYPE_NONE, Color.BLACK, null);
- BLACK_ON_WHITE = new CaptionStyle(
- Color.BLACK, Color.WHITE, EDGE_TYPE_NONE, Color.BLACK, null);
- YELLOW_ON_BLACK = new CaptionStyle(
- Color.YELLOW, Color.BLACK, EDGE_TYPE_NONE, Color.BLACK, null);
- YELLOW_ON_BLUE = new CaptionStyle(
- Color.YELLOW, Color.BLUE, EDGE_TYPE_NONE, Color.BLACK, null);
+ WHITE_ON_BLACK = new CaptionStyle(Color.WHITE, Color.BLACK, EDGE_TYPE_NONE,
+ Color.BLACK, COLOR_NONE_OPAQUE, null);
+ BLACK_ON_WHITE = new CaptionStyle(Color.BLACK, Color.WHITE, EDGE_TYPE_NONE,
+ Color.BLACK, COLOR_NONE_OPAQUE, null);
+ YELLOW_ON_BLACK = new CaptionStyle(Color.YELLOW, Color.BLACK, EDGE_TYPE_NONE,
+ Color.BLACK, COLOR_NONE_OPAQUE, null);
+ YELLOW_ON_BLUE = new CaptionStyle(Color.YELLOW, Color.BLUE, EDGE_TYPE_NONE,
+ Color.BLACK, COLOR_NONE_OPAQUE, null);
PRESETS = new CaptionStyle[] {
WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 38043b2..1d1fa1e 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -324,6 +324,8 @@ public class AnimationUtils {
interpolator = new AnticipateOvershootInterpolator(c, attrs);
} else if (name.equals("bounceInterpolator")) {
interpolator = new BounceInterpolator(c, attrs);
+ } else if (name.equals("pathInterpolator")) {
+ interpolator = new PathInterpolator(c, attrs);
} else {
throw new RuntimeException("Unknown interpolator name: " + parser.getName());
}
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/animation/PathInterpolator.java b/core/java/android/view/animation/PathInterpolator.java
new file mode 100644
index 0000000..a369509
--- /dev/null
+++ b/core/java/android/view/animation/PathInterpolator.java
@@ -0,0 +1,203 @@
+/*
+ * 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.animation;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.InflateException;
+
+/**
+ * An interpolator that can traverse a Path that extends from <code>Point</code>
+ * <code>(0, 0)</code> to <code>(1, 1)</code>. The x coordinate along the <code>Path</code>
+ * is the input value and the output is the y coordinate of the line at that point.
+ * This means that the Path must conform to a function <code>y = f(x)</code>.
+ *
+ * <p>The <code>Path</code> must not have gaps in the x direction and must not
+ * loop back on itself such that there can be two points sharing the same x coordinate.
+ * It is alright to have a disjoint line in the vertical direction:</p>
+ * <p><blockquote><pre>
+ * Path path = new Path();
+ * path.lineTo(0.25f, 0.25f);
+ * path.moveTo(0.25f, 0.5f);
+ * path.lineTo(1f, 1f);
+ * </pre></blockquote></p>
+ */
+public class PathInterpolator implements Interpolator {
+
+ // This governs how accurate the approximation of the Path is.
+ private static final float PRECISION = 0.002f;
+
+ private float[] mX; // x coordinates in the line
+
+ private float[] mY; // y coordinates in the line
+
+ /**
+ * Create an interpolator for an arbitrary <code>Path</code>. The <code>Path</code>
+ * must begin at <code>(0, 0)</code> and end at <code>(1, 1)</code>.
+ *
+ * @param path The <code>Path</code> to use to make the line representing the interpolator.
+ */
+ public PathInterpolator(Path path) {
+ initPath(path);
+ }
+
+ /**
+ * Create an interpolator for a quadratic Bezier curve. The end points
+ * <code>(0, 0)</code> and <code>(1, 1)</code> are assumed.
+ *
+ * @param controlX The x coordinate of the quadratic Bezier control point.
+ * @param controlY The y coordinate of the quadratic Bezier control point.
+ */
+ public PathInterpolator(float controlX, float controlY) {
+ initQuad(controlX, controlY);
+ }
+
+ /**
+ * Create an interpolator for a cubic Bezier curve. The end points
+ * <code>(0, 0)</code> and <code>(1, 1)</code> are assumed.
+ *
+ * @param controlX1 The x coordinate of the first control point of the cubic Bezier.
+ * @param controlY1 The y coordinate of the first control point of the cubic Bezier.
+ * @param controlX2 The x coordinate of the second control point of the cubic Bezier.
+ * @param controlY2 The y coordinate of the second control point of the cubic Bezier.
+ */
+ public PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2) {
+ initCubic(controlX1, controlY1, controlX2, controlY2);
+ }
+
+ public PathInterpolator(Context context, AttributeSet attrs) {
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.PathInterpolator);
+ if (!a.hasValue(com.android.internal.R.styleable.PathInterpolator_controlX1)) {
+ throw new InflateException("pathInterpolator requires the controlX1 attribute");
+ } else if (!a.hasValue(com.android.internal.R.styleable.PathInterpolator_controlY1)) {
+ throw new InflateException("pathInterpolator requires the controlY1 attribute");
+ }
+ float x1 = a.getFloat(com.android.internal.R.styleable.PathInterpolator_controlX1, 0);
+ float y1 = a.getFloat(com.android.internal.R.styleable.PathInterpolator_controlY1, 0);
+
+ boolean hasX2 = a.hasValue(com.android.internal.R.styleable.PathInterpolator_controlX2);
+ boolean hasY2 = a.hasValue(com.android.internal.R.styleable.PathInterpolator_controlY2);
+
+ if (hasX2 != hasY2) {
+ throw new InflateException(
+ "pathInterpolator requires both controlX2 and controlY2 for cubic Beziers.");
+ }
+
+ if (!hasX2) {
+ initQuad(x1, y1);
+ } else {
+ float x2 = a.getFloat(com.android.internal.R.styleable.PathInterpolator_controlX2, 0);
+ float y2 = a.getFloat(com.android.internal.R.styleable.PathInterpolator_controlY2, 0);
+ initCubic(x1, y1, x2, y2);
+ }
+
+ a.recycle();
+ }
+
+ private void initQuad(float controlX, float controlY) {
+ Path path = new Path();
+ path.moveTo(0, 0);
+ path.quadTo(controlX, controlY, 1f, 1f);
+ initPath(path);
+ }
+
+ private void initCubic(float x1, float y1, float x2, float y2) {
+ Path path = new Path();
+ path.moveTo(0, 0);
+ path.cubicTo(x1, y1, x2, y2, 1f, 1f);
+ initPath(path);
+ }
+
+ private void initPath(Path path) {
+ float[] pointComponents = path.approximate(PRECISION);
+
+ int numPoints = pointComponents.length / 3;
+ if (pointComponents[1] != 0 || pointComponents[2] != 0
+ || pointComponents[pointComponents.length - 2] != 1
+ || pointComponents[pointComponents.length - 1] != 1) {
+ throw new IllegalArgumentException("The Path must start at (0,0) and end at (1,1)");
+ }
+
+ mX = new float[numPoints];
+ mY = new float[numPoints];
+ float prevX = 0;
+ float prevFraction = 0;
+ int componentIndex = 0;
+ for (int i = 0; i < numPoints; i++) {
+ float fraction = pointComponents[componentIndex++];
+ float x = pointComponents[componentIndex++];
+ float y = pointComponents[componentIndex++];
+ if (fraction == prevFraction && x != prevX) {
+ throw new IllegalArgumentException(
+ "The Path cannot have discontinuity in the X axis.");
+ }
+ if (x < prevX) {
+ throw new IllegalArgumentException("The Path cannot loop back on itself.");
+ }
+ mX[i] = x;
+ mY[i] = y;
+ prevX = x;
+ prevFraction = fraction;
+ }
+ }
+
+ /**
+ * Using the line in the Path in this interpolator that can be described as
+ * <code>y = f(x)</code>, finds the y coordinate of the line given <code>t</code>
+ * as the x coordinate. Values less than 0 will always return 0 and values greater
+ * than 1 will always return 1.
+ *
+ * @param t Treated as the x coordinate along the line.
+ * @return The y coordinate of the Path along the line where x = <code>t</code>.
+ * @see Interpolator#getInterpolation(float)
+ */
+ @Override
+ public float getInterpolation(float t) {
+ if (t <= 0) {
+ return 0;
+ } else if (t >= 1) {
+ return 1;
+ }
+ // Do a binary search for the correct x to interpolate between.
+ int startIndex = 0;
+ int endIndex = mX.length - 1;
+
+ while (endIndex - startIndex > 1) {
+ int midIndex = (startIndex + endIndex) / 2;
+ if (t < mX[midIndex]) {
+ endIndex = midIndex;
+ } else {
+ startIndex = midIndex;
+ }
+ }
+
+ float xRange = mX[endIndex] - mX[startIndex];
+ if (xRange == 0) {
+ return mY[startIndex];
+ }
+
+ float tInRange = t - mX[startIndex];
+ float fraction = tInRange / xRange;
+
+ float startY = mY[startIndex];
+ float endY = mY[endIndex];
+ return startY + (fraction * (endY - startY));
+ }
+
+}
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index f730cf7..cccfa78 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -484,10 +484,10 @@ public class BaseInputConnection implements InputConnection {
final Editable content = getEditable();
if (content == null) return false;
int len = content.length();
- if (start > len || end > len) {
+ if (start > len || end > len || start < 0 || end < 0) {
// If the given selection is out of bounds, just ignore it.
// Most likely the text was changed out from under the IME,
- // the the IME is going to have to update all of its state
+ // and the IME is going to have to update all of its state
// anyway.
return true;
}
@@ -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/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index d4e005b..c0395cf 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -26,13 +26,13 @@ import android.util.Printer;
/**
* An EditorInfo describes several attributes of a text editing object
* that an input method is communicating with (typically an EditText), most
- * importantly the type of text content it contains.
+ * importantly the type of text content it contains and the current cursor position.
*/
public class EditorInfo implements InputType, Parcelable {
/**
* The content type of the text box, whose bits are defined by
* {@link InputType}.
- *
+ *
* @see InputType
* @see #TYPE_MASK_CLASS
* @see #TYPE_MASK_VARIATION
@@ -47,55 +47,55 @@ public class EditorInfo implements InputType, Parcelable {
* to provide alternative mechanisms for providing that command.
*/
public static final int IME_MASK_ACTION = 0x000000ff;
-
+
/**
* Bits of {@link #IME_MASK_ACTION}: no specific action has been
* associated with this editor, let the editor come up with its own if
* it can.
*/
public static final int IME_ACTION_UNSPECIFIED = 0x00000000;
-
+
/**
* Bits of {@link #IME_MASK_ACTION}: there is no available action.
*/
public static final int IME_ACTION_NONE = 0x00000001;
-
+
/**
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "go"
* operation to take the user to the target of the text they typed.
* Typically used, for example, when entering a URL.
*/
public static final int IME_ACTION_GO = 0x00000002;
-
+
/**
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "search"
* operation, taking the user to the results of searching for the text
* they have typed (in whatever context is appropriate).
*/
public static final int IME_ACTION_SEARCH = 0x00000003;
-
+
/**
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "send"
* operation, delivering the text to its target. This is typically used
* when composing a message in IM or SMS where sending is immediate.
*/
public static final int IME_ACTION_SEND = 0x00000004;
-
+
/**
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "next"
* operation, taking the user to the next field that will accept text.
*/
public static final int IME_ACTION_NEXT = 0x00000005;
-
+
/**
* Bits of {@link #IME_MASK_ACTION}: the action key performs a "done"
* operation, typically meaning there is nothing more to input and the
* IME will be closed.
*/
public static final int IME_ACTION_DONE = 0x00000006;
-
+
/**
- * Bits of {@link #IME_MASK_ACTION}: Like {@link #IME_ACTION_NEXT}, but
+ * Bits of {@link #IME_MASK_ACTION}: like {@link #IME_ACTION_NEXT}, but
* for moving to the previous field. This will normally not be used to
* specify an action (since it precludes {@link #IME_ACTION_NEXT}), but
* can be returned to the app if it sets {@link #IME_FLAG_NAVIGATE_PREVIOUS}.
@@ -154,7 +154,7 @@ public class EditorInfo implements InputType, Parcelable {
* on older versions of the platform.
*/
public static final int IME_FLAG_NO_EXTRACT_UI = 0x10000000;
-
+
/**
* Flag of {@link #imeOptions}: used in conjunction with one of the actions
* masked by {@link #IME_MASK_ACTION}, this indicates that the action
@@ -167,7 +167,7 @@ public class EditorInfo implements InputType, Parcelable {
* to show more text.
*/
public static final int IME_FLAG_NO_ACCESSORY_ACTION = 0x20000000;
-
+
/**
* Flag of {@link #imeOptions}: used in conjunction with one of the actions
* masked by {@link #IME_MASK_ACTION}. If this flag is not set, IMEs will
@@ -202,13 +202,13 @@ public class EditorInfo implements InputType, Parcelable {
* Generic unspecified type for {@link #imeOptions}.
*/
public static final int IME_NULL = 0x00000000;
-
+
/**
* Extended type information for the editor, to help the IME better
* integrate with it.
*/
public int imeOptions = IME_NULL;
-
+
/**
* A string supplying additional information options that are
* private to a particular IME implementation. The string must be
@@ -221,7 +221,7 @@ public class EditorInfo implements InputType, Parcelable {
* attribute of a TextView.
*/
public String privateImeOptions = null;
-
+
/**
* In some cases an IME may be able to display an arbitrary label for
* a command the user can perform, which you can specify here. This is
@@ -233,7 +233,7 @@ public class EditorInfo implements InputType, Parcelable {
* ignore this.
*/
public CharSequence actionLabel = null;
-
+
/**
* If {@link #actionLabel} has been given, this is the id for that command
* when the user presses its button that is delivered back with
@@ -241,50 +241,66 @@ public class EditorInfo implements InputType, Parcelable {
* InputConnection.performEditorAction()}.
*/
public int actionId = 0;
-
+
/**
* The text offset of the start of the selection at the time editing
- * began; -1 if not known. Keep in mind some IMEs may not be able
- * to give their full feature set without knowing the cursor position;
- * avoid passing -1 here if you can.
+ * begins; -1 if not known. Keep in mind that, without knowing the cursor
+ * position, many IMEs will not be able to offer their full feature set and
+ * may even behave in unpredictable ways: pass the actual cursor position
+ * here if possible at all.
+ *
+ * <p>Also, this needs to be the cursor position <strong>right now</strong>,
+ * not at some point in the past, even if input is starting in the same text field
+ * as before. When the app is filling this object, input is about to start by
+ * definition, and this value will override any value the app may have passed to
+ * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)}
+ * before.</p>
*/
public int initialSelStart = -1;
-
+
/**
- * The text offset of the end of the selection at the time editing
- * began; -1 if not known. Keep in mind some IMEs may not be able
- * to give their full feature set without knowing the cursor position;
- * avoid passing -1 here if you can.
+ * <p>The text offset of the end of the selection at the time editing
+ * begins; -1 if not known. Keep in mind that, without knowing the cursor
+ * position, many IMEs will not be able to offer their full feature set and
+ * may behave in unpredictable ways: pass the actual cursor position
+ * here if possible at all.</p>
+ *
+ * <p>Also, this needs to be the cursor position <strong>right now</strong>,
+ * not at some point in the past, even if input is starting in the same text field
+ * as before. When the app is filling this object, input is about to start by
+ * definition, and this value will override any value the app may have passed to
+ * {@link InputMethodManager#updateSelection(android.view.View, int, int, int, int)}
+ * before.</p>
*/
public int initialSelEnd = -1;
-
+
/**
* The capitalization mode of the first character being edited in the
* text. Values may be any combination of
* {@link TextUtils#CAP_MODE_CHARACTERS TextUtils.CAP_MODE_CHARACTERS},
* {@link TextUtils#CAP_MODE_WORDS TextUtils.CAP_MODE_WORDS}, and
* {@link TextUtils#CAP_MODE_SENTENCES TextUtils.CAP_MODE_SENTENCES}, though
- * you should generally just take a non-zero value to mean start out in
- * caps mode.
+ * you should generally just take a non-zero value to mean "start out in
+ * caps mode".
*/
public int initialCapsMode = 0;
-
+
/**
* The "hint" text of the text view, typically shown in-line when the
* text is empty to tell the user what to enter.
*/
public CharSequence hintText;
-
+
/**
* A label to show to the user describing the text they are writing.
*/
public CharSequence label;
-
+
/**
* Name of the package that owns this editor.
*/
public String packageName;
-
+
/**
* Identifier for the editor's field. This is optional, and may be
* 0. By default it is filled in with the result of
@@ -292,14 +308,14 @@ public class EditorInfo implements InputType, Parcelable {
* is being edited.
*/
public int fieldId;
-
+
/**
* Additional name for the editor's field. This can supply additional
* name information for the field. By default it is null. The actual
* contents have no meaning.
*/
public String fieldName;
-
+
/**
* Any extra data to supply to the input method. This is for extended
* communication with specific input methods; the name fields in the
@@ -309,7 +325,7 @@ public class EditorInfo implements InputType, Parcelable {
* attribute of a TextView.
*/
public Bundle extras;
-
+
/**
* Ensure that the data in this EditorInfo is compatible with an application
* that was developed against the given target API version. This can
@@ -365,10 +381,10 @@ public class EditorInfo implements InputType, Parcelable {
+ " fieldName=" + fieldName);
pw.println(prefix + "extras=" + extras);
}
-
+
/**
* Used to package this object into a {@link Parcel}.
- *
+ *
* @param dest The {@link Parcel} to be written.
* @param flags The flags used for parceling.
*/
@@ -392,30 +408,31 @@ public class EditorInfo implements InputType, Parcelable {
/**
* Used to make this class parcelable.
*/
- public static final Parcelable.Creator<EditorInfo> CREATOR = new Parcelable.Creator<EditorInfo>() {
- public EditorInfo createFromParcel(Parcel source) {
- EditorInfo res = new EditorInfo();
- res.inputType = source.readInt();
- res.imeOptions = source.readInt();
- res.privateImeOptions = source.readString();
- res.actionLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- res.actionId = source.readInt();
- res.initialSelStart = source.readInt();
- res.initialSelEnd = source.readInt();
- res.initialCapsMode = source.readInt();
- res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- res.packageName = source.readString();
- res.fieldId = source.readInt();
- res.fieldName = source.readString();
- res.extras = source.readBundle();
- return res;
- }
+ public static final Parcelable.Creator<EditorInfo> CREATOR =
+ new Parcelable.Creator<EditorInfo>() {
+ public EditorInfo createFromParcel(Parcel source) {
+ EditorInfo res = new EditorInfo();
+ res.inputType = source.readInt();
+ res.imeOptions = source.readInt();
+ res.privateImeOptions = source.readString();
+ res.actionLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ res.actionId = source.readInt();
+ res.initialSelStart = source.readInt();
+ res.initialSelEnd = source.readInt();
+ res.initialCapsMode = source.readInt();
+ res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ res.packageName = source.readString();
+ res.fieldId = source.readInt();
+ res.fieldName = source.readString();
+ res.extras = source.readBundle();
+ return res;
+ }
- public EditorInfo[] newArray(int size) {
- return new EditorInfo[size];
- }
- };
+ public EditorInfo[] newArray(int size) {
+ return new EditorInfo[size];
+ }
+ };
public int describeContents() {
return 0;
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/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 5df5811..f8160c8 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -1,12 +1,12 @@
/*
* Copyright (C) 2007-2008 The Android Open Source Project
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -37,6 +37,7 @@ import android.util.Printer;
import android.util.Slog;
import android.util.Xml;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
+import android.view.inputmethod.InputMethodSubtypeArray;
import java.io.IOException;
import java.util.ArrayList;
@@ -64,7 +65,7 @@ public final class InputMethodInfo implements Parcelable {
* The Service that implements this input method component.
*/
final ResolveInfo mService;
-
+
/**
* The unique string Id to identify the input method. This is generated
* from the input method component.
@@ -86,9 +87,9 @@ public final class InputMethodInfo implements Parcelable {
final int mIsDefaultResId;
/**
- * The array of the subtypes.
+ * An array-like container of the subtypes.
*/
- private final ArrayList<InputMethodSubtype> mSubtypes = new ArrayList<InputMethodSubtype>();
+ private final InputMethodSubtypeArray mSubtypes;
private final boolean mIsAuxIme;
@@ -138,28 +139,29 @@ public final class InputMethodInfo implements Parcelable {
int isDefaultResId = 0;
XmlResourceParser parser = null;
+ final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
try {
parser = si.loadXmlMetaData(pm, InputMethod.SERVICE_META_DATA);
if (parser == null) {
throw new XmlPullParserException("No "
+ InputMethod.SERVICE_META_DATA + " meta-data");
}
-
+
Resources res = pm.getResourcesForApplication(si.applicationInfo);
-
+
AttributeSet attrs = Xml.asAttributeSet(parser);
-
+
int type;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& type != XmlPullParser.START_TAG) {
}
-
+
String nodeName = parser.getName();
if (!"input-method".equals(nodeName)) {
throw new XmlPullParserException(
"Meta-data does not start with input-method tag");
}
-
+
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.InputMethod);
settingsActivityComponent = sa.getString(
@@ -206,7 +208,7 @@ public final class InputMethodInfo implements Parcelable {
if (!subtype.isAuxiliary()) {
isAuxIme = false;
}
- mSubtypes.add(subtype);
+ subtypes.add(subtype);
}
}
} catch (NameNotFoundException e) {
@@ -216,7 +218,7 @@ public final class InputMethodInfo implements Parcelable {
if (parser != null) parser.close();
}
- if (mSubtypes.size() == 0) {
+ if (subtypes.size() == 0) {
isAuxIme = false;
}
@@ -225,14 +227,15 @@ public final class InputMethodInfo implements Parcelable {
final int N = additionalSubtypes.size();
for (int i = 0; i < N; ++i) {
final InputMethodSubtype subtype = additionalSubtypes.get(i);
- if (!mSubtypes.contains(subtype)) {
- mSubtypes.add(subtype);
+ if (!subtypes.contains(subtype)) {
+ subtypes.add(subtype);
} else {
Slog.w(TAG, "Duplicated subtype definition found: "
+ subtype.getLocale() + ", " + subtype.getMode());
}
}
}
+ mSubtypes = new InputMethodSubtypeArray(subtypes);
mSettingsActivityName = settingsActivityComponent;
mIsDefaultResId = isDefaultResId;
mIsAuxIme = isAuxIme;
@@ -246,7 +249,7 @@ public final class InputMethodInfo implements Parcelable {
mIsAuxIme = source.readInt() == 1;
mSupportsSwitchingToNextInputMethod = source.readInt() == 1;
mService = ResolveInfo.CREATOR.createFromParcel(source);
- source.readTypedList(mSubtypes, InputMethodSubtype.CREATOR);
+ mSubtypes = new InputMethodSubtypeArray(source);
mForceDefault = false;
}
@@ -272,9 +275,7 @@ public final class InputMethodInfo implements Parcelable {
mSettingsActivityName = settingsActivity;
mIsDefaultResId = isDefaultResId;
mIsAuxIme = isAuxIme;
- if (subtypes != null) {
- mSubtypes.addAll(subtypes);
- }
+ mSubtypes = new InputMethodSubtypeArray(subtypes);
mForceDefault = forceDefault;
mSupportsSwitchingToNextInputMethod = true;
}
@@ -338,7 +339,7 @@ public final class InputMethodInfo implements Parcelable {
/**
* Load the user-displayed label for this input method.
- *
+ *
* @param pm Supply a PackageManager used to load the input method's
* resources.
*/
@@ -348,7 +349,7 @@ public final class InputMethodInfo implements Parcelable {
/**
* Load the user-displayed icon for this input method.
- *
+ *
* @param pm Supply a PackageManager used to load the input method's
* resources.
*/
@@ -362,9 +363,9 @@ public final class InputMethodInfo implements Parcelable {
* an {@link android.content.Intent} whose action is MAIN and with an
* explicit {@link android.content.ComponentName}
* composed of {@link #getPackageName} and the class name returned here.
- *
+ *
* <p>A null will be returned if there is no settings activity associated
- * with the input method.
+ * with the input method.</p>
*/
public String getSettingsActivity() {
return mSettingsActivityName;
@@ -374,7 +375,7 @@ public final class InputMethodInfo implements Parcelable {
* Return the count of the subtypes of Input Method.
*/
public int getSubtypeCount() {
- return mSubtypes.size();
+ return mSubtypes.getCount();
}
/**
@@ -419,7 +420,7 @@ public final class InputMethodInfo implements Parcelable {
pw.println(prefix + "Service:");
mService.dump(pw, prefix + " ");
}
-
+
@Override
public String toString() {
return "InputMethodInfo{" + mId
@@ -430,7 +431,7 @@ public final class InputMethodInfo implements Parcelable {
/**
* Used to test whether the given parameter object is an
* {@link InputMethodInfo} and its Id is the same to this one.
- *
+ *
* @return true if the given parameter object is an
* {@link InputMethodInfo} and its Id is the same to this one.
*/
@@ -467,7 +468,7 @@ public final class InputMethodInfo implements Parcelable {
/**
* Used to package this object into a {@link Parcel}.
- *
+ *
* @param dest The {@link Parcel} to be written.
* @param flags The flags used for parceling.
*/
@@ -479,7 +480,7 @@ public final class InputMethodInfo implements Parcelable {
dest.writeInt(mIsAuxIme ? 1 : 0);
dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0);
mService.writeToParcel(dest, flags);
- dest.writeTypedList(mSubtypes);
+ mSubtypes.writeToParcel(dest);
}
/**
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 53f7c79..e812edd 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1394,6 +1394,14 @@ public final class InputMethodManager {
/**
* Report the current selection range.
+ *
+ * <p><strong>Editor authors</strong>, you need to call this method whenever
+ * the cursor moves in your editor. Remember that in addition to doing this, your
+ * editor needs to always supply current cursor values in
+ * {@link EditorInfo#initialSelStart} and {@link EditorInfo#initialSelEnd} every
+ * time {@link android.view.View#onCreateInputConnection(EditorInfo)} is
+ * called, which happens whenever the keyboard shows up or the focus changes
+ * to a text field, among other cases.</p>
*/
public void updateSelection(View view, int selStart, int selEnd,
int candidatesStart, int candidatesEnd) {
@@ -1805,6 +1813,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 +1862,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/view/inputmethod/InputMethodSubtypeArray.java b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
new file mode 100644
index 0000000..5bef71f
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2007-2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.view.inputmethod;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AndroidRuntimeException;
+import android.util.Slog;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * An array-like container that stores multiple instances of {@link InputMethodSubtype}.
+ *
+ * <p>This container is designed to reduce the risk of {@link TransactionTooLargeException}
+ * when one or more instancess of {@link InputMethodInfo} are transferred through IPC.
+ * Basically this class does following three tasks.</p>
+ * <ul>
+ * <li>Applying compression for the marshalled data</li>
+ * <li>Lazily unmarshalling objects</li>
+ * <li>Caching the marshalled data when appropriate</li>
+ * </ul>
+ *
+ * @hide
+ */
+public class InputMethodSubtypeArray {
+ private final static String TAG = "InputMethodSubtypeArray";
+
+ /**
+ * Create a new instance of {@link InputMethodSubtypeArray} from an existing list of
+ * {@link InputMethodSubtype}.
+ *
+ * @param subtypes A list of {@link InputMethodSubtype} from which
+ * {@link InputMethodSubtypeArray} will be created.
+ */
+ public InputMethodSubtypeArray(final List<InputMethodSubtype> subtypes) {
+ if (subtypes == null) {
+ mCount = 0;
+ return;
+ }
+ mCount = subtypes.size();
+ mInstance = subtypes.toArray(new InputMethodSubtype[mCount]);
+ }
+
+ /**
+ * Unmarshall an instance of {@link InputMethodSubtypeArray} from a given {@link Parcel}
+ * object.
+ *
+ * @param source A {@link Parcel} object from which {@link InputMethodSubtypeArray} will be
+ * unmarshalled.
+ */
+ public InputMethodSubtypeArray(final Parcel source) {
+ mCount = source.readInt();
+ if (mCount > 0) {
+ mDecompressedSize = source.readInt();
+ mCompressedData = source.createByteArray();
+ }
+ }
+
+ /**
+ * Marshall the instance into a given {@link Parcel} object.
+ *
+ * <p>This methods may take a bit additional time to compress data lazily when called
+ * first time.</p>
+ *
+ * @param source A {@link Parcel} object to which {@link InputMethodSubtypeArray} will be
+ * marshalled.
+ */
+ public void writeToParcel(final Parcel dest) {
+ if (mCount == 0) {
+ dest.writeInt(mCount);
+ return;
+ }
+
+ byte[] compressedData = mCompressedData;
+ int decompressedSize = mDecompressedSize;
+ if (compressedData == null && decompressedSize == 0) {
+ synchronized (mLockObject) {
+ compressedData = mCompressedData;
+ decompressedSize = mDecompressedSize;
+ if (compressedData == null && decompressedSize == 0) {
+ final byte[] decompressedData = marshall(mInstance);
+ compressedData = compress(decompressedData);
+ if (compressedData == null) {
+ decompressedSize = -1;
+ Slog.i(TAG, "Failed to compress data.");
+ } else {
+ decompressedSize = decompressedData.length;
+ }
+ mDecompressedSize = decompressedSize;
+ mCompressedData = compressedData;
+ }
+ }
+ }
+
+ if (compressedData != null && decompressedSize > 0) {
+ dest.writeInt(mCount);
+ dest.writeInt(decompressedSize);
+ dest.writeByteArray(compressedData);
+ } else {
+ Slog.i(TAG, "Unexpected state. Behaving as an empty array.");
+ dest.writeInt(0);
+ }
+ }
+
+ /**
+ * Return {@link InputMethodSubtype} specified with the given index.
+ *
+ * <p>This methods may take a bit additional time to decompress data lazily when called
+ * first time.</p>
+ *
+ * @param index The index of {@link InputMethodSubtype}.
+ */
+ public InputMethodSubtype get(final int index) {
+ if (index < 0 || mCount <= index) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ InputMethodSubtype[] instance = mInstance;
+ if (instance == null) {
+ synchronized (mLockObject) {
+ instance = mInstance;
+ if (instance == null) {
+ final byte[] decompressedData =
+ decompress(mCompressedData, mDecompressedSize);
+ // Clear the compressed data until {@link #getMarshalled()} is called.
+ mCompressedData = null;
+ mDecompressedSize = 0;
+ if (decompressedData != null) {
+ instance = unmarshall(decompressedData);
+ } else {
+ Slog.e(TAG, "Failed to decompress data. Returns null as fallback.");
+ instance = new InputMethodSubtype[mCount];
+ }
+ mInstance = instance;
+ }
+ }
+ }
+ return instance[index];
+ }
+
+ /**
+ * Return the number of {@link InputMethodSubtype} objects.
+ */
+ public int getCount() {
+ return mCount;
+ }
+
+ private final Object mLockObject = new Object();
+ private final int mCount;
+
+ private volatile InputMethodSubtype[] mInstance;
+ private volatile byte[] mCompressedData;
+ private volatile int mDecompressedSize;
+
+ private static byte[] marshall(final InputMethodSubtype[] array) {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ parcel.writeTypedArray(array, 0);
+ return parcel.marshall();
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ parcel = null;
+ }
+ }
+ }
+
+ private static InputMethodSubtype[] unmarshall(final byte[] data) {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ parcel.unmarshall(data, 0, data.length);
+ parcel.setDataPosition(0);
+ return parcel.createTypedArray(InputMethodSubtype.CREATOR);
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ parcel = null;
+ }
+ }
+ }
+
+ private static byte[] compress(final byte[] data) {
+ ByteArrayOutputStream resultStream = null;
+ GZIPOutputStream zipper = null;
+ try {
+ resultStream = new ByteArrayOutputStream();
+ zipper = new GZIPOutputStream(resultStream);
+ zipper.write(data);
+ } catch(IOException e) {
+ return null;
+ } finally {
+ try {
+ if (zipper != null) {
+ zipper.close();
+ }
+ } catch (IOException e) {
+ zipper = null;
+ Slog.e(TAG, "Failed to close the stream.", e);
+ // swallowed, not propagated back to the caller
+ }
+ try {
+ if (resultStream != null) {
+ resultStream.close();
+ }
+ } catch (IOException e) {
+ resultStream = null;
+ Slog.e(TAG, "Failed to close the stream.", e);
+ // swallowed, not propagated back to the caller
+ }
+ }
+ return resultStream != null ? resultStream.toByteArray() : null;
+ }
+
+ private static byte[] decompress(final byte[] data, final int expectedSize) {
+ ByteArrayInputStream inputStream = null;
+ GZIPInputStream unzipper = null;
+ try {
+ inputStream = new ByteArrayInputStream(data);
+ unzipper = new GZIPInputStream(inputStream);
+ final byte [] result = new byte[expectedSize];
+ int totalReadBytes = 0;
+ while (totalReadBytes < result.length) {
+ final int restBytes = result.length - totalReadBytes;
+ final int readBytes = unzipper.read(result, totalReadBytes, restBytes);
+ if (readBytes < 0) {
+ break;
+ }
+ totalReadBytes += readBytes;
+ }
+ if (expectedSize != totalReadBytes) {
+ return null;
+ }
+ return result;
+ } catch(IOException e) {
+ return null;
+ } finally {
+ try {
+ if (unzipper != null) {
+ unzipper.close();
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to close the stream.", e);
+ // swallowed, not propagated back to the caller
+ }
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to close the stream.", e);
+ // swallowed, not propagated back to the caller
+ }
+ }
+ }
+}
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/DebugFlags.java b/core/java/android/webkit/DebugFlags.java
index b5ca8c1..7b3cb1b 100644
--- a/core/java/android/webkit/DebugFlags.java
+++ b/core/java/android/webkit/DebugFlags.java
@@ -31,7 +31,6 @@ public class DebugFlags {
public static final boolean COOKIE_SYNC_MANAGER = false;
public static final boolean TRACE_API = false;
public static final boolean TRACE_CALLBACK = false;
- public static final boolean TRACE_JAVASCRIPT_BRIDGE = false;
public static final boolean URL_UTIL = false;
public static final boolean WEB_SYNC_MANAGER = false;
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 d53bb74..81d36a4 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");
}
@@ -790,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);
}
@@ -807,8 +845,8 @@ public class WebView extends AbsoluteLayout
/**
* Loads the URL with postData using "POST" method into this WebView. If url
- * is not a network URL, it will be loaded with {link
- * {@link #loadUrl(String)} instead.
+ * is not a network URL, it will be loaded with {@link #loadUrl(String)}
+ * instead, ignoring the postData param.
*
* @param url the URL of the resource to load
* @param postData the data will be passed to "POST" request, which must be
@@ -817,7 +855,11 @@ public class WebView extends AbsoluteLayout
public void postUrl(String url, byte[] postData) {
checkThread();
if (DebugFlags.TRACE_API) Log.d(LOGTAG, "postUrl=" + url);
- mProvider.postUrl(url, postData);
+ if (URLUtil.isNetworkUrl(url)) {
+ mProvider.postUrl(url, postData);
+ } else {
+ mProvider.loadUrl(url);
+ }
}
/**
@@ -2097,10 +2139,11 @@ public class WebView extends AbsoluteLayout
mProvider.getViewDelegate().onAttachedToWindow();
}
+ /** @hide */
@Override
- protected void onDetachedFromWindow() {
+ protected void onDetachedFromWindowInternal() {
mProvider.getViewDelegate().onDetachedFromWindow();
- super.onDetachedFromWindow();
+ super.onDetachedFromWindowInternal();
}
@Override
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/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 25a43a6..96a2ab5 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -36,6 +36,7 @@ import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.util.LongSparseArray;
+import android.util.MathUtils;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.StateSet;
@@ -59,6 +60,9 @@ import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.inputmethod.BaseInputConnection;
@@ -417,7 +421,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
/**
* Handles scrolling between positions within the list.
*/
- PositionScroller mPositionScroller;
+ AbsPositionScroller mPositionScroller;
/**
* The offset in pixels form the top of the AdapterView to the top
@@ -581,7 +585,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;
@@ -693,6 +703,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private SavedState mPendingSync;
/**
+ * Whether the view is in the process of detaching from its window.
+ */
+ private boolean mIsDetaching;
+
+ /**
* Interface definition for a callback to be invoked when the list or grid
* has been scrolled.
*/
@@ -773,14 +788,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 +828,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 +1260,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 +1324,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 +1343,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 +1366,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 +1446,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);
@@ -1460,6 +1496,21 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ int getSelectionModeForAccessibility() {
+ final int choiceMode = getChoiceMode();
+ switch (choiceMode) {
+ case CHOICE_MODE_NONE:
+ return CollectionInfo.SELECTION_MODE_NONE;
+ case CHOICE_MODE_SINGLE:
+ return CollectionInfo.SELECTION_MODE_SINGLE;
+ case CHOICE_MODE_MULTIPLE:
+ case CHOICE_MODE_MULTIPLE_MODAL:
+ return CollectionInfo.SELECTION_MODE_MULTIPLE;
+ default:
+ return CollectionInfo.SELECTION_MODE_NONE;
+ }
+ }
+
@Override
public boolean performAccessibilityAction(int action, Bundle arguments) {
if (super.performAccessibilityAction(action, arguments)) {
@@ -1576,7 +1627,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
private void useDefaultSelector() {
- setSelector(getResources().getDrawable(
+ setSelector(getContext().getDrawable(
com.android.internal.R.drawable.list_selector_background));
}
@@ -2090,8 +2141,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
// TODO: Move somewhere sane. This doesn't belong in onLayout().
- if (mFastScroller != null) {
- mFastScroller.onItemCountChanged(getChildCount(), mItemCount);
+ if (mFastScroll != null) {
+ mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
}
}
@@ -2121,6 +2172,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
protected void layoutChildren() {
}
+ /**
+ * @param focusedView view that holds accessibility focus
+ * @return direct child that contains accessibility focus, or null if no
+ * child contains accessibility focus
+ */
+ View getAccessibilityFocusedChild(View focusedView) {
+ 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;
@@ -2242,6 +2312,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// If we failed to re-bind the data, scrap the obtained view.
if (updatedView != transientView) {
+ setItemViewLayoutParams(updatedView, position);
mRecycler.addScrapView(updatedView, position);
}
}
@@ -2260,12 +2331,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
} else {
isScrap[0] = true;
- // Clear any system-managed transient state so that we can
- // recycle this view and bind it to different data.
- if (child.isAccessibilityFocused()) {
- child.clearAccessibilityFocus();
- }
-
child.dispatchFinishTemporaryDetach();
}
}
@@ -2278,19 +2343,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
- if (mAdapterHasStableIds) {
- final ViewGroup.LayoutParams vlp = child.getLayoutParams();
- LayoutParams lp;
- if (vlp == null) {
- lp = (LayoutParams) generateDefaultLayoutParams();
- } else if (!checkLayoutParams(vlp)) {
- lp = (LayoutParams) generateLayoutParams(vlp);
- } else {
- lp = (LayoutParams) vlp;
- }
- lp.itemId = mAdapter.getItemId(position);
- child.setLayoutParams(lp);
- }
+ setItemViewLayoutParams(child, position);
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
if (mAccessibilityDelegate == null) {
@@ -2306,6 +2359,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return child;
}
+ private void setItemViewLayoutParams(View child, int position) {
+ final ViewGroup.LayoutParams vlp = child.getLayoutParams();
+ LayoutParams lp;
+ if (vlp == null) {
+ lp = (LayoutParams) generateDefaultLayoutParams();
+ } else if (!checkLayoutParams(vlp)) {
+ lp = (LayoutParams) generateLayoutParams(vlp);
+ } else {
+ lp = (LayoutParams) vlp;
+ }
+
+ if (mAdapterHasStableIds) {
+ lp.itemId = mAdapter.getItemId(position);
+ }
+ lp.viewType = mAdapter.getItemViewType(position);
+ child.setLayoutParams(lp);
+ }
+
class ListItemAccessibilityDelegate extends AccessibilityDelegate {
@Override
public AccessibilityNodeInfo createAccessibilityNodeInfo(View host) {
@@ -2506,8 +2577,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);
}
}
@@ -2566,7 +2637,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* @attr ref android.R.styleable#AbsListView_listSelector
*/
public void setSelector(int resID) {
- setSelector(getResources().getDrawable(resID));
+ setSelector(getContext().getDrawable(resID));
}
public void setSelector(Drawable sel) {
@@ -2728,6 +2799,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
+ mIsDetaching = true;
+
// Dismiss the popup in case onSaveInstanceState() was not invoked
dismissPopup();
@@ -2776,6 +2849,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
removeCallbacks(mTouchModeReset);
mTouchModeReset.run();
}
+
+ mIsDetaching = false;
}
@Override
@@ -2836,8 +2911,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());
}
}
@@ -3402,7 +3477,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mPositionScroller.stop();
}
- if (!isAttachedToWindow()) {
+ if (mIsDetaching || !isAttachedToWindow()) {
// Something isn't right.
// Since we rely on being attached to get data set change notifications,
// don't risk doing anything where we might try to resync and find things
@@ -3410,8 +3485,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;
}
@@ -3641,7 +3716,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mTouchMode = TOUCH_MODE_REST;
child.setPressed(false);
setPressed(false);
- if (!mDataChanged && isAttachedToWindow()) {
+ if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
performClick.run();
}
}
@@ -3900,7 +3975,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;
}
@@ -3916,7 +3991,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mPositionScroller.stop();
}
- if (!isAttachedToWindow()) {
+ if (mIsDetaching || !isAttachedToWindow()) {
// Something isn't right.
// Since we rely on being attached to get data set change notifications,
// don't risk doing anything where we might try to resync and find things
@@ -3924,7 +3999,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;
}
@@ -4321,447 +4396,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
- class PositionScroller implements Runnable {
- private static final int SCROLL_DURATION = 200;
-
- private static final int MOVE_DOWN_POS = 1;
- private static final int MOVE_UP_POS = 2;
- private static final int MOVE_DOWN_BOUND = 3;
- private static final int MOVE_UP_BOUND = 4;
- private static final int MOVE_OFFSET = 5;
-
- private int mMode;
- private int mTargetPos;
- private int mBoundPos;
- private int mLastSeenPos;
- private int mScrollDuration;
- private final int mExtraScroll;
-
- private int mOffsetFromTop;
-
- PositionScroller() {
- mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
- }
-
- void start(final int position) {
- stop();
-
- if (mDataChanged) {
- // Wait until we're back in a stable state to try this.
- mPositionScrollAfterLayout = new Runnable() {
- @Override public void run() {
- start(position);
- }
- };
- return;
- }
-
- final int childCount = getChildCount();
- if (childCount == 0) {
- // Can't scroll without children.
- return;
- }
-
- final int firstPos = mFirstPosition;
- final int lastPos = firstPos + childCount - 1;
-
- int viewTravelCount;
- int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
- if (clampedPosition < firstPos) {
- viewTravelCount = firstPos - clampedPosition + 1;
- mMode = MOVE_UP_POS;
- } else if (clampedPosition > lastPos) {
- viewTravelCount = clampedPosition - lastPos + 1;
- mMode = MOVE_DOWN_POS;
- } else {
- scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
- return;
- }
-
- if (viewTravelCount > 0) {
- mScrollDuration = SCROLL_DURATION / viewTravelCount;
- } else {
- mScrollDuration = SCROLL_DURATION;
- }
- mTargetPos = clampedPosition;
- mBoundPos = INVALID_POSITION;
- mLastSeenPos = INVALID_POSITION;
-
- postOnAnimation(this);
- }
-
- void start(final int position, final int boundPosition) {
- stop();
-
- if (boundPosition == INVALID_POSITION) {
- start(position);
- return;
- }
-
- if (mDataChanged) {
- // Wait until we're back in a stable state to try this.
- mPositionScrollAfterLayout = new Runnable() {
- @Override public void run() {
- start(position, boundPosition);
- }
- };
- return;
- }
-
- final int childCount = getChildCount();
- if (childCount == 0) {
- // Can't scroll without children.
- return;
- }
-
- final int firstPos = mFirstPosition;
- final int lastPos = firstPos + childCount - 1;
-
- int viewTravelCount;
- int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
- if (clampedPosition < firstPos) {
- final int boundPosFromLast = lastPos - boundPosition;
- if (boundPosFromLast < 1) {
- // Moving would shift our bound position off the screen. Abort.
- return;
- }
-
- final int posTravel = firstPos - clampedPosition + 1;
- final int boundTravel = boundPosFromLast - 1;
- if (boundTravel < posTravel) {
- viewTravelCount = boundTravel;
- mMode = MOVE_UP_BOUND;
- } else {
- viewTravelCount = posTravel;
- mMode = MOVE_UP_POS;
- }
- } else if (clampedPosition > lastPos) {
- final int boundPosFromFirst = boundPosition - firstPos;
- if (boundPosFromFirst < 1) {
- // Moving would shift our bound position off the screen. Abort.
- return;
- }
-
- final int posTravel = clampedPosition - lastPos + 1;
- final int boundTravel = boundPosFromFirst - 1;
- if (boundTravel < posTravel) {
- viewTravelCount = boundTravel;
- mMode = MOVE_DOWN_BOUND;
- } else {
- viewTravelCount = posTravel;
- mMode = MOVE_DOWN_POS;
- }
- } else {
- scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
- return;
- }
-
- if (viewTravelCount > 0) {
- mScrollDuration = SCROLL_DURATION / viewTravelCount;
- } else {
- mScrollDuration = SCROLL_DURATION;
- }
- mTargetPos = clampedPosition;
- mBoundPos = boundPosition;
- mLastSeenPos = INVALID_POSITION;
-
- postOnAnimation(this);
- }
-
- void startWithOffset(int position, int offset) {
- startWithOffset(position, offset, SCROLL_DURATION);
- }
-
- void startWithOffset(final int position, int offset, final int duration) {
- stop();
-
- if (mDataChanged) {
- // Wait until we're back in a stable state to try this.
- final int postOffset = offset;
- mPositionScrollAfterLayout = new Runnable() {
- @Override public void run() {
- startWithOffset(position, postOffset, duration);
- }
- };
- return;
- }
-
- final int childCount = getChildCount();
- if (childCount == 0) {
- // Can't scroll without children.
- return;
- }
-
- offset += getPaddingTop();
-
- mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
- mOffsetFromTop = offset;
- mBoundPos = INVALID_POSITION;
- mLastSeenPos = INVALID_POSITION;
- mMode = MOVE_OFFSET;
-
- final int firstPos = mFirstPosition;
- final int lastPos = firstPos + childCount - 1;
-
- int viewTravelCount;
- if (mTargetPos < firstPos) {
- viewTravelCount = firstPos - mTargetPos;
- } else if (mTargetPos > lastPos) {
- viewTravelCount = mTargetPos - lastPos;
- } else {
- // On-screen, just scroll.
- final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
- smoothScrollBy(targetTop - offset, duration, true);
- return;
- }
-
- // Estimate how many screens we should travel
- final float screenTravelCount = (float) viewTravelCount / childCount;
- mScrollDuration = screenTravelCount < 1 ?
- duration : (int) (duration / screenTravelCount);
- mLastSeenPos = INVALID_POSITION;
-
- postOnAnimation(this);
- }
-
- /**
- * Scroll such that targetPos is in the visible padded region without scrolling
- * boundPos out of view. Assumes targetPos is onscreen.
- */
- void scrollToVisible(int targetPos, int boundPos, int duration) {
- final int firstPos = mFirstPosition;
- final int childCount = getChildCount();
- final int lastPos = firstPos + childCount - 1;
- final int paddedTop = mListPadding.top;
- final int paddedBottom = getHeight() - mListPadding.bottom;
-
- if (targetPos < firstPos || targetPos > lastPos) {
- Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
- " not visible [" + firstPos + ", " + lastPos + "]");
- }
- if (boundPos < firstPos || boundPos > lastPos) {
- // boundPos doesn't matter, it's already offscreen.
- boundPos = INVALID_POSITION;
- }
-
- final View targetChild = getChildAt(targetPos - firstPos);
- final int targetTop = targetChild.getTop();
- final int targetBottom = targetChild.getBottom();
- int scrollBy = 0;
-
- if (targetBottom > paddedBottom) {
- scrollBy = targetBottom - paddedBottom;
- }
- if (targetTop < paddedTop) {
- scrollBy = targetTop - paddedTop;
- }
-
- if (scrollBy == 0) {
- return;
- }
-
- if (boundPos >= 0) {
- final View boundChild = getChildAt(boundPos - firstPos);
- final int boundTop = boundChild.getTop();
- final int boundBottom = boundChild.getBottom();
- final int absScroll = Math.abs(scrollBy);
-
- if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
- // Don't scroll the bound view off the bottom of the screen.
- scrollBy = Math.max(0, boundBottom - paddedBottom);
- } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
- // Don't scroll the bound view off the top of the screen.
- scrollBy = Math.min(0, boundTop - paddedTop);
- }
- }
-
- smoothScrollBy(scrollBy, duration);
- }
-
- void stop() {
- removeCallbacks(this);
- }
-
- @Override
- public void run() {
- final int listHeight = getHeight();
- final int firstPos = mFirstPosition;
-
- switch (mMode) {
- case MOVE_DOWN_POS: {
- final int lastViewIndex = getChildCount() - 1;
- final int lastPos = firstPos + lastViewIndex;
-
- if (lastViewIndex < 0) {
- return;
- }
-
- if (lastPos == mLastSeenPos) {
- // No new views, let things keep going.
- postOnAnimation(this);
- return;
- }
-
- final View lastView = getChildAt(lastViewIndex);
- final int lastViewHeight = lastView.getHeight();
- final int lastViewTop = lastView.getTop();
- final int lastViewPixelsShowing = listHeight - lastViewTop;
- final int extraScroll = lastPos < mItemCount - 1 ?
- Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
-
- final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
- smoothScrollBy(scrollBy, mScrollDuration, true);
-
- mLastSeenPos = lastPos;
- if (lastPos < mTargetPos) {
- postOnAnimation(this);
- }
- break;
- }
-
- case MOVE_DOWN_BOUND: {
- final int nextViewIndex = 1;
- final int childCount = getChildCount();
-
- if (firstPos == mBoundPos || childCount <= nextViewIndex
- || firstPos + childCount >= mItemCount) {
- return;
- }
- final int nextPos = firstPos + nextViewIndex;
-
- if (nextPos == mLastSeenPos) {
- // No new views, let things keep going.
- postOnAnimation(this);
- return;
- }
-
- final View nextView = getChildAt(nextViewIndex);
- final int nextViewHeight = nextView.getHeight();
- final int nextViewTop = nextView.getTop();
- final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
- if (nextPos < mBoundPos) {
- smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
- mScrollDuration, true);
-
- mLastSeenPos = nextPos;
-
- postOnAnimation(this);
- } else {
- if (nextViewTop > extraScroll) {
- smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
- }
- }
- break;
- }
-
- case MOVE_UP_POS: {
- if (firstPos == mLastSeenPos) {
- // No new views, let things keep going.
- postOnAnimation(this);
- return;
- }
-
- final View firstView = getChildAt(0);
- if (firstView == null) {
- return;
- }
- final int firstViewTop = firstView.getTop();
- final int extraScroll = firstPos > 0 ?
- Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
-
- smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
-
- mLastSeenPos = firstPos;
-
- if (firstPos > mTargetPos) {
- postOnAnimation(this);
- }
- break;
- }
-
- case MOVE_UP_BOUND: {
- final int lastViewIndex = getChildCount() - 2;
- if (lastViewIndex < 0) {
- return;
- }
- final int lastPos = firstPos + lastViewIndex;
-
- if (lastPos == mLastSeenPos) {
- // No new views, let things keep going.
- postOnAnimation(this);
- return;
- }
-
- final View lastView = getChildAt(lastViewIndex);
- final int lastViewHeight = lastView.getHeight();
- final int lastViewTop = lastView.getTop();
- final int lastViewPixelsShowing = listHeight - lastViewTop;
- final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
- mLastSeenPos = lastPos;
- if (lastPos > mBoundPos) {
- smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
- postOnAnimation(this);
- } else {
- final int bottom = listHeight - extraScroll;
- final int lastViewBottom = lastViewTop + lastViewHeight;
- if (bottom > lastViewBottom) {
- smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
- }
- }
- break;
- }
-
- case MOVE_OFFSET: {
- if (mLastSeenPos == firstPos) {
- // No new views, let things keep going.
- postOnAnimation(this);
- return;
- }
-
- mLastSeenPos = firstPos;
-
- final int childCount = getChildCount();
- final int position = mTargetPos;
- final int lastPos = firstPos + childCount - 1;
-
- int viewTravelCount = 0;
- if (position < firstPos) {
- viewTravelCount = firstPos - position + 1;
- } else if (position > lastPos) {
- viewTravelCount = position - lastPos;
- }
-
- // Estimate how many screens we should travel
- final float screenTravelCount = (float) viewTravelCount / childCount;
-
- final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
- if (position < firstPos) {
- final int distance = (int) (-getHeight() * modifier);
- final int duration = (int) (mScrollDuration * modifier);
- smoothScrollBy(distance, duration, true);
- postOnAnimation(this);
- } else if (position > lastPos) {
- final int distance = (int) (getHeight() * modifier);
- final int duration = (int) (mScrollDuration * modifier);
- smoothScrollBy(distance, duration, true);
- postOnAnimation(this);
- } else {
- // On-screen, just scroll.
- final int targetTop = getChildAt(position - firstPos).getTop();
- final int distance = targetTop - mOffsetFromTop;
- final int duration = (int) (mScrollDuration *
- ((float) Math.abs(distance) / getHeight()));
- smoothScrollBy(distance, duration, true);
- }
- break;
- }
-
- default:
- break;
- }
- }
- }
-
/**
* The amount of friction applied to flings. The default value
* is {@link ViewConfiguration#getScrollFriction}.
@@ -4784,20 +4418,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
/**
+ * Override this for better control over position scrolling.
+ */
+ AbsPositionScroller createPositionScroller() {
+ return new PositionScroller();
+ }
+
+ /**
* Smoothly scroll to the specified adapter position. The view will
* scroll such that the indicated position is displayed.
* @param position Scroll to this adapter position.
*/
public void smoothScrollToPosition(int position) {
if (mPositionScroller == null) {
- mPositionScroller = new PositionScroller();
+ mPositionScroller = createPositionScroller();
}
mPositionScroller.start(position);
}
/**
* Smoothly scroll to the specified adapter position. The view will scroll
- * such that the indicated position is displayed <code>offset</code> pixels from
+ * such that the indicated position is displayed <code>offset</code> pixels below
* the top edge of the view. If this is impossible, (e.g. the offset would scroll
* the first or last item beyond the boundaries of the list) it will get as close
* as possible. The scroll will take <code>duration</code> milliseconds to complete.
@@ -4809,14 +4450,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
public void smoothScrollToPositionFromTop(int position, int offset, int duration) {
if (mPositionScroller == null) {
- mPositionScroller = new PositionScroller();
+ mPositionScroller = createPositionScroller();
}
mPositionScroller.startWithOffset(position, offset, duration);
}
/**
* Smoothly scroll to the specified adapter position. The view will scroll
- * such that the indicated position is displayed <code>offset</code> pixels from
+ * such that the indicated position is displayed <code>offset</code> pixels below
* the top edge of the view. If this is impossible, (e.g. the offset would scroll
* the first or last item beyond the boundaries of the list) it will get as close
* as possible.
@@ -4827,9 +4468,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
public void smoothScrollToPositionFromTop(int position, int offset) {
if (mPositionScroller == null) {
- mPositionScroller = new PositionScroller();
+ mPositionScroller = createPositionScroller();
}
- mPositionScroller.startWithOffset(position, offset);
+ mPositionScroller.startWithOffset(position, offset, offset);
}
/**
@@ -4837,13 +4478,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* scroll such that the indicated position is displayed, but it will
* stop early if scrolling further would scroll boundPosition out of
* view.
+ *
* @param position Scroll to this adapter position.
* @param boundPosition Do not scroll if it would move this adapter
* position out of view.
*/
public void smoothScrollToPosition(int position, int boundPosition) {
if (mPositionScroller == null) {
- mPositionScroller = new PositionScroller();
+ mPositionScroller = createPositionScroller();
}
mPositionScroller.start(position, boundPosition);
}
@@ -6285,16 +5927,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();
}
}
}
@@ -6555,18 +6197,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
void clear() {
if (mViewTypeCount == 1) {
final ArrayList<View> scrap = mCurrentScrap;
- final int scrapCount = scrap.size();
- for (int i = 0; i < scrapCount; i++) {
- removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
- }
+ clearScrap(scrap);
} else {
final int typeCount = mViewTypeCount;
for (int i = 0; i < typeCount; i++) {
final ArrayList<View> scrap = mScrapViews[i];
- final int scrapCount = scrap.size();
- for (int j = 0; j < scrapCount; j++) {
- removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
- }
+ clearScrap(scrap);
}
}
@@ -6667,7 +6303,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
} else {
- int whichScrap = mAdapter.getItemViewType(position);
+ final int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
@@ -6739,13 +6375,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mScrapViews[viewType].add(scrap);
}
- // Clear any system-managed transient state.
- if (scrap.isAccessibilityFocused()) {
- scrap.clearAccessibilityFocus();
- }
-
- scrap.setAccessibilityDelegate(null);
-
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
@@ -6819,7 +6448,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
lp.scrappedFromPosition = mFirstActivePosition + i;
scrapViews.add(victim);
- victim.setAccessibilityDelegate(null);
if (hasListener) {
mRecyclerListener.onMovedToScrapHeap(victim);
}
@@ -6923,23 +6551,861 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
}
- }
- static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
- int size = scrapViews.size();
- if (size > 0) {
- // See if we still have a view for this position.
- for (int i=0; i<size; i++) {
- View view = scrapViews.get(i);
- if (((AbsListView.LayoutParams)view.getLayoutParams())
- .scrappedFromPosition == position) {
- scrapViews.remove(i);
- return view;
+ private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
+ final int size = scrapViews.size();
+ if (size > 0) {
+ // See if we still have a view for this position or ID.
+ for (int i = 0; i < size; i++) {
+ final View view = scrapViews.get(i);
+ final AbsListView.LayoutParams params =
+ (AbsListView.LayoutParams) view.getLayoutParams();
+
+ if (mAdapterHasStableIds) {
+ final long id = mAdapter.getItemId(position);
+ if (id == params.itemId) {
+ return scrapViews.remove(i);
+ }
+ } else if (params.scrappedFromPosition == position) {
+ final View scrap = scrapViews.remove(i);
+ clearAccessibilityFromScrap(scrap);
+ return scrap;
+ }
}
+ final View scrap = scrapViews.remove(size - 1);
+ clearAccessibilityFromScrap(scrap);
+ return scrap;
+ } else {
+ return null;
}
- return scrapViews.remove(size - 1);
+ }
+
+ private void clearScrap(final ArrayList<View> scrap) {
+ final int scrapCount = scrap.size();
+ for (int j = 0; j < scrapCount; j++) {
+ removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
+ }
+ }
+
+ private void clearAccessibilityFromScrap(View view) {
+ if (view.isAccessibilityFocused()) {
+ view.clearAccessibilityFocus();
+ }
+ view.setAccessibilityDelegate(null);
+ }
+
+ private void removeDetachedView(View child, boolean animate) {
+ child.setAccessibilityDelegate(null);
+ AbsListView.this.removeDetachedView(child, animate);
+ }
+ }
+
+ /**
+ * Returns the height of the view for the specified position.
+ *
+ * @param position the item position
+ * @return view height in pixels
+ */
+ int getHeightForPosition(int position) {
+ final int firstVisiblePosition = getFirstVisiblePosition();
+ final int childCount = getChildCount();
+ final int index = position - firstVisiblePosition;
+ if (index >= 0 && index < childCount) {
+ // Position is on-screen, use existing view.
+ final View view = getChildAt(index);
+ return view.getHeight();
} else {
- return null;
+ // Position is off-screen, obtain & recycle view.
+ final View view = obtainView(position, mIsScrap);
+ view.measure(mWidthMeasureSpec, MeasureSpec.UNSPECIFIED);
+ final int height = view.getMeasuredHeight();
+ mRecycler.addScrapView(view, position);
+ return height;
+ }
+ }
+
+ /**
+ * Sets the selected item and positions the selection y pixels from the top edge
+ * of the ListView. (If in touch mode, the item will not be selected but it will
+ * still be positioned appropriately.)
+ *
+ * @param position Index (starting at 0) of the data item to be selected.
+ * @param y The distance from the top edge of the ListView (plus padding) that the
+ * item will be positioned.
+ */
+ public void setSelectionFromTop(int position, int y) {
+ if (mAdapter == null) {
+ return;
+ }
+
+ if (!isInTouchMode()) {
+ position = lookForSelectablePosition(position, true);
+ if (position >= 0) {
+ setNextSelectedPositionInt(position);
+ }
+ } else {
+ mResurrectToPosition = position;
+ }
+
+ if (position >= 0) {
+ mLayoutMode = LAYOUT_SPECIFIC;
+ mSpecificTop = mListPadding.top + y;
+
+ if (mNeedSync) {
+ mSyncPosition = position;
+ mSyncRowId = mAdapter.getItemId(position);
+ }
+
+ if (mPositionScroller != null) {
+ mPositionScroller.stop();
+ }
+ requestLayout();
+ }
+ }
+
+ /**
+ * Abstract positon scroller used to handle smooth scrolling.
+ */
+ static abstract class AbsPositionScroller {
+ public abstract void start(int position);
+ public abstract void start(int position, int boundPosition);
+ public abstract void startWithOffset(int position, int offset);
+ public abstract void startWithOffset(int position, int offset, int duration);
+ public abstract void stop();
+ }
+
+ /**
+ * Default position scroller that simulates a fling.
+ */
+ class PositionScroller extends AbsPositionScroller implements Runnable {
+ private static final int SCROLL_DURATION = 200;
+
+ private static final int MOVE_DOWN_POS = 1;
+ private static final int MOVE_UP_POS = 2;
+ private static final int MOVE_DOWN_BOUND = 3;
+ private static final int MOVE_UP_BOUND = 4;
+ private static final int MOVE_OFFSET = 5;
+
+ private int mMode;
+ private int mTargetPos;
+ private int mBoundPos;
+ private int mLastSeenPos;
+ private int mScrollDuration;
+ private final int mExtraScroll;
+
+ private int mOffsetFromTop;
+
+ PositionScroller() {
+ mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
+ }
+
+ @Override
+ public void start(final int position) {
+ stop();
+
+ if (mDataChanged) {
+ // Wait until we're back in a stable state to try this.
+ mPositionScrollAfterLayout = new Runnable() {
+ @Override public void run() {
+ start(position);
+ }
+ };
+ return;
+ }
+
+ final int childCount = getChildCount();
+ if (childCount == 0) {
+ // Can't scroll without children.
+ return;
+ }
+
+ final int firstPos = mFirstPosition;
+ final int lastPos = firstPos + childCount - 1;
+
+ int viewTravelCount;
+ int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
+ if (clampedPosition < firstPos) {
+ viewTravelCount = firstPos - clampedPosition + 1;
+ mMode = MOVE_UP_POS;
+ } else if (clampedPosition > lastPos) {
+ viewTravelCount = clampedPosition - lastPos + 1;
+ mMode = MOVE_DOWN_POS;
+ } else {
+ scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
+ return;
+ }
+
+ if (viewTravelCount > 0) {
+ mScrollDuration = SCROLL_DURATION / viewTravelCount;
+ } else {
+ mScrollDuration = SCROLL_DURATION;
+ }
+ mTargetPos = clampedPosition;
+ mBoundPos = INVALID_POSITION;
+ mLastSeenPos = INVALID_POSITION;
+
+ postOnAnimation(this);
+ }
+
+ @Override
+ public void start(final int position, final int boundPosition) {
+ stop();
+
+ if (boundPosition == INVALID_POSITION) {
+ start(position);
+ return;
+ }
+
+ if (mDataChanged) {
+ // Wait until we're back in a stable state to try this.
+ mPositionScrollAfterLayout = new Runnable() {
+ @Override public void run() {
+ start(position, boundPosition);
+ }
+ };
+ return;
+ }
+
+ final int childCount = getChildCount();
+ if (childCount == 0) {
+ // Can't scroll without children.
+ return;
+ }
+
+ final int firstPos = mFirstPosition;
+ final int lastPos = firstPos + childCount - 1;
+
+ int viewTravelCount;
+ int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
+ if (clampedPosition < firstPos) {
+ final int boundPosFromLast = lastPos - boundPosition;
+ if (boundPosFromLast < 1) {
+ // Moving would shift our bound position off the screen. Abort.
+ return;
+ }
+
+ final int posTravel = firstPos - clampedPosition + 1;
+ final int boundTravel = boundPosFromLast - 1;
+ if (boundTravel < posTravel) {
+ viewTravelCount = boundTravel;
+ mMode = MOVE_UP_BOUND;
+ } else {
+ viewTravelCount = posTravel;
+ mMode = MOVE_UP_POS;
+ }
+ } else if (clampedPosition > lastPos) {
+ final int boundPosFromFirst = boundPosition - firstPos;
+ if (boundPosFromFirst < 1) {
+ // Moving would shift our bound position off the screen. Abort.
+ return;
+ }
+
+ final int posTravel = clampedPosition - lastPos + 1;
+ final int boundTravel = boundPosFromFirst - 1;
+ if (boundTravel < posTravel) {
+ viewTravelCount = boundTravel;
+ mMode = MOVE_DOWN_BOUND;
+ } else {
+ viewTravelCount = posTravel;
+ mMode = MOVE_DOWN_POS;
+ }
+ } else {
+ scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
+ return;
+ }
+
+ if (viewTravelCount > 0) {
+ mScrollDuration = SCROLL_DURATION / viewTravelCount;
+ } else {
+ mScrollDuration = SCROLL_DURATION;
+ }
+ mTargetPos = clampedPosition;
+ mBoundPos = boundPosition;
+ mLastSeenPos = INVALID_POSITION;
+
+ postOnAnimation(this);
+ }
+
+ @Override
+ public void startWithOffset(int position, int offset) {
+ startWithOffset(position, offset, SCROLL_DURATION);
+ }
+
+ @Override
+ public void startWithOffset(final int position, int offset, final int duration) {
+ stop();
+
+ if (mDataChanged) {
+ // Wait until we're back in a stable state to try this.
+ final int postOffset = offset;
+ mPositionScrollAfterLayout = new Runnable() {
+ @Override public void run() {
+ startWithOffset(position, postOffset, duration);
+ }
+ };
+ return;
+ }
+
+ final int childCount = getChildCount();
+ if (childCount == 0) {
+ // Can't scroll without children.
+ return;
+ }
+
+ offset += getPaddingTop();
+
+ mTargetPos = Math.max(0, Math.min(getCount() - 1, position));
+ mOffsetFromTop = offset;
+ mBoundPos = INVALID_POSITION;
+ mLastSeenPos = INVALID_POSITION;
+ mMode = MOVE_OFFSET;
+
+ final int firstPos = mFirstPosition;
+ final int lastPos = firstPos + childCount - 1;
+
+ int viewTravelCount;
+ if (mTargetPos < firstPos) {
+ viewTravelCount = firstPos - mTargetPos;
+ } else if (mTargetPos > lastPos) {
+ viewTravelCount = mTargetPos - lastPos;
+ } else {
+ // On-screen, just scroll.
+ final int targetTop = getChildAt(mTargetPos - firstPos).getTop();
+ smoothScrollBy(targetTop - offset, duration, true);
+ return;
+ }
+
+ // Estimate how many screens we should travel
+ final float screenTravelCount = (float) viewTravelCount / childCount;
+ mScrollDuration = screenTravelCount < 1 ?
+ duration : (int) (duration / screenTravelCount);
+ mLastSeenPos = INVALID_POSITION;
+
+ postOnAnimation(this);
+ }
+
+ /**
+ * Scroll such that targetPos is in the visible padded region without scrolling
+ * boundPos out of view. Assumes targetPos is onscreen.
+ */
+ private void scrollToVisible(int targetPos, int boundPos, int duration) {
+ final int firstPos = mFirstPosition;
+ final int childCount = getChildCount();
+ final int lastPos = firstPos + childCount - 1;
+ final int paddedTop = mListPadding.top;
+ final int paddedBottom = getHeight() - mListPadding.bottom;
+
+ if (targetPos < firstPos || targetPos > lastPos) {
+ Log.w(TAG, "scrollToVisible called with targetPos " + targetPos +
+ " not visible [" + firstPos + ", " + lastPos + "]");
+ }
+ if (boundPos < firstPos || boundPos > lastPos) {
+ // boundPos doesn't matter, it's already offscreen.
+ boundPos = INVALID_POSITION;
+ }
+
+ final View targetChild = getChildAt(targetPos - firstPos);
+ final int targetTop = targetChild.getTop();
+ final int targetBottom = targetChild.getBottom();
+ int scrollBy = 0;
+
+ if (targetBottom > paddedBottom) {
+ scrollBy = targetBottom - paddedBottom;
+ }
+ if (targetTop < paddedTop) {
+ scrollBy = targetTop - paddedTop;
+ }
+
+ if (scrollBy == 0) {
+ return;
+ }
+
+ if (boundPos >= 0) {
+ final View boundChild = getChildAt(boundPos - firstPos);
+ final int boundTop = boundChild.getTop();
+ final int boundBottom = boundChild.getBottom();
+ final int absScroll = Math.abs(scrollBy);
+
+ if (scrollBy < 0 && boundBottom + absScroll > paddedBottom) {
+ // Don't scroll the bound view off the bottom of the screen.
+ scrollBy = Math.max(0, boundBottom - paddedBottom);
+ } else if (scrollBy > 0 && boundTop - absScroll < paddedTop) {
+ // Don't scroll the bound view off the top of the screen.
+ scrollBy = Math.min(0, boundTop - paddedTop);
+ }
+ }
+
+ smoothScrollBy(scrollBy, duration);
+ }
+
+ @Override
+ public void stop() {
+ removeCallbacks(this);
+ }
+
+ @Override
+ public void run() {
+ final int listHeight = getHeight();
+ final int firstPos = mFirstPosition;
+
+ switch (mMode) {
+ case MOVE_DOWN_POS: {
+ final int lastViewIndex = getChildCount() - 1;
+ final int lastPos = firstPos + lastViewIndex;
+
+ if (lastViewIndex < 0) {
+ return;
+ }
+
+ if (lastPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ postOnAnimation(this);
+ return;
+ }
+
+ final View lastView = getChildAt(lastViewIndex);
+ final int lastViewHeight = lastView.getHeight();
+ final int lastViewTop = lastView.getTop();
+ final int lastViewPixelsShowing = listHeight - lastViewTop;
+ final int extraScroll = lastPos < mItemCount - 1 ?
+ Math.max(mListPadding.bottom, mExtraScroll) : mListPadding.bottom;
+
+ final int scrollBy = lastViewHeight - lastViewPixelsShowing + extraScroll;
+ smoothScrollBy(scrollBy, mScrollDuration, true);
+
+ mLastSeenPos = lastPos;
+ if (lastPos < mTargetPos) {
+ postOnAnimation(this);
+ }
+ break;
+ }
+
+ case MOVE_DOWN_BOUND: {
+ final int nextViewIndex = 1;
+ final int childCount = getChildCount();
+
+ if (firstPos == mBoundPos || childCount <= nextViewIndex
+ || firstPos + childCount >= mItemCount) {
+ return;
+ }
+ final int nextPos = firstPos + nextViewIndex;
+
+ if (nextPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ postOnAnimation(this);
+ return;
+ }
+
+ final View nextView = getChildAt(nextViewIndex);
+ final int nextViewHeight = nextView.getHeight();
+ final int nextViewTop = nextView.getTop();
+ final int extraScroll = Math.max(mListPadding.bottom, mExtraScroll);
+ if (nextPos < mBoundPos) {
+ smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
+ mScrollDuration, true);
+
+ mLastSeenPos = nextPos;
+
+ postOnAnimation(this);
+ } else {
+ if (nextViewTop > extraScroll) {
+ smoothScrollBy(nextViewTop - extraScroll, mScrollDuration, true);
+ }
+ }
+ break;
+ }
+
+ case MOVE_UP_POS: {
+ if (firstPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ postOnAnimation(this);
+ return;
+ }
+
+ final View firstView = getChildAt(0);
+ if (firstView == null) {
+ return;
+ }
+ final int firstViewTop = firstView.getTop();
+ final int extraScroll = firstPos > 0 ?
+ Math.max(mExtraScroll, mListPadding.top) : mListPadding.top;
+
+ smoothScrollBy(firstViewTop - extraScroll, mScrollDuration, true);
+
+ mLastSeenPos = firstPos;
+
+ if (firstPos > mTargetPos) {
+ postOnAnimation(this);
+ }
+ break;
+ }
+
+ case MOVE_UP_BOUND: {
+ final int lastViewIndex = getChildCount() - 2;
+ if (lastViewIndex < 0) {
+ return;
+ }
+ final int lastPos = firstPos + lastViewIndex;
+
+ if (lastPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ postOnAnimation(this);
+ return;
+ }
+
+ final View lastView = getChildAt(lastViewIndex);
+ final int lastViewHeight = lastView.getHeight();
+ final int lastViewTop = lastView.getTop();
+ final int lastViewPixelsShowing = listHeight - lastViewTop;
+ final int extraScroll = Math.max(mListPadding.top, mExtraScroll);
+ mLastSeenPos = lastPos;
+ if (lastPos > mBoundPos) {
+ smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration, true);
+ postOnAnimation(this);
+ } else {
+ final int bottom = listHeight - extraScroll;
+ final int lastViewBottom = lastViewTop + lastViewHeight;
+ if (bottom > lastViewBottom) {
+ smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration, true);
+ }
+ }
+ break;
+ }
+
+ case MOVE_OFFSET: {
+ if (mLastSeenPos == firstPos) {
+ // No new views, let things keep going.
+ postOnAnimation(this);
+ return;
+ }
+
+ mLastSeenPos = firstPos;
+
+ final int childCount = getChildCount();
+ final int position = mTargetPos;
+ final int lastPos = firstPos + childCount - 1;
+
+ int viewTravelCount = 0;
+ if (position < firstPos) {
+ viewTravelCount = firstPos - position + 1;
+ } else if (position > lastPos) {
+ viewTravelCount = position - lastPos;
+ }
+
+ // Estimate how many screens we should travel
+ final float screenTravelCount = (float) viewTravelCount / childCount;
+
+ final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
+ if (position < firstPos) {
+ final int distance = (int) (-getHeight() * modifier);
+ final int duration = (int) (mScrollDuration * modifier);
+ smoothScrollBy(distance, duration, true);
+ postOnAnimation(this);
+ } else if (position > lastPos) {
+ final int distance = (int) (getHeight() * modifier);
+ final int duration = (int) (mScrollDuration * modifier);
+ smoothScrollBy(distance, duration, true);
+ postOnAnimation(this);
+ } else {
+ // On-screen, just scroll.
+ final int targetTop = getChildAt(position - firstPos).getTop();
+ final int distance = targetTop - mOffsetFromTop;
+ final int duration = (int) (mScrollDuration *
+ ((float) Math.abs(distance) / getHeight()));
+ smoothScrollBy(distance, duration, true);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Abstract position scroller that handles sub-position scrolling but has no
+ * understanding of layout.
+ */
+ abstract class AbsSubPositionScroller extends AbsPositionScroller {
+ private static final int DURATION_AUTO = -1;
+
+ private static final int DURATION_AUTO_MIN = 100;
+ private static final int DURATION_AUTO_MAX = 500;
+
+ private final SubScroller mSubScroller = new SubScroller();
+
+ /**
+ * The target offset in pixels between the top of the list and the top
+ * of the target position.
+ */
+ private int mOffset;
+
+ /**
+ * Scroll the minimum amount to get the target view entirely on-screen.
+ */
+ private void scrollToPosition(final int targetPosition, final boolean useOffset,
+ final int offset, final int boundPosition, final int duration) {
+ stop();
+
+ if (mDataChanged) {
+ // Wait until we're back in a stable state to try this.
+ mPositionScrollAfterLayout = new Runnable() {
+ @Override
+ public void run() {
+ scrollToPosition(
+ targetPosition, useOffset, offset, boundPosition, duration);
+ }
+ };
+ return;
+ }
+
+ if (mAdapter == null) {
+ // Can't scroll anywhere without an adapter.
+ return;
+ }
+
+ final int itemCount = getCount();
+ final int clampedPosition = MathUtils.constrain(targetPosition, 0, itemCount - 1);
+ final int clampedBoundPosition = MathUtils.constrain(boundPosition, 0, itemCount - 1);
+ final int firstPosition = getFirstVisiblePosition();
+ final int lastPosition = firstPosition + getChildCount();
+ final int targetRow = getRowForPosition(clampedPosition);
+ final int firstRow = getRowForPosition(firstPosition);
+ final int lastRow = getRowForPosition(lastPosition);
+ if (useOffset || targetRow <= firstRow) {
+ // Offset so the target row is top-aligned.
+ mOffset = offset;
+ } else if (targetRow >= lastRow - 1) {
+ // Offset so the target row is bottom-aligned.
+ final int listHeight = getHeight() - getPaddingTop() - getPaddingBottom();
+ mOffset = getHeightForPosition(clampedPosition) - listHeight;
+ } else {
+ // Don't scroll, target is entirely on-screen.
+ return;
+ }
+
+ float endSubRow = targetRow;
+ if (clampedBoundPosition != INVALID_POSITION) {
+ final int boundRow = getRowForPosition(clampedBoundPosition);
+ if (boundRow >= firstRow && boundRow < lastRow && boundRow != targetRow) {
+ endSubRow = computeBoundSubRow(targetRow, boundRow);
+ }
+ }
+
+ final View firstChild = getChildAt(0);
+ if (firstChild == null) {
+ return;
+ }
+
+ final int firstChildHeight = firstChild.getHeight();
+ final float startOffsetRatio;
+ if (firstChildHeight == 0) {
+ startOffsetRatio = 0;
+ } else {
+ startOffsetRatio = -firstChild.getTop() / (float) firstChildHeight;
+ }
+
+ final float startSubRow = MathUtils.constrain(
+ firstRow + startOffsetRatio, 0, getCount());
+ if (startSubRow == endSubRow && mOffset == 0) {
+ // Don't scroll, target is already in position.
+ return;
+ }
+
+ final int durationMillis;
+ if (duration == DURATION_AUTO) {
+ final float subRowDelta = Math.abs(startSubRow - endSubRow);
+ durationMillis = (int) MathUtils.lerp(
+ DURATION_AUTO_MIN, DURATION_AUTO_MAX, subRowDelta / getCount());
+ } else {
+ durationMillis = duration;
+ }
+
+ mSubScroller.startScroll(startSubRow, endSubRow, durationMillis);
+
+ postOnAnimation(mAnimationFrame);
+ }
+
+ /**
+ * Given a target row and offset, computes the sub-row position that
+ * aligns with the top of the list. If the offset is negative, the
+ * resulting sub-row will be smaller than the target row.
+ */
+ private float resolveOffset(int targetRow, int offset) {
+ // Compute the target sub-row position by finding the actual row
+ // indicated by the target and offset.
+ int remainingOffset = offset;
+ int targetHeight = getHeightForRow(targetRow);
+ if (offset < 0) {
+ // Subtract row heights until we find the right row.
+ while (targetRow > 0 && remainingOffset < 0) {
+ remainingOffset += targetHeight;
+ targetRow--;
+ targetHeight = getHeightForRow(targetRow);
+ }
+ } else if (offset > 0) {
+ // Add row heights until we find the right row.
+ while (targetRow < getCount() - 1 && remainingOffset > targetHeight) {
+ remainingOffset -= targetHeight;
+ targetRow++;
+ targetHeight = getHeightForRow(targetRow);
+ }
+ }
+
+ final float targetOffsetRatio;
+ if (remainingOffset < 0 || targetHeight == 0) {
+ targetOffsetRatio = 0;
+ } else {
+ targetOffsetRatio = remainingOffset / (float) targetHeight;
+ }
+
+ return targetRow + targetOffsetRatio;
+ }
+
+ private float computeBoundSubRow(int targetRow, int boundRow) {
+ final float targetSubRow = resolveOffset(targetRow, mOffset);
+ mOffset = 0;
+
+ // The target row is below the bound row, so the end position would
+ // push the bound position above the list. Abort!
+ if (targetSubRow >= boundRow) {
+ return boundRow;
+ }
+
+ // Compute the closest possible sub-position that wouldn't push the
+ // bound position's view further below the list.
+ final int listHeight = getHeight() - getPaddingTop() - getPaddingBottom();
+ final int boundHeight = getHeightForRow(boundRow);
+ final float boundSubRow = resolveOffset(boundRow, -listHeight + boundHeight);
+
+ return Math.max(boundSubRow, targetSubRow);
+ }
+
+ @Override
+ public void start(int position) {
+ scrollToPosition(position, false, 0, INVALID_POSITION, DURATION_AUTO);
+ }
+
+ @Override
+ public void start(int position, int boundPosition) {
+ scrollToPosition(position, false, 0, boundPosition, DURATION_AUTO);
+ }
+
+ @Override
+ public void startWithOffset(int position, int offset) {
+ scrollToPosition(position, true, offset, INVALID_POSITION, DURATION_AUTO);
+ }
+
+ @Override
+ public void startWithOffset(int position, int offset, int duration) {
+ scrollToPosition(position, true, offset, INVALID_POSITION, duration);
+ }
+
+ @Override
+ public void stop() {
+ removeCallbacks(mAnimationFrame);
+ }
+
+ /**
+ * Returns the height of a row, which is computed as the maximum height of
+ * the items in the row.
+ *
+ * @param row the row index
+ * @return row height in pixels
+ */
+ public abstract int getHeightForRow(int row);
+
+ /**
+ * Returns the row for the specified item position.
+ *
+ * @param position the item position
+ * @return the row index
+ */
+ public abstract int getRowForPosition(int position);
+
+ /**
+ * Returns the first item position within the specified row.
+ *
+ * @param row the row
+ * @return the position of the first item in the row
+ */
+ public abstract int getFirstPositionForRow(int row);
+
+ private void onAnimationFrame() {
+ final boolean shouldPost = mSubScroller.computePosition();
+ final float subRow = mSubScroller.getPosition();
+
+ final int row = (int) subRow;
+ final int position = getFirstPositionForRow(row);
+ if (position >= getCount()) {
+ // Invalid position, abort scrolling.
+ return;
+ }
+
+ final int rowHeight = getHeightForRow(row);
+ final int offset = (int) (rowHeight * (subRow - row));
+ final int addOffset = (int) (mOffset * mSubScroller.getInterpolatedValue());
+ setSelectionFromTop(position, -offset - addOffset);
+
+ if (shouldPost) {
+ postOnAnimation(mAnimationFrame);
+ }
+ }
+
+ private Runnable mAnimationFrame = new Runnable() {
+ @Override
+ public void run() {
+ onAnimationFrame();
+ }
+ };
+ }
+
+ /**
+ * Scroller capable of returning floating point positions.
+ */
+ static class SubScroller {
+ private static final Interpolator INTERPOLATOR = new AccelerateDecelerateInterpolator();
+
+ private float mStartPosition;
+ private float mEndPosition;
+ private long mStartTime;
+ private long mDuration;
+
+ private float mPosition;
+ private float mInterpolatedValue;
+
+ public void startScroll(float startPosition, float endPosition, int duration) {
+ mStartPosition = startPosition;
+ mEndPosition = endPosition;
+ mDuration = duration;
+
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mPosition = startPosition;
+ mInterpolatedValue = 0;
+ }
+
+ public boolean computePosition() {
+ final long elapsed = AnimationUtils.currentAnimationTimeMillis() - mStartTime;
+ final float value;
+ if (mDuration <= 0) {
+ value = 1;
+ } else {
+ value = MathUtils.constrain(elapsed / (float) mDuration, 0, 1);
+ }
+
+ mInterpolatedValue = INTERPOLATOR.getInterpolation(value);
+ mPosition = (mEndPosition - mStartPosition) * mInterpolatedValue + mStartPosition;
+
+ return elapsed < mDuration;
+ }
+
+ public float getPosition() {
+ return mPosition;
+ }
+
+ public float getInterpolatedValue() {
+ return mInterpolatedValue;
}
}
}
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index fe2fc96..438a9da 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/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index fe1cf72..1f405a7 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package com.android.internal.view.menu;
+package android.widget;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
-import android.transition.Transition;
-import android.transition.TransitionManager;
import android.util.SparseBooleanArray;
import android.view.ActionProvider;
import android.view.Gravity;
@@ -32,17 +30,23 @@ import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.ImageButton;
-import android.widget.ListPopupWindow;
import android.widget.ListPopupWindow.ForwardingListener;
import com.android.internal.transition.ActionBarTransition;
import com.android.internal.view.ActionBarPolicy;
-import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView;
+import com.android.internal.view.menu.ActionMenuItemView;
+import com.android.internal.view.menu.BaseMenuPresenter;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuItemImpl;
+import com.android.internal.view.menu.MenuPopupHelper;
+import com.android.internal.view.menu.MenuView;
+import com.android.internal.view.menu.SubMenuBuilder;
import java.util.ArrayList;
/**
* MenuPresenter for building action menus as seen in the action bar and action modes.
+ *
+ * @hide
*/
public class ActionMenuPresenter extends BaseMenuPresenter
implements ActionProvider.SubUiVisibilityListener {
@@ -70,6 +74,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
private ActionButtonSubmenu mActionButtonPopup;
private OpenOverflowRunnable mPostedOpenRunnable;
+ private ActionMenuPopupCallback mPopupCallback;
final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
int mOpenSubMenuId;
@@ -173,33 +178,17 @@ public class ActionMenuPresenter extends BaseMenuPresenter
}
@Override
- public void bindItemView(final MenuItemImpl item, MenuView.ItemView itemView) {
+ public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
itemView.initialize(item, 0);
final ActionMenuView menuView = (ActionMenuView) mMenuView;
final ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;
actionItemView.setItemInvoker(menuView);
- if (item.hasSubMenu()) {
- actionItemView.setOnTouchListener(new ForwardingListener(actionItemView) {
- @Override
- public ListPopupWindow getPopup() {
- return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
- }
-
- @Override
- protected boolean onForwardingStarted() {
- return onSubMenuSelected((SubMenuBuilder) item.getSubMenu());
- }
-
- @Override
- protected boolean onForwardingStopped() {
- return dismissPopupMenus();
- }
- });
- } else {
- actionItemView.setOnTouchListener(null);
+ if (mPopupCallback == null) {
+ mPopupCallback = new ActionMenuPopupCallback();
}
+ actionItemView.setPopupCallback(mPopupCallback);
}
@Override
@@ -553,6 +542,10 @@ public class ActionMenuPresenter extends BaseMenuPresenter
}
}
+ public void setMenuView(ActionMenuView menuView) {
+ mMenuView = menuView;
+ }
+
private static class SavedState implements Parcelable {
public int openSubMenuId;
@@ -585,7 +578,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
};
}
- private class OverflowMenuButton extends ImageButton implements ActionMenuChildView {
+ private class OverflowMenuButton extends ImageButton implements ActionMenuView.ActionMenuChildView {
public OverflowMenuButton(Context context) {
super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
@@ -714,14 +707,14 @@ public class ActionMenuPresenter extends BaseMenuPresenter
}
}
- private class PopupPresenterCallback implements MenuPresenter.Callback {
+ private class PopupPresenterCallback implements Callback {
@Override
public boolean onOpenSubMenu(MenuBuilder subMenu) {
if (subMenu == null) return false;
mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
- final MenuPresenter.Callback cb = getCallback();
+ final Callback cb = getCallback();
return cb != null ? cb.onOpenSubMenu(subMenu) : false;
}
@@ -730,7 +723,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
if (menu instanceof SubMenuBuilder) {
((SubMenuBuilder) menu).getRootMenu().close(false);
}
- final MenuPresenter.Callback cb = getCallback();
+ final Callback cb = getCallback();
if (cb != null) {
cb.onCloseMenu(menu, allMenusAreClosing);
}
@@ -753,4 +746,11 @@ public class ActionMenuPresenter extends BaseMenuPresenter
mPostedOpenRunnable = null;
}
}
+
+ private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback {
+ @Override
+ public ListPopupWindow getPopup() {
+ return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
+ }
+ }
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index 16a2031..32c7086 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -13,22 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.internal.view.menu;
+package android.widget;
import android.content.Context;
import android.content.res.Configuration;
-import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
+import android.view.Menu;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
-import android.widget.LinearLayout;
-import com.android.internal.R;
+import com.android.internal.view.menu.ActionMenuItemView;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuItemImpl;
+import com.android.internal.view.menu.MenuView;
/**
- * @hide
+ * ActionMenuView is a presentation of a series of menu options as a View. It provides
+ * several top level options as action buttons while spilling remaining options over as
+ * items in an overflow menu. This allows applications to present packs of actions inline with
+ * specific or repeating content.
*/
public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
private static final String TAG = "ActionMenuView";
@@ -44,8 +49,6 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
private int mFormatItemsWidth;
private int mMinCellSize;
private int mGeneratedItemPadding;
- private int mMeasuredExtraWidth;
- private int mMaxItemHeight;
public ActionMenuView(Context context) {
this(context, null);
@@ -57,26 +60,13 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
final float density = context.getResources().getDisplayMetrics().density;
mMinCellSize = (int) (MIN_CELL_SIZE * density);
mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar,
- R.attr.actionBarStyle, 0);
- mMaxItemHeight = a.getDimensionPixelSize(R.styleable.ActionBar_height, 0);
- a.recycle();
}
+ /** @hide */
public void setPresenter(ActionMenuPresenter presenter) {
mPresenter = presenter;
}
- public boolean isExpandedFormat() {
- return mFormatItems;
- }
-
- public void setMaxItemHeight(int maxItemHeight) {
- mMaxItemHeight = maxItemHeight;
- requestLayout();
- }
-
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -129,10 +119,8 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
final int widthPadding = getPaddingLeft() + getPaddingRight();
final int heightPadding = getPaddingTop() + getPaddingBottom();
- final int itemHeightSpec = heightMode == MeasureSpec.EXACTLY
- ? MeasureSpec.makeMeasureSpec(heightSize - heightPadding, MeasureSpec.EXACTLY)
- : MeasureSpec.makeMeasureSpec(
- Math.min(mMaxItemHeight, heightSize - heightPadding), MeasureSpec.AT_MOST);
+ final int itemHeightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
widthSize -= widthPadding;
@@ -333,7 +321,6 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
}
setMeasuredDimension(widthSize, heightSize);
- mMeasuredExtraWidth = cellsRemaining * cellSize;
}
/**
@@ -496,10 +483,12 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
mPresenter.dismissPopupMenus();
}
+ /** @hide */
public boolean isOverflowReserved() {
return mReserveOverflow;
}
-
+
+ /** @hide */
public void setOverflowReserved(boolean reserveOverflow) {
mReserveOverflow = reserveOverflow;
}
@@ -536,24 +525,51 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
return p != null && p instanceof LayoutParams;
}
+ /** @hide */
public LayoutParams generateOverflowButtonLayoutParams() {
LayoutParams result = generateDefaultLayoutParams();
result.isOverflowButton = true;
return result;
}
+ /** @hide */
public boolean invokeItem(MenuItemImpl item) {
return mMenu.performItemAction(item, 0);
}
+ /** @hide */
public int getWindowAnimations() {
return 0;
}
+ /** @hide */
public void initialize(MenuBuilder menu) {
mMenu = menu;
}
+ /**
+ * Returns the Menu object that this ActionMenuView is currently presenting.
+ *
+ * <p>Applications should use this method to obtain the ActionMenuView's Menu object
+ * and inflate or add content to it as necessary.</p>
+ *
+ * @return the Menu presented by this view
+ */
+ public Menu getMenu() {
+ if (mMenu == null) {
+ final Context context = getContext();
+ mMenu = new MenuBuilder(context);
+ mPresenter = new ActionMenuPresenter(context);
+ mPresenter.initForMenu(context, mMenu);
+ mPresenter.setMenuView(this);
+ }
+
+ return mMenu;
+ }
+
+ /**
+ * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public.
+ */
@Override
protected boolean hasDividerBeforeChildAt(int childIndex) {
if (childIndex == 0) {
@@ -575,23 +591,34 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
return false;
}
+ /** @hide */
public interface ActionMenuChildView {
public boolean needsDividerBefore();
public boolean needsDividerAfter();
}
public static class LayoutParams extends LinearLayout.LayoutParams {
+ /** @hide */
@ViewDebug.ExportedProperty(category = "layout")
public boolean isOverflowButton;
+
+ /** @hide */
@ViewDebug.ExportedProperty(category = "layout")
public int cellsUsed;
+
+ /** @hide */
@ViewDebug.ExportedProperty(category = "layout")
public int extraPixels;
+
+ /** @hide */
@ViewDebug.ExportedProperty(category = "layout")
public boolean expandable;
+
+ /** @hide */
@ViewDebug.ExportedProperty(category = "layout")
public boolean preventEdgeOffset;
+ /** @hide */
public boolean expanded;
public LayoutParams(Context c, AttributeSet attrs) {
@@ -612,6 +639,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
isOverflowButton = false;
}
+ /** @hide */
public LayoutParams(int width, int height, boolean isOverflowButton) {
super(width, height);
this.isOverflowButton = isOverflowButton;
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 a06344f..1da22ca 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..5b80648 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -67,27 +67,30 @@ 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) {
- mDial = r.getDrawable(com.android.internal.R.drawable.clock_dial);
+ mDial = context.getDrawable(com.android.internal.R.drawable.clock_dial);
}
mHourHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_hour);
if (mHourHand == null) {
- mHourHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_hour);
+ mHourHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_hour);
}
mMinuteHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_minute);
if (mMinuteHand == null) {
- mMinuteHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
+ mMinuteHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
}
mCalendar = new Time();
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index 34cfea5..10e56c7 100644
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -322,7 +322,7 @@ public class AppSecurityPermissions {
CharSequence grpName, CharSequence description, boolean dangerous) {
LayoutInflater inflater = (LayoutInflater)context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
- Drawable icon = context.getResources().getDrawable(dangerous
+ Drawable icon = context.getDrawable(dangerous
? R.drawable.ic_bullet_key_permission : R.drawable.ic_text_dot);
return getPermissionItemViewOld(context, inflater, grpName,
description, dangerous, icon);
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index f0eb94f..eb232fd 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);
@@ -362,7 +366,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* @attr ref android.R.styleable#PopupWindow_popupBackground
*/
public void setDropDownBackgroundResource(int id) {
- mPopup.setBackgroundDrawable(getResources().getDrawable(id));
+ mPopup.setBackgroundDrawable(getContext().getDrawable(id));
}
/**
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 e90b460..ea60abb 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);
+ }
- invalidate();
+ public CalendarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ 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,937 +505,1648 @@ 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();
- // refresh the view with the new parameters
- mAdapter.notifyDataSetChanged();
+ 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);
+
+ 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;
- 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);
- }
- }
+ /**
+ * Affects when the month selection will change while scrolling upe
+ */
+ private static final int SCROLL_HYST_WEEKS = 2;
- /**
- * 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;
- }
- }
+ /**
+ * How long the GoTo fling animation should last.
+ */
+ private static final int GOTO_SCROLL_DURATION = 1000;
- /**
- * Called when a <code>view</code> transitions to a new <code>scrollState
- * </code>.
- */
- private void onScrollStateChanged(AbsListView view, int scrollState) {
- mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
- }
+ /**
+ * The duration of the adjustment upon a user scroll in milliseconds.
+ */
+ private static final int ADJUSTMENT_SCROLL_DURATION = 500;
- /**
- * 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;
- }
+ /**
+ * How long to wait after receiving an onScrollStateChanged notification
+ * before acting on it.
+ */
+ private static final int SCROLL_CHANGE_DELAY = 40;
- // Figure out where we are
- long currScroll = view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
+ /**
+ * String for parsing dates.
+ */
+ private static final String DATE_FORMAT = "MM/dd/yyyy";
- // 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 default minimal date.
+ */
+ private static final String DEFAULT_MIN_DATE = "01/01/1900";
- // 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);
- }
+ /**
+ * The default maximal date.
+ */
+ private static final String DEFAULT_MAX_DATE = "01/01/2100";
- if (child != null) {
- // Find out which month we're moving into
- int month;
- if (mIsScrollingUp) {
- month = child.getMonthOfFirstWeekDay();
- } else {
- month = child.getMonthOfLastWeekDay();
- }
+ private static final int DEFAULT_SHOWN_WEEK_COUNT = 6;
- // 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;
- }
+ private static final int DEFAULT_DATE_TEXT_SIZE = 14;
- // 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);
- }
- setMonthDisplayed(firstDay);
- }
- }
+ private static final int UNSCALED_SELECTED_DATE_VERTICAL_BAR_WIDTH = 6;
- mPreviousScrollPosition = currScroll;
- mPreviousScrollState = mCurrentScrollState;
- }
+ private static final int UNSCALED_WEEK_MIN_VISIBLE_HEIGHT = 12;
- /**
- * 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();
- }
+ private static final int UNSCALED_LIST_SCROLL_TOP_OFFSET = 2;
- /**
- * @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());
- }
- 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);
- }
+ private static final int UNSCALED_BOTTOM_BUFFER = 20;
- /**
- * Command responsible for acting upon scroll state changes.
- */
- private class ScrollStateRunnable implements Runnable {
- private AbsListView mView;
+ 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 mNewState;
+ private int mUnfocusedMonthDateColor;
+
+ private int mWeekSeparatorLineColor;
+
+ private int mWeekNumberColor;
+
+ private int mWeekDayTextAppearanceResId;
+
+ private int mDateTextAppearanceResId;
/**
- * 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
+ * The top offset of the weeks list.
*/
- public void doScrollStateChange(AbsListView view, int scrollState) {
- mView = view;
- mNewState = scrollState;
- removeCallbacks(this);
- postDelayed(this, SCROLL_CHANGE_DELAY);
- }
+ private int mListScrollTopOffset = 2;
- 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;
- }
- }
+ /**
+ * The visible height of a week view.
+ */
+ private int mWeekMinVisibleHeight = 12;
- /**
- * <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;
+ /**
+ * The visible height of a week view.
+ */
+ private int mBottomBuffer = 20;
- private int mSelectedWeek;
+ /**
+ * The number of shown weeks.
+ */
+ private int mShownWeekCount;
- private int mFocusedMonth;
+ /**
+ * Flag whether to show the week number.
+ */
+ private boolean mShowWeekNumber;
- private int mTotalWeekCount;
+ /**
+ * The number of day per week to be shown.
+ */
+ private int mDaysPerWeek = 7;
- public WeeksAdapter() {
- mGestureDetector = new GestureDetector(mContext, new CalendarGestureListener());
- init();
- }
+ /**
+ * The friction of the week list while flinging.
+ */
+ private float mFriction = .05f;
/**
- * Set up the gesture detector and selected time
+ * Scale for adjusting velocity of the week list while flinging.
*/
- 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();
- }
+ private float mVelocityScale = 0.333f;
/**
- * Updates the selected day and related parameters.
- *
- * @param selectedDay The time to highlight
+ * The adapter for the weeks list.
*/
- 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();
- }
+ private WeeksAdapter mAdapter;
/**
- * @return The selected day of month.
+ * The weeks list.
*/
- public Calendar getSelectedDay() {
- return mSelectedDate;
+ 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);
+
+ 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);
+ }
+
+ mDelegator.invalidate();
}
@Override
- public int getCount() {
- return mTotalWeekCount;
+ public void setShownWeekCount(int count) {
+ if (mShownWeekCount != count) {
+ mShownWeekCount = count;
+ mDelegator.invalidate();
+ }
}
@Override
- public Object getItem(int position) {
- return null;
+ public int getShownWeekCount() {
+ return mShownWeekCount;
}
@Override
- public long getItemId(int position) {
- return position;
+ 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();
+ }
+ }
+ }
}
@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 int getSelectedWeekBackgroundColor() {
+ return mSelectedWeekBackgroundColor;
+ }
- int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
- Calendar.DAY_OF_WEEK) : -1;
- weekView.init(position, selectedWeekDay, mFocusedMonth);
+ @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();
+ }
+ }
+ }
+ }
- return weekView;
+ @Override
+ public int getFocusedMonthDateColor() {
+ return mFocusedMonthDateColor;
}
- /**
- * 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;
+ @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();
+ }
+ }
}
- mFocusedMonth = month;
- notifyDataSetChanged();
}
@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;
+ public int getUnfocusedMonthDateColor() {
+ return mFocusedMonthDateColor;
+ }
+
+ @Override
+ public void setWeekNumberColor(int color) {
+ if (mWeekNumberColor != color) {
+ mWeekNumberColor = color;
+ if (mShowWeekNumber) {
+ invalidateAllWeekViews();
}
- onDateTapped(mTempDate);
- return true;
}
- return false;
}
- /**
- * 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);
+ @Override
+ public int getWeekNumberColor() {
+ return mWeekNumberColor;
}
- /**
- * 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;
+ @Override
+ public void setWeekSeparatorLineColor(int color) {
+ if (mWeekSeparatorLineColor != color) {
+ mWeekSeparatorLineColor = color;
+ invalidateAllWeekViews();
}
}
- }
- /**
- * <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 {
+ @Override
+ public int getWeekSeparatorLineColor() {
+ return mWeekSeparatorLineColor;
+ }
- private final Rect mTempRect = new Rect();
+ @Override
+ public void setSelectedDateVerticalBar(int resourceId) {
+ Drawable drawable = mDelegator.getContext().getDrawable(resourceId);
+ setSelectedDateVerticalBar(drawable);
+ }
- private final Paint mDrawPaint = new Paint();
+ @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();
+ }
+ }
+ }
+ }
- private final Paint mMonthNumDrawPaint = new Paint();
+ @Override
+ public Drawable getSelectedDateVerticalBar() {
+ return mSelectedDateVerticalBar;
+ }
- // Cache the number strings so we don't have to recompute them each time
- private String[] mDayNumbers;
+ @Override
+ public void setWeekDayTextAppearance(int resourceId) {
+ if (mWeekDayTextAppearanceResId != resourceId) {
+ mWeekDayTextAppearanceResId = resourceId;
+ setUpHeader();
+ }
+ }
+
+ @Override
+ public int getWeekDayTextAppearance() {
+ return mWeekDayTextAppearanceResId;
+ }
+
+ @Override
+ public void setDateTextAppearance(int resourceId) {
+ if (mDateTextAppearanceResId != resourceId) {
+ mDateTextAppearanceResId = resourceId;
+ updateDateTextSize();
+ invalidateAllWeekViews();
+ }
+ }
+
+ @Override
+ public int getDateTextAppearance() {
+ return mDateTextAppearanceResId;
+ }
- // Quick lookup for checking which days are in the focus month
- private boolean[] mFocusDay;
+ @Override
+ public void setEnabled(boolean enabled) {
+ mListView.setEnabled(enabled);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mListView.isEnabled();
+ }
- // Whether this view has a focused day.
- private boolean mHasFocusedDay;
+ @Override
+ 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);
+ }
+ }
- // Whether this view has only focused days.
- private boolean mHasUnfocusedDay;
+ @Override
+ public long getMinDate() {
+ return mMinDate.getTimeInMillis();
+ }
- // The first day displayed by this item
- private Calendar mFirstDay;
+ @Override
+ 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);
+ }
+ }
- // The month of the first day in this week
- private int mMonthOfFirstWeekDay = -1;
+ @Override
+ public long getMaxDate() {
+ return mMaxDate.getTimeInMillis();
+ }
- // The month of the last day in this week
- private int mLastWeekDayMonth = -1;
+ @Override
+ public void setShowWeekNumber(boolean showWeekNumber) {
+ if (mShowWeekNumber == showWeekNumber) {
+ return;
+ }
+ mShowWeekNumber = showWeekNumber;
+ mAdapter.notifyDataSetChanged();
+ setUpHeader();
+ }
- // The position of this week, equivalent to weeks since the week of Jan
- // 1st, 1900
- private int mWeek = -1;
+ @Override
+ public boolean getShowWeekNumber() {
+ return mShowWeekNumber;
+ }
- // Quick reference to the width of this view, matches parent
- private int mWidth;
+ @Override
+ public void setFirstDayOfWeek(int firstDayOfWeek) {
+ if (mFirstDayOfWeek == firstDayOfWeek) {
+ return;
+ }
+ mFirstDayOfWeek = firstDayOfWeek;
+ mAdapter.init();
+ mAdapter.notifyDataSetChanged();
+ setUpHeader();
+ }
- // The height this view should draw at in pixels, set by height param
- private int mHeight;
+ @Override
+ public int getFirstDayOfWeek() {
+ return mFirstDayOfWeek;
+ }
- // If this view contains the selected day
- private boolean mHasSelectedDay = false;
+ @Override
+ public void setDate(long date) {
+ setDate(date, false, false);
+ }
- // Which day is selected [0-6] or -1 if no day is selected
- private int mSelectedDay = -1;
+ @Override
+ public void setDate(long date, boolean animate, boolean center) {
+ mTempDate.setTimeInMillis(date);
+ if (isSameDate(mTempDate, mAdapter.mSelectedDate)) {
+ return;
+ }
+ goTo(mTempDate, animate, true, center);
+ }
- // The number of days + a spot for week number if it is displayed
- private int mNumCells;
+ @Override
+ public long getDate() {
+ return mAdapter.mSelectedDate.getTimeInMillis();
+ }
- // The left edge of the selected day
- private int mSelectedLeft = -1;
+ @Override
+ public void setOnDateChangeListener(OnDateChangeListener listener) {
+ mOnDateChangeListener = listener;
+ }
- // The right edge of the selected day
- private int mSelectedRight = -1;
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ setCurrentLocale(newConfig.locale);
+ }
- public WeekView(Context context) {
- super(context);
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ event.setClassName(CalendarView.class.getName());
+ }
- // Sets up any standard paints that will be used
- initilaizePaints();
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ info.setClassName(CalendarView.class.getName());
}
/**
- * Initializes this week view.
+ * Sets the current locale.
*
- * @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.
+ * @param locale The current locale.
*/
- 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] = "";
- } 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);
+ @Override
+ protected void setCurrentLocale(Locale locale) {
+ super.setCurrentLocale(locale);
- updateSelectionPositions();
+ 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();
}
/**
- * Initialize the paint instances.
+ * Invalidates all week views.
*/
- 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);
+ private void invalidateAllWeekViews() {
+ final int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View view = mListView.getChildAt(i);
+ view.invalidate();
+ }
}
/**
- * Returns the month of the first day in this week.
+ * Gets a calendar for locale bootstrapped with the value of a given calendar.
*
- * @return The month the first day of this view is in.
+ * @param oldCalendar The old calendar.
+ * @param locale The locale.
*/
- public int getMonthOfFirstWeekDay() {
- return mMonthOfFirstWeekDay;
+ 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;
+ }
}
/**
- * Returns the month of the last day in this week
- *
- * @return The month the last day of this view is in
+ * @return True if the <code>firstDate</code> is the same as the <code>
+ * secondDate</code>.
*/
- public int getMonthOfLastWeekDay() {
- return mLastWeekDayMonth;
+ 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));
}
/**
- * Returns the first day in this view.
- *
- * @return The first day in the view.
+ * Creates a new adapter if necessary and sets up its parameters.
*/
- public Calendar getFirstDay() {
- return mFirstDay;
+ 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);
+ }
+
+ // refresh the view with the new parameters
+ mAdapter.notifyDataSetChanged();
}
/**
- * 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.
+ * Sets up the strings to be used by the header.
*/
- public boolean getDayFromLocation(float x, Calendar outCalendar) {
- final boolean isLayoutRtl = isLayoutRtl();
-
- int start;
- int end;
+ 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);
+ }
- if (isLayoutRtl) {
- start = 0;
- end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
+ TextView label = (TextView) mDayNamesHeader.getChildAt(0);
+ if (mShowWeekNumber) {
+ label.setVisibility(View.VISIBLE);
} else {
- start = mShowWeekNumber ? mWidth / mNumCells : 0;
- end = mWidth;
+ label.setVisibility(View.GONE);
}
-
- if (x < start || x > end) {
- outCalendar.clear();
- return false;
+ 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();
+ }
+
+ /**
+ * 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);
+ }
- // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
- int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
+ 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);
+ }
- if (isLayoutRtl) {
- dayPosition = mDaysPerWeek - 1 - dayPosition;
+ /**
+ * 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);
- outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
- outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
+ // 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);
- return true;
- }
+ setMonthDisplayed(mFirstDayOfMonth);
- @Override
- protected void onDraw(Canvas canvas) {
- drawBackground(canvas);
- drawWeekNumbersAndDates(canvas);
- drawWeekSeparators(canvas);
- drawSelectedDateVerticalBars(canvas);
+ // the earliest time we can scroll to is the min date
+ if (mFirstDayOfMonth.before(mMinDate)) {
+ position = 0;
+ } else {
+ position = getWeeksSinceMinDate(mFirstDayOfMonth);
+ }
+
+ 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);
+ }
}
/**
- * This draws the selection highlight if a day is selected in this week.
+ * Parses the given <code>date</code> and in case of success sets
+ * the result to the <code>outDate</code>.
*
- * @param canvas The canvas to draw on
+ * @return True if the date was parsed.
*/
- private void drawBackground(Canvas canvas) {
- if (!mHasSelectedDay) {
- return;
+ 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;
}
- mDrawPaint.setColor(mSelectedWeekBackgroundColor);
+ }
- mTempRect.top = mWeekSeperatorLineWidth;
- mTempRect.bottom = mHeight;
+ /**
+ * Called when a <code>view</code> transitions to a new <code>scrollState
+ * </code>.
+ */
+ private void onScrollStateChanged(AbsListView view, int scrollState) {
+ mScrollStateChangedRunnable.doScrollStateChange(view, scrollState);
+ }
- final boolean isLayoutRtl = isLayoutRtl();
+ /**
+ * 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;
+ }
- if (isLayoutRtl) {
- mTempRect.left = 0;
- mTempRect.right = mSelectedLeft - 2;
+ // Figure out where we are
+ long currScroll =
+ view.getFirstVisiblePosition() * child.getHeight() - child.getBottom();
+
+ // If we have moved since our last call update the direction
+ if (currScroll < mPreviousScrollPosition) {
+ mIsScrollingUp = true;
+ } else if (currScroll > mPreviousScrollPosition) {
+ mIsScrollingUp = false;
} else {
- mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
- mTempRect.right = mSelectedLeft - 2;
+ return;
}
- 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;
+ // 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);
}
- canvas.drawRect(mTempRect, mDrawPaint);
- }
- /**
- * 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);
+ if (child != null) {
+ // Find out which month we're moving into
+ int month;
+ if (mIsScrollingUp) {
+ month = child.getMonthOfFirstWeekDay();
+ } else {
+ month = child.getMonthOfLastWeekDay();
}
- } else {
- if (mShowWeekNumber) {
- mDrawPaint.setColor(mWeekNumberColor);
- int x = mWidth / divisor;
- canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
- i++;
+
+ // 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;
}
- for (; i < nDays; i++) {
- mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
- : mUnfocusedMonthDateColor);
- int x = (2 * i + 1) * mWidth / divisor;
- canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
+
+ // 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);
+ }
+ setMonthDisplayed(firstDay);
}
}
+ mPreviousScrollPosition = currScroll;
+ mPreviousScrollState = mCurrentScrollState;
}
/**
- * Draws a horizontal line for separating the weeks.
+ * Sets the month displayed at the top of this view based on time. Override
+ * to add custom events when the title is changed.
*
- * @param canvas The canvas to draw on.
+ * @param calendar A day in the new focus month.
*/
- 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++;
+ 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());
}
- if (firstFullyVisiblePosition == mWeek) {
- return;
+ 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;
+
+ 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);
}
- 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;
+
+ 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;
}
- 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
+ * <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 void drawSelectedDateVerticalBars(Canvas canvas) {
- if (!mHasSelectedDay) {
- return;
+ private class WeeksAdapter extends BaseAdapter implements OnTouchListener {
+
+ private int mSelectedWeek;
+
+ 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();
}
- 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();
+ /**
+ * 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();
+ }
+
+ /**
+ * 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;
+ }
+
+ @Override
+ public int getCount() {
+ return mTotalWeekCount;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @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);
+ }
+
+ int selectedWeekDay = (mSelectedWeek == position) ? mSelectedDate.get(
+ Calendar.DAY_OF_WEEK) : -1;
+ weekView.init(position, selectedWeekDay, mFocusedMonth);
+
+ return weekView;
+ }
+
+ /**
+ * 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();
+ }
+
+ @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;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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;
+ }
+ }
}
/**
- * This calculates the positions for the selected day lines.
+ * <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 updateSelectionPositions() {
- if (mHasSelectedDay) {
+ 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) {
+ 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] = "";
+ } 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);
+
+ updateSelectionPositions();
+ }
+
+ /**
+ * 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.
+ *
+ * @return The month the first day of this view is in.
+ */
+ public int getMonthOfFirstWeekDay() {
+ return mMonthOfFirstWeekDay;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Returns the first day in this view.
+ *
+ * @return The first day in the view.
+ */
+ public Calendar getFirstDay() {
+ return mFirstDay;
+ }
+
+ /**
+ * 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 (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) {
+ 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;
}
- if (mShowWeekNumber && !isLayoutRtl) {
- selectedPosition++;
+ 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) {
- mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
+ mTempRect.left = mSelectedRight + 3;
+ mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
+ } else {
+ mTempRect.left = mSelectedRight + 3;
+ mTempRect.right = mWidth;
+ }
+ canvas.drawRect(mTempRect, mDrawPaint);
+ }
+ /**
+ * 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 {
- mSelectedLeft = selectedPosition * mWidth / mNumCells;
+ 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);
+ }
}
- 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);
+ /**
+ * 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..1533510 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) {
@@ -119,7 +123,7 @@ public class CheckedTextView extends TextView implements Checkable {
Drawable d = null;
if (mCheckMarkResource != 0) {
- d = getResources().getDrawable(mCheckMarkResource);
+ d = getContext().getDrawable(mCheckMarkResource);
}
setCheckMarkDrawable(d);
}
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 abddc90..4298545 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) {
@@ -183,7 +186,7 @@ public abstract class CompoundButton extends Button implements Checkable {
Drawable d = null;
if (mButtonResource != 0) {
- d = getResources().getDrawable(mButtonResource);
+ d = getContext().getDrawable(mButtonResource);
}
setButtonDrawable(d);
}
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/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 30752e0..fa37443 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -136,8 +136,8 @@ public class EdgeEffect {
*/
public EdgeEffect(Context context) {
final Resources res = context.getResources();
- mEdge = res.getDrawable(R.drawable.overscroll_edge);
- mGlow = res.getDrawable(R.drawable.overscroll_glow);
+ mEdge = context.getDrawable(R.drawable.overscroll_edge);
+ mGlow = context.getDrawable(R.drawable.overscroll_glow);
mEdgeHeight = mEdge.getIntrinsicHeight();
mGlowHeight = mGlow.getIntrinsicHeight();
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 57e51c2..a8ff562 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -17,6 +17,7 @@
package android.widget;
import android.content.Context;
+import android.os.Bundle;
import android.text.Editable;
import android.text.Selection;
import android.text.Spannable;
@@ -56,8 +57,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
@@ -128,4 +133,22 @@ public class EditText extends TextView {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(EditText.class.getName());
}
+
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_SET_TEXT: {
+ CharSequence text = (arguments != null) ? arguments.getCharSequence(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null;
+ setText(text);
+ if (text != null && text.length() > 0) {
+ setSelection(text.length());
+ }
+ return true;
+ }
+ default: {
+ return super.performAccessibilityAction(action, arguments);
+ }
+ }
+ }
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 748af7b..53d9e28 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -23,6 +23,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.InputFilter;
import android.text.SpannableString;
+
import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.EditableInputConnection;
@@ -45,8 +46,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;
@@ -79,6 +78,7 @@ import android.view.DisplayList;
import android.view.DragEvent;
import android.view.Gravity;
import android.view.HardwareCanvas;
+import android.view.HardwareRenderer;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@@ -137,7 +137,16 @@ public class Editor {
InputContentType mInputContentType;
InputMethodState mInputMethodState;
- DisplayList[] mTextDisplayLists;
+ private static class TextDisplayList {
+ DisplayList displayList;
+ boolean isDirty;
+ public TextDisplayList(String name) {
+ isDirty = true;
+ displayList = DisplayList.create(name);
+ }
+ boolean needsRecord() { return isDirty || !displayList.isValid(); }
+ }
+ TextDisplayList[] mTextDisplayLists;
boolean mFrozenWithFocus;
boolean mSelectionMoved;
@@ -262,7 +271,7 @@ public class Editor {
mTextView.removeCallbacks(mShowSuggestionRunnable);
}
- invalidateTextDisplayList();
+ destroyDisplayListsData();
if (mSpellChecker != null) {
mSpellChecker.closeSession();
@@ -277,6 +286,18 @@ public class Editor {
mTemporaryDetach = false;
}
+ private void destroyDisplayListsData() {
+ if (mTextDisplayLists != null) {
+ for (int i = 0; i < mTextDisplayLists.length; i++) {
+ DisplayList displayList = mTextDisplayLists[i] != null
+ ? mTextDisplayLists[i].displayList : null;
+ if (displayList != null && displayList.isValid()) {
+ displayList.destroyDisplayListData();
+ }
+ }
+ }
+ }
+
private void showError() {
if (mTextView.getWindowToken() == null) {
mShowErrorAfterAttach = true;
@@ -1316,10 +1337,11 @@ public class Editor {
layout.drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,
firstLine, lastLine);
+ final HardwareRenderer renderer = mTextView.getHardwareRenderer();
if (layout instanceof DynamicLayout) {
if (mTextDisplayLists == null) {
- mTextDisplayLists = new DisplayList[ArrayUtils.idealObjectArraySize(0)];
+ mTextDisplayLists = new TextDisplayList[ArrayUtils.idealObjectArraySize(0)];
}
DynamicLayout dynamicLayout = (DynamicLayout) layout;
@@ -1343,15 +1365,13 @@ public class Editor {
searchStartIndex = blockIndex + 1;
}
- DisplayList blockDisplayList = mTextDisplayLists[blockIndex];
- if (blockDisplayList == null) {
- blockDisplayList = mTextDisplayLists[blockIndex] =
- mTextView.getHardwareRenderer().createDisplayList("Text " + blockIndex);
- } else {
- if (blockIsInvalid) blockDisplayList.clear();
+ if (mTextDisplayLists[blockIndex] == null) {
+ mTextDisplayLists[blockIndex] =
+ new TextDisplayList("Text " + blockIndex);
}
- final boolean blockDisplayListIsInvalid = !blockDisplayList.isValid();
+ final boolean blockDisplayListIsInvalid = mTextDisplayLists[blockIndex].needsRecord();
+ DisplayList blockDisplayList = mTextDisplayLists[blockIndex].displayList;
if (i >= indexFirstChangedBlock || blockDisplayListIsInvalid) {
final int blockBeginLine = endOfPreviousBlock + 1;
final int top = layout.getLineTop(blockBeginLine);
@@ -1381,7 +1401,7 @@ public class Editor {
// No need to untranslate, previous context is popped after
// drawDisplayList
} finally {
- blockDisplayList.end();
+ blockDisplayList.end(renderer, hardwareCanvas);
// Same as drawDisplayList below, handled by our TextView's parent
blockDisplayList.setClipToBounds(false);
}
@@ -1422,7 +1442,7 @@ public class Editor {
// No available index found, the pool has to grow
int newSize = ArrayUtils.idealIntArraySize(length + 1);
- DisplayList[] displayLists = new DisplayList[newSize];
+ TextDisplayList[] displayLists = new TextDisplayList[newSize];
System.arraycopy(mTextDisplayLists, 0, displayLists, 0, length);
mTextDisplayLists = displayLists;
return length;
@@ -1461,7 +1481,7 @@ public class Editor {
while (i < numberOfBlocks) {
final int blockIndex = blockIndices[i];
if (blockIndex != DynamicLayout.INVALID_BLOCK_INDEX) {
- mTextDisplayLists[blockIndex].clear();
+ mTextDisplayLists[blockIndex].isDirty = true;
}
if (blockEndLines[i] >= lastLine) break;
i++;
@@ -1472,7 +1492,7 @@ public class Editor {
void invalidateTextDisplayList() {
if (mTextDisplayLists != null) {
for (int i = 0; i < mTextDisplayLists.length; i++) {
- if (mTextDisplayLists[i] != null) mTextDisplayLists[i].clear();
+ if (mTextDisplayLists[i] != null) mTextDisplayLists[i].isDirty = true;
}
}
}
@@ -1681,7 +1701,7 @@ public class Editor {
private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) {
if (mCursorDrawable[cursorIndex] == null)
- mCursorDrawable[cursorIndex] = mTextView.getResources().getDrawable(
+ mCursorDrawable[cursorIndex] = mTextView.getContext().getDrawable(
mTextView.mCursorDrawableRes);
if (mTempRect == null) mTempRect = new Rect();
@@ -2321,8 +2341,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
@@ -2971,7 +2991,7 @@ public class Editor {
positionY += mContentView.getMeasuredHeight();
// Assumes insertion and selection handles share the same height
- final Drawable handle = mTextView.getResources().getDrawable(
+ final Drawable handle = mTextView.getContext().getDrawable(
mTextView.mTextSelectHandleRes);
positionY += handle.getIntrinsicHeight();
}
@@ -3548,7 +3568,7 @@ public class Editor {
private InsertionHandleView getHandle() {
if (mSelectHandleCenter == null) {
- mSelectHandleCenter = mTextView.getResources().getDrawable(
+ mSelectHandleCenter = mTextView.getContext().getDrawable(
mTextView.mTextSelectHandleRes);
}
if (mHandle == null) {
@@ -3594,11 +3614,11 @@ public class Editor {
private void initDrawables() {
if (mSelectHandleLeft == null) {
- mSelectHandleLeft = mTextView.getContext().getResources().getDrawable(
+ mSelectHandleLeft = mTextView.getContext().getDrawable(
mTextView.mTextSelectHandleLeftRes);
}
if (mSelectHandleRight == null) {
- mSelectHandleRight = mTextView.getContext().getResources().getDrawable(
+ mSelectHandleRight = mTextView.getContext().getDrawable(
mTextView.mTextSelectHandleRightRes);
}
}
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 4379bf6..c0961fd 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -24,11 +24,11 @@ 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;
import android.os.Build;
+import android.os.SystemClock;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.util.IntProperty;
@@ -43,7 +43,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 +76,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 +97,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 +109,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;
- /** Whether there is a track image to display. */
- private final boolean mHasTrackImage;
+ /** Theme-specified text size. Used only if text appearance is not set. */
+ private float mTextSize;
+
+ /** 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;
@@ -180,7 +174,7 @@ class FastScroller {
/** Whether the preview image is visible. */
private boolean mShowingPreview;
- private BaseAdapter mListAdapter;
+ private Adapter mListAdapter;
private SectionIndexer mSectionIndexer;
/** Whether decorations should be laid out from right to left. */
@@ -208,22 +202,9 @@ class FastScroller {
private boolean mMatchDragPosition;
private float mInitialTouchY;
- private boolean mHasPendingDrag;
+ private long mPendingDrag = -1;
private int mScaledTouchSlop;
- private final Runnable mDeferStartDrag = new Runnable() {
- @Override
- public void run() {
- if (mList.isAttachedToWindow()) {
- beginDrag();
-
- final float pos = getPosFromMotionEvent(mInitialTouchY);
- scrollTo(pos);
- }
-
- mHasPendingDrag = false;
- }
- };
private int mOldItemCount;
private int mOldChildCount;
@@ -247,92 +228,145 @@ class FastScroller {
}
};
- public FastScroller(AbsListView listView) {
+ public FastScroller(AbsListView listView, int styleResId) {
mList = listView;
- mOverlay = listView.getOverlay();
mOldItemCount = listView.getCount();
mOldChildCount = listView.getChildCount();
final Context context = listView.getContext();
mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mScrollBarStyle = listView.getScrollBarStyle();
- final Resources res = context.getResources();
- final TypedArray ta = context.getTheme().obtainStyledAttributes(ATTRS);
+ 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);
- final ImageView trackImage = new ImageView(context);
- mTrackImage = trackImage;
+ setStyle(styleResId);
+ final ViewGroupOverlay overlay = listView.getOverlay();
+ mOverlay = overlay;
+ overlay.add(mTrackImage);
+ overlay.add(mThumbImage);
+ overlay.add(mPreviewImage);
+ overlay.add(mPrimaryText);
+ overlay.add(mSecondaryText);
+
+ getSectionsFromIndexer();
updateLongList(mOldChildCount, mOldItemCount);
+ 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();
}
/**
@@ -353,7 +387,7 @@ class FastScroller {
if (mEnabled != enabled) {
mEnabled = enabled;
- onStateDependencyChanged();
+ onStateDependencyChanged(true);
}
}
@@ -371,7 +405,7 @@ class FastScroller {
if (mAlwaysShow != alwaysShow) {
mAlwaysShow = alwaysShow;
- onStateDependencyChanged();
+ onStateDependencyChanged(false);
}
}
@@ -385,13 +419,18 @@ class FastScroller {
/**
* Called when one of the variables affecting enabled state changes.
+ *
+ * @param peekIfEnabled whether the thumb should peek, if enabled
*/
- private void onStateDependencyChanged() {
+ private void onStateDependencyChanged(boolean peekIfEnabled) {
if (isEnabled()) {
if (isAlwaysShowEnabled()) {
setState(STATE_VISIBLE);
} else if (mState == STATE_VISIBLE) {
postAutoHide();
+ } else if (peekIfEnabled) {
+ setState(STATE_VISIBLE);
+ postAutoHide();
}
} else {
stop();
@@ -470,24 +509,18 @@ class FastScroller {
if (mLongList != longList) {
mLongList = longList;
- onStateDependencyChanged();
+ onStateDependencyChanged(false);
}
}
/**
* 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);
@@ -611,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) {
@@ -872,15 +905,15 @@ class FastScroller {
.getAdapter();
if (expAdapter instanceof SectionIndexer) {
mSectionIndexer = (SectionIndexer) expAdapter;
- mListAdapter = (BaseAdapter) adapter;
+ mListAdapter = adapter;
mSections = mSectionIndexer.getSections();
}
} else if (adapter instanceof SectionIndexer) {
- mListAdapter = (BaseAdapter) adapter;
+ mListAdapter = adapter;
mSectionIndexer = (SectionIndexer) adapter;
mSections = mSectionIndexer.getSections();
} else {
- mListAdapter = (BaseAdapter) adapter;
+ mListAdapter = adapter;
mSections = null;
}
}
@@ -1028,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) {
@@ -1054,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);
@@ -1105,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;
@@ -1117,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;
@@ -1130,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;
@@ -1235,8 +1264,7 @@ class FastScroller {
* @see #startPendingDrag()
*/
private void cancelPendingDrag() {
- mList.removeCallbacks(mDeferStartDrag);
- mHasPendingDrag = false;
+ mPendingDrag = -1;
}
/**
@@ -1244,11 +1272,12 @@ class FastScroller {
* scrolling, rather than tapping.
*/
private void startPendingDrag() {
- mHasPendingDrag = true;
- mList.postDelayed(mDeferStartDrag, TAP_TIMEOUT);
+ mPendingDrag = SystemClock.uptimeMillis() + TAP_TIMEOUT;
}
private void beginDrag() {
+ mPendingDrag = -1;
+
setState(STATE_DRAGGING);
if (mListAdapter == null && mList != null) {
@@ -1288,6 +1317,13 @@ class FastScroller {
case MotionEvent.ACTION_MOVE:
if (!isPointInside(ev.getX(), ev.getY())) {
cancelPendingDrag();
+ } else if (mPendingDrag >= 0 && mPendingDrag <= SystemClock.uptimeMillis()) {
+ beginDrag();
+
+ final float pos = getPosFromMotionEvent(mInitialTouchY);
+ scrollTo(pos);
+
+ return onTouchEvent(ev);
}
break;
case MotionEvent.ACTION_UP:
@@ -1322,7 +1358,7 @@ class FastScroller {
switch (me.getActionMasked()) {
case MotionEvent.ACTION_UP: {
- if (mHasPendingDrag) {
+ if (mPendingDrag >= 0) {
// Allow a tap to scroll.
beginDrag();
@@ -1330,7 +1366,6 @@ class FastScroller {
setThumbPos(pos);
scrollTo(pos);
- cancelPendingDrag();
// Will hit the STATE_DRAGGING check below
}
@@ -1351,20 +1386,9 @@ class FastScroller {
} break;
case MotionEvent.ACTION_MOVE: {
- if (mHasPendingDrag && Math.abs(me.getY() - mInitialTouchY) > mScaledTouchSlop) {
- setState(STATE_DRAGGING);
-
- if (mListAdapter == null && mList != null) {
- getSectionsFromIndexer();
- }
-
- if (mList != null) {
- mList.requestDisallowInterceptTouchEvent(true);
- mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
- }
+ if (mPendingDrag >= 0 && Math.abs(me.getY() - mInitialTouchY) > mScaledTouchSlop) {
+ beginDrag();
- cancelFling();
- cancelPendingDrag();
// Will hit the STATE_DRAGGING check below
}
@@ -1401,7 +1425,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..04b18c1 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -16,26 +16,32 @@
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;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.ViewRootImpl;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
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 +59,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 +121,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);
@@ -1014,6 +1029,11 @@ public class GridView extends AbsListView {
}
@Override
+ AbsPositionScroller createPositionScroller() {
+ return new GridViewPositionScroller();
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -1202,6 +1222,34 @@ public class GridView extends AbsListView {
setSelectedPositionInt(mNextSelectedPosition);
+ AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
+ View accessibilityFocusLayoutRestoreView = null;
+ int accessibilityFocusPosition = INVALID_POSITION;
+
+ // Remember which child, if any, had accessibility focus. This must
+ // occur before recycling any views, since that will clear
+ // accessibility focus.
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
+ if (focusHost != null) {
+ final View focusChild = getAccessibilityFocusedChild(focusHost);
+ if (focusChild != null) {
+ if (!dataChanged || focusChild.hasTransientState()
+ || mAdapterHasStableIds) {
+ // The views won't be changing, so try to maintain
+ // focus on the current host and virtual view.
+ accessibilityFocusLayoutRestoreView = focusHost;
+ accessibilityFocusLayoutRestoreNode = viewRootImpl
+ .getAccessibilityFocusedVirtualView();
+ }
+
+ // Try to maintain focus at the same position.
+ accessibilityFocusPosition = getPositionForView(focusChild);
+ }
+ }
+ }
+
// Pull all children into the RecycleBin.
// These views will be reused if possible
final int firstPosition = mFirstPosition;
@@ -1216,7 +1264,6 @@ public class GridView extends AbsListView {
}
// Clear out old views
- //removeAllViewsInLayout();
detachAllViewsFromParent();
recycleBin.removeSkippedScrap();
@@ -1287,6 +1334,35 @@ public class GridView extends AbsListView {
mSelectorRect.setEmpty();
}
+ // Attempt to restore accessibility focus, if necessary.
+ if (viewRootImpl != null) {
+ final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();
+ if (newAccessibilityFocusedView == null) {
+ if (accessibilityFocusLayoutRestoreView != null
+ && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {
+ final AccessibilityNodeProvider provider =
+ accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
+ if (accessibilityFocusLayoutRestoreNode != null && provider != null) {
+ final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
+ accessibilityFocusLayoutRestoreNode.getSourceNodeId());
+ provider.performAction(virtualViewId,
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ } else {
+ accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
+ }
+ } else if (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();
+ }
+ }
+ }
+ }
+
mLayoutMode = LAYOUT_NORMAL;
mDataChanged = false;
if (mPositionScrollAfterLayout != null) {
@@ -2056,13 +2132,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;
}
@@ -2265,7 +2342,9 @@ public class GridView extends AbsListView {
final int columnsCount = getNumColumns();
final int rowsCount = getCount() / columnsCount;
- final CollectionInfo collectionInfo = CollectionInfo.obtain(columnsCount, rowsCount, false);
+ final int selectionMode = getSelectionModeForAccessibility();
+ final CollectionInfo collectionInfo = CollectionInfo.obtain(
+ columnsCount, rowsCount, false, selectionMode);
info.setCollectionInfo(collectionInfo);
}
@@ -2292,7 +2371,38 @@ public class GridView extends AbsListView {
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final boolean isHeading = lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
- final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(column, 1, row, 1, isHeading);
+ final boolean isSelected = isItemChecked(position);
+ final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
+ column, 1, row, 1, isHeading, isSelected);
info.setCollectionItemInfo(itemInfo);
}
+
+ /**
+ * Sub-position scroller that understands the layout of a GridView.
+ */
+ class GridViewPositionScroller extends AbsSubPositionScroller {
+ @Override
+ public int getRowForPosition(int position) {
+ return position / mNumColumns;
+ }
+
+ @Override
+ public int getFirstPositionForRow(int row) {
+ return row * mNumColumns;
+ }
+
+ @Override
+ public int getHeightForRow(int row) {
+ final int firstRowPosition = row * mNumColumns;
+ final int lastRowPosition = Math.min(getCount(), firstRowPosition + mNumColumns);
+ int maxHeight = 0;
+ for (int i = firstRowPosition; i < lastRowPosition; i++) {
+ final int height = getHeightForPosition(i);
+ if (height > maxHeight) {
+ maxHeight = height;
+ }
+ }
+ return maxHeight;
+ }
+ }
}
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 f05179b..572302a 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -24,8 +24,10 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
+import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Xfermode;
import android.graphics.drawable.BitmapDrawable;
@@ -119,12 +121,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) {
@@ -357,13 +364,13 @@ public class ImageView extends View {
@android.view.RemotableViewMethod
public void setImageResource(int resId) {
if (mUri != null || mResource != resId) {
+ final int oldWidth = mDrawableWidth;
+ final int oldHeight = mDrawableHeight;
+
updateDrawable(null);
mResource = resId;
mUri = null;
- final int oldWidth = mDrawableWidth;
- final int oldHeight = mDrawableHeight;
-
resolveUri();
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
@@ -635,7 +642,7 @@ public class ImageView extends View {
if (mResource != 0) {
try {
- d = rsrc.getDrawable(mResource);
+ d = mContext.getDrawable(mResource);
} catch (Exception e) {
Log.w("ImageView", "Unable to find resource: " + mResource, e);
// Don't try again.
@@ -648,7 +655,7 @@ public class ImageView extends View {
// Load drawable through Resources, to get the source density information
ContentResolver.OpenResourceIdResult r =
mContext.getContentResolver().getResourceId(mUri);
- d = r.r.getDrawable(r.id);
+ d = r.r.getDrawable(r.id, mContext.getTheme());
} catch (Exception e) {
Log.w("ImageView", "Unable to open content: " + mUri, e);
}
@@ -1220,6 +1227,37 @@ public class ImageView extends View {
}
}
+ @Override
+ public boolean isOpaque() {
+ return super.isOpaque() || mDrawable != null && mXfermode == null
+ && mDrawable.getOpacity() == PixelFormat.OPAQUE
+ && mAlpha * mViewAlphaScale >> 8 == 255
+ && isFilledByImage();
+ }
+
+ private boolean isFilledByImage() {
+ if (mDrawable == null) {
+ return false;
+ }
+
+ final Rect bounds = mDrawable.getBounds();
+ final Matrix matrix = mDrawMatrix;
+ if (matrix == null) {
+ return bounds.left <= 0 && bounds.top <= 0 && bounds.right >= getWidth()
+ && bounds.bottom >= getHeight();
+ } else if (matrix.rectStaysRect()) {
+ final RectF boundsSrc = mTempSrc;
+ final RectF boundsDst = mTempDst;
+ boundsSrc.set(bounds);
+ matrix.mapRect(boundsDst, boundsSrc);
+ return boundsDst.left <= 0 && boundsDst.top <= 0 && boundsDst.right >= getWidth()
+ && boundsDst.bottom >= getHeight();
+ } else {
+ // If the matrix doesn't map to a rectangle, assume the worst.
+ return false;
+ }
+ }
+
@RemotableViewMethod
@Override
public void setVisibility(int visibility) {
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..82e624d 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;
}
@@ -642,6 +667,7 @@ public class LinearLayout extends ViewGroup {
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
+ boolean skippedMeasure = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
@@ -676,6 +702,7 @@ public class LinearLayout extends ViewGroup {
// there is any leftover space.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
+ skippedMeasure = true;
} else {
int oldHeight = Integer.MIN_VALUE;
@@ -802,9 +829,10 @@ public class LinearLayout extends ViewGroup {
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
// Either expand children with weight to take up available space or
- // shrink them if they extend beyond our current bounds
+ // shrink them if they extend beyond our current bounds. If we skipped
+ // measurement on any children, we need to measure them now.
int delta = heightSize - mTotalLength;
- if (delta != 0 && totalWeight > 0.0f) {
+ if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
mTotalLength = 0;
@@ -970,6 +998,7 @@ public class LinearLayout extends ViewGroup {
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchHeight = false;
+ boolean skippedMeasure = false;
if (mMaxAscent == null || mMaxDescent == null) {
mMaxAscent = new int[VERTICAL_GRAVITY_COUNT];
@@ -1032,6 +1061,8 @@ public class LinearLayout extends ViewGroup {
if (baselineAligned) {
final int freeSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
child.measure(freeSpec, freeSpec);
+ } else {
+ skippedMeasure = true;
}
} else {
int oldWidth = Integer.MIN_VALUE;
@@ -1180,9 +1211,10 @@ public class LinearLayout extends ViewGroup {
widthSize = widthSizeAndState & MEASURED_SIZE_MASK;
// Either expand children with weight to take up available space or
- // shrink them if they extend beyond our current bounds
+ // shrink them if they extend beyond our current bounds. If we skipped
+ // measurement on any children, we need to measure them now.
int delta = widthSize - mTotalLength;
- if (delta != 0 && totalWeight > 0.0f) {
+ if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1;
@@ -1673,12 +1705,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 +1722,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..5de67c8 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -44,6 +44,7 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
import android.widget.RemoteViews.RemoteView;
import java.util.ArrayList;
@@ -142,11 +143,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);
@@ -263,6 +268,7 @@ public class ListView extends AbsListView {
info.data = data;
info.isSelectable = isSelectable;
mHeaderViewInfos.add(info);
+ mAreAllItemsSelectable &= isSelectable;
// Wrap the adapter if it wasn't already wrapped.
if (mAdapter != null) {
@@ -356,6 +362,7 @@ public class ListView extends AbsListView {
info.data = data;
info.isSelectable = isSelectable;
mFooterViewInfos.add(info);
+ mAreAllItemsSelectable &= isSelectable;
// Wrap the adapter if it wasn't already wrapped.
if (mAdapter != null) {
@@ -1562,22 +1569,58 @@ public class ListView 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;
+ AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
+ View accessibilityFocusLayoutRestoreView = null;
+ int accessibilityFocusPosition = INVALID_POSITION;
+
+ // Remember which child, if any, had accessibility focus. This must
+ // occur before recycling any views, since that will clear
+ // accessibility focus.
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
+ if (focusHost != null) {
+ final View focusChild = getAccessibilityFocusedChild(focusHost);
+ if (focusChild != null) {
+ if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)
+ || focusChild.hasTransientState() || mAdapterHasStableIds) {
+ // The views won't be changing, so try to maintain
+ // focus on the current host and virtual view.
+ accessibilityFocusLayoutRestoreView = focusHost;
+ accessibilityFocusLayoutRestoreNode = viewRootImpl
+ .getAccessibilityFocusedVirtualView();
+ }
+
+ // If all else fails, maintain focus at the same
+ // position.
+ accessibilityFocusPosition = getPositionForView(focusChild);
+ }
+ }
}
- // 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.
+ View focusLayoutRestoreDirectChild = null;
+ View focusLayoutRestoreView = null;
+
+ // Take focus back to us temporarily to avoid the eventual call to
+ // clear focus when removing the focused child below from messing
+ // things up when ViewAncestor assigns focus back to someone else.
final View focusedChild = getFocusedChild();
if (focusedChild != null) {
- focusedChild.setHasTransientState(true);
+ // TODO: in some cases focusedChild.getParent() == null
+
+ // We can remember the focused view to restore after re-layout
+ // if the data hasn't changed, or if the focused position is a
+ // header or footer.
+ if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {
+ focusLayoutRestoreDirectChild = focusedChild;
+ // Remember the specific view that had focus.
+ focusLayoutRestoreView = findFocus();
+ if (focusLayoutRestoreView != null) {
+ // Tell it we are going to mess with it.
+ focusLayoutRestoreView.onStartTemporaryDetach();
+ }
+ }
+ requestFocus();
}
// Pull all children into the RecycleBin.
@@ -1651,20 +1694,24 @@ public class ListView extends AbsListView {
recycleBin.scrapActiveViews();
if (sel != null) {
- final boolean shouldPlaceFocus = mItemsCanFocus && hasFocus();
- final boolean maintainedFocus = focusedChild != null && focusedChild.hasFocus();
- if (shouldPlaceFocus && !maintainedFocus && !sel.hasFocus()) {
- if (sel.requestFocus()) {
- // Successfully placed focus, clear selection.
- sel.setSelected(false);
- mSelectorRect.setEmpty();
- } else {
- // Failed to place focus, clear current (invalid) focus.
+ // The current selected item should get focus if items are
+ // focusable.
+ if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
+ final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
+ focusLayoutRestoreView != null &&
+ focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
+ if (!focusWasTaken) {
+ // Selected item didn't take focus, but we still want to
+ // make sure something else outside of the selected view
+ // has focus.
final View focused = getFocusedChild();
if (focused != null) {
focused.clearFocus();
}
positionSelector(INVALID_POSITION, sel);
+ } else {
+ sel.setSelected(false);
+ mSelectorRect.setEmpty();
}
} else {
positionSelector(INVALID_POSITION, sel);
@@ -1682,27 +1729,48 @@ public class ListView extends AbsListView {
mSelectedTop = 0;
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();
+ // Even if there is not selected position, we may need to
+ // restore focus (i.e. something focusable in touch mode).
+ if (hasFocus() && focusLayoutRestoreView != null) {
+ focusLayoutRestoreView.requestFocus();
+ }
+ }
+
+ // Attempt to restore accessibility focus, if necessary.
+ if (viewRootImpl != null) {
+ final View newAccessibilityFocusedView = viewRootImpl.getAccessibilityFocusedHost();
+ if (newAccessibilityFocusedView == null) {
+ if (accessibilityFocusLayoutRestoreView != null
+ && accessibilityFocusLayoutRestoreView.isAttachedToWindow()) {
+ final AccessibilityNodeProvider provider =
+ accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
+ if (accessibilityFocusLayoutRestoreNode != null && provider != null) {
+ final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
+ accessibilityFocusLayoutRestoreNode.getSourceNodeId());
+ provider.performAction(virtualViewId,
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ } else {
+ accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
+ }
+ } else if (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);
+ // Tell focus view we are done mucking with it, if it is still in
+ // our view hierarchy.
+ if (focusLayoutRestoreView != null
+ && focusLayoutRestoreView.getWindowToken() != null) {
+ focusLayoutRestoreView.onFinishTemporaryDetach();
}
mLayoutMode = LAYOUT_NORMAL;
@@ -1729,31 +1797,27 @@ public class ListView extends AbsListView {
}
/**
- * @return the direct child that contains accessibility focus, or null if no
- * child contains accessibility focus
+ * @param child a direct child of this list.
+ * @return Whether child is a header or footer view.
*/
- 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();
+ private boolean isDirectChildHeaderOrFooter(View child) {
+ final ArrayList<FixedViewInfo> headers = mHeaderViewInfos;
+ final int numHeaders = headers.size();
+ for (int i = 0; i < numHeaders; i++) {
+ if (child == headers.get(i).view) {
+ return true;
+ }
}
- if (!(viewParent instanceof View)) {
- return null;
+ final ArrayList<FixedViewInfo> footers = mFooterViewInfos;
+ final int numFooters = footers.size();
+ for (int i = 0; i < numFooters; i++) {
+ if (child == footers.get(i).view) {
+ return true;
+ }
}
- return focusedView;
+ return false;
}
/**
@@ -1915,45 +1979,6 @@ public class ListView extends AbsListView {
}
/**
- * Sets the selected item and positions the selection y pixels from the top edge
- * of the ListView. (If in touch mode, the item will not be selected but it will
- * still be positioned appropriately.)
- *
- * @param position Index (starting at 0) of the data item to be selected.
- * @param y The distance from the top edge of the ListView (plus padding) that the
- * item will be positioned.
- */
- public void setSelectionFromTop(int position, int y) {
- if (mAdapter == null) {
- return;
- }
-
- if (!isInTouchMode()) {
- position = lookForSelectablePosition(position, true);
- if (position >= 0) {
- setNextSelectedPositionInt(position);
- }
- } else {
- mResurrectToPosition = position;
- }
-
- if (position >= 0) {
- mLayoutMode = LAYOUT_SPECIFIC;
- mSpecificTop = mListPadding.top + y;
-
- if (mNeedSync) {
- mSyncPosition = position;
- mSyncRowId = mAdapter.getItemId(position);
- }
-
- if (mPositionScroller != null) {
- mPositionScroller.stop();
- }
- requestLayout();
- }
- }
-
- /**
* Makes the item at the supplied position selected.
*
* @param position the position of the item to select
@@ -3266,14 +3291,13 @@ public class ListView extends AbsListView {
if (drawDividers && (bottom < listBottom)
&& !(drawOverscrollFooter && isLastItem)) {
final int nextIndex = (itemIndex + 1);
- // Draw dividers between enabled items, headers and/or
- // footers when enabled, and the end of the list.
- if (areAllItemsSelectable || ((adapter.isEnabled(itemIndex)
- || (headerDividers && isHeader)
- || (footerDividers && isFooter)) && (isLastItem
- || adapter.isEnabled(nextIndex)
- || (headerDividers && (nextIndex < headerCount))
- || (footerDividers && (nextIndex >= footerLimit))))) {
+ // Draw dividers between enabled items, headers
+ // and/or footers when enabled and requested, and
+ // after the last enabled item.
+ if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
+ && (nextIndex >= headerCount)) && (isLastItem
+ || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
+ && (nextIndex < footerLimit)))) {
bounds.top = bottom;
bounds.bottom = bottom + dividerHeight;
drawDivider(canvas, bounds, i);
@@ -3315,14 +3339,13 @@ public class ListView extends AbsListView {
if (drawDividers && (top > effectivePaddingTop)) {
final boolean isFirstItem = (i == start);
final int previousIndex = (itemIndex - 1);
- // Draw dividers between enabled items, headers and/or
- // footers when enabled, and the end of the list.
- if (areAllItemsSelectable || ((adapter.isEnabled(itemIndex)
- || (headerDividers && isHeader)
- || (footerDividers && isFooter)) && (isFirstItem
- || adapter.isEnabled(previousIndex)
- || (headerDividers && (previousIndex < headerCount))
- || (footerDividers && (previousIndex >= footerLimit))))) {
+ // Draw dividers between enabled items, headers
+ // and/or footers when enabled and requested, and
+ // before the first enabled item.
+ if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
+ && (previousIndex >= headerCount)) && (isFirstItem ||
+ adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
+ && (previousIndex < footerLimit)))) {
bounds.top = top - dividerHeight;
bounds.bottom = top;
// Give the method the child ABOVE the divider,
@@ -3771,6 +3794,79 @@ public class ListView extends AbsListView {
}
@Override
+ int getHeightForPosition(int position) {
+ final int height = super.getHeightForPosition(position);
+ if (shouldAdjustHeightForDivider(position)) {
+ return height + mDividerHeight;
+ }
+ return height;
+ }
+
+ private boolean shouldAdjustHeightForDivider(int itemIndex) {
+ final int dividerHeight = mDividerHeight;
+ final Drawable overscrollHeader = mOverScrollHeader;
+ final Drawable overscrollFooter = mOverScrollFooter;
+ final boolean drawOverscrollHeader = overscrollHeader != null;
+ final boolean drawOverscrollFooter = overscrollFooter != null;
+ final boolean drawDividers = dividerHeight > 0 && mDivider != null;
+
+ if (drawDividers) {
+ final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
+ final int itemCount = mItemCount;
+ final int headerCount = mHeaderViewInfos.size();
+ final int footerLimit = (itemCount - mFooterViewInfos.size());
+ final boolean isHeader = (itemIndex < headerCount);
+ final boolean isFooter = (itemIndex >= footerLimit);
+ final boolean headerDividers = mHeaderDividersEnabled;
+ final boolean footerDividers = mFooterDividersEnabled;
+ if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
+ final ListAdapter adapter = mAdapter;
+ if (!mStackFromBottom) {
+ final boolean isLastItem = (itemIndex == (itemCount - 1));
+ if (!drawOverscrollFooter || !isLastItem) {
+ final int nextIndex = itemIndex + 1;
+ // Draw dividers between enabled items, headers
+ // and/or footers when enabled and requested, and
+ // after the last enabled item.
+ if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
+ && (nextIndex >= headerCount)) && (isLastItem
+ || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter
+ && (nextIndex < footerLimit)))) {
+ return true;
+ } else if (fillForMissingDividers) {
+ return true;
+ }
+ }
+ } else {
+ final int start = drawOverscrollHeader ? 1 : 0;
+ final boolean isFirstItem = (itemIndex == start);
+ if (!isFirstItem) {
+ final int previousIndex = (itemIndex - 1);
+ // Draw dividers between enabled items, headers
+ // and/or footers when enabled and requested, and
+ // before the first enabled item.
+ if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader
+ && (previousIndex >= headerCount)) && (isFirstItem ||
+ adapter.isEnabled(previousIndex) && (footerDividers || !isFooter
+ && (previousIndex < footerLimit)))) {
+ return true;
+ } else if (fillForMissingDividers) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ AbsPositionScroller createPositionScroller() {
+ return new ListViewPositionScroller();
+ }
+
+ @Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(ListView.class.getName());
@@ -3782,7 +3878,8 @@ public class ListView extends AbsListView {
info.setClassName(ListView.class.getName());
final int count = getCount();
- final CollectionInfo collectionInfo = CollectionInfo.obtain(1, count, false);
+ final int selectionMode = getSelectionModeForAccessibility();
+ final CollectionInfo collectionInfo = CollectionInfo.obtain(1, count, false, selectionMode);
info.setCollectionInfo(collectionInfo);
}
@@ -3793,7 +3890,29 @@ public class ListView extends AbsListView {
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final boolean isHeading = lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
- final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(0, 1, position, 1, isHeading);
+ final boolean isSelected = isItemChecked(position);
+ final CollectionItemInfo itemInfo = CollectionItemInfo.obtain(
+ 0, 1, position, 1, isHeading, isSelected);
info.setCollectionItemInfo(itemInfo);
}
+
+ /**
+ * Sub-position scroller that understands the layout of a ListView.
+ */
+ class ListViewPositionScroller extends AbsSubPositionScroller {
+ @Override
+ public int getRowForPosition(int position) {
+ return position;
+ }
+
+ @Override
+ public int getFirstPositionForRow(int row) {
+ return row;
+ }
+
+ @Override
+ public int getHeightForRow(int row) {
+ return getHeightForPosition(row);
+ }
+ }
}
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index 9c61fd6..546cc5f 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -442,7 +442,19 @@ public class MediaController extends FrameLayout {
@Override
public boolean onTouchEvent(MotionEvent event) {
- show(sDefaultTimeout);
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ show(0); // show until hide is called
+ break;
+ case MotionEvent.ACTION_UP:
+ show(sDefaultTimeout); // start timeout
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ hide();
+ break;
+ default:
+ break;
+ }
return true;
}
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 9c6a2e3..00b2c13 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 be20d2d..6e71a5c 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 6a369a6..f7e81b8 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(
@@ -350,19 +346,24 @@ public class ProgressBar extends View {
return out;
} else if (drawable instanceof BitmapDrawable) {
- final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
+ final BitmapDrawable bitmap = (BitmapDrawable) drawable;
+ final Bitmap tileBitmap = bitmap.getBitmap();
if (mSampleTile == null) {
mSampleTile = tileBitmap;
}
-
- final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
+ final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
shapeDrawable.getPaint().setShader(bitmapShader);
- return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
- ClipDrawable.HORIZONTAL) : shapeDrawable;
+ // Ensure the color filter and tint are propagated.
+ shapeDrawable.setTint(bitmap.getTint());
+ shapeDrawable.setTintMode(bitmap.getTintMode());
+ shapeDrawable.setColorFilter(bitmap.getColorFilter());
+
+ return clip ? new ClipDrawable(
+ shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL) : shapeDrawable;
}
return drawable;
@@ -472,11 +473,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)
*/
@@ -493,6 +492,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
@@ -508,11 +526,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)
*/
@@ -551,6 +567,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..74b41c9 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(
@@ -150,7 +155,7 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
*/
public void setImageToDefault() {
if (mDefaultAvatar == null) {
- mDefaultAvatar = getResources().getDrawable(R.drawable.ic_contact_picture);
+ mDefaultAvatar = mContext.getDrawable(R.drawable.ic_contact_picture);
}
setImageDrawable(mDefaultAvatar);
}
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..90e80d3 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();
@@ -738,18 +741,29 @@ public class RelativeLayout extends ViewGroup {
private int getChildMeasureSpec(int childStart, int childEnd,
int childSize, int startMargin, int endMargin, int startPadding,
int endPadding, int mySize) {
+ int childSpecMode = 0;
+ int childSpecSize = 0;
+
+ // Negative values in a mySize/myWidth/myWidth value in RelativeLayout
+ // measurement is code for, "we got an unspecified mode in the
+ // RelativeLayout's measure spec."
if (mySize < 0 && !mAllowBrokenMeasureSpecs) {
- if (childSize >= 0) {
- return MeasureSpec.makeMeasureSpec(childSize, MeasureSpec.EXACTLY);
+ if (childStart >= 0 && childEnd >= 0) {
+ // Constraints fixed both edges, so child has an exact size.
+ childSpecSize = Math.max(0, childEnd - childStart);
+ childSpecMode = MeasureSpec.EXACTLY;
+ } else if (childSize >= 0) {
+ // The child specified an exact size.
+ childSpecSize = childSize;
+ childSpecMode = MeasureSpec.EXACTLY;
+ } else {
+ // Allow the child to be whatever size it wants.
+ childSpecSize = 0;
+ childSpecMode = MeasureSpec.UNSPECIFIED;
}
- // Negative values in a mySize/myWidth/myWidth value in RelativeLayout measurement
- // is code for, "we got an unspecified mode in the RelativeLayout's measurespec."
- // Carry it forward.
- return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- }
- int childSpecMode = 0;
- int childSpecSize = 0;
+ return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);
+ }
// Figure out start and end bounds.
int tempStart = childStart;
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..1eedc5d 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);
@@ -1050,7 +1059,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
SpannableStringBuilder ssb = new SpannableStringBuilder(" "); // for the icon
ssb.append(hintText);
- Drawable searchIcon = getContext().getResources().getDrawable(getSearchIconId());
+ Drawable searchIcon = getContext().getDrawable(getSearchIconId());
int textSize = (int) (mQueryTextView.getTextSize() * 1.25);
searchIcon.setBounds(0, 0, textSize, textSize);
ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
@@ -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/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java
index bdaaa01..64a1574 100644
--- a/core/java/android/widget/ShareActionProvider.java
+++ b/core/java/android/widget/ShareActionProvider.java
@@ -168,7 +168,7 @@ public class ShareActionProvider extends ActionProvider {
// Lookup and set the expand action icon.
TypedValue outTypedValue = new TypedValue();
mContext.getTheme().resolveAttribute(R.attr.actionModeShareDrawable, outTypedValue, true);
- Drawable drawable = mContext.getResources().getDrawable(outTypedValue.resourceId);
+ Drawable drawable = mContext.getDrawable(outTypedValue.resourceId);
activityChooserView.setExpandActivityOverflowButtonDrawable(drawable);
activityChooserView.setProvider(this);
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/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index b204dfd..1cda631 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -731,10 +731,14 @@ public class SpellChecker implements SpellCheckerSessionListener {
}
}
- if (scheduleOtherSpellCheck) {
+ if (scheduleOtherSpellCheck && wordStart <= end) {
// Update range span: start new spell check from last wordStart
setRangeSpan(editable, wordStart, end);
} else {
+ if (DBG && scheduleOtherSpellCheck) {
+ Log.w(TAG, "Trying to schedule spellcheck for invalid region, from "
+ + wordStart + " to " + end);
+ }
removeRangeSpan(editable);
}
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index 5cbabef..9601d4a 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,
@@ -258,7 +281,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
* @attr ref android.R.styleable#Spinner_popupBackground
*/
public void setPopupBackgroundResource(int resId) {
- setPopupBackgroundDrawable(getContext().getResources().getDrawable(resId));
+ setPopupBackgroundDrawable(getContext().getDrawable(resId));
}
/**
@@ -1033,8 +1056,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/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java
index c44d431..c8917e0 100644
--- a/core/java/android/widget/SuggestionsAdapter.java
+++ b/core/java/android/widget/SuggestionsAdapter.java
@@ -529,7 +529,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
return drawable;
}
// Not cached, find it by resource ID
- drawable = mProviderContext.getResources().getDrawable(resourceId);
+ drawable = mProviderContext.getDrawable(resourceId);
// Stick it in the cache, using the URI as key
storeInIconCache(drawableUri, drawable);
return drawable;
@@ -563,7 +563,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
OpenResourceIdResult r =
mProviderContext.getContentResolver().getResourceId(uri);
try {
- return r.r.getDrawable(r.id);
+ return r.r.getDrawable(r.id, mContext.getTheme());
} catch (Resources.NotFoundException ex) {
throw new FileNotFoundException("Resource does not exist: " + uri);
}
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index e754c17..3d23e4d 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 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 defStyle) {
- super(context, attrs, defStyle);
+ 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);
@@ -389,7 +417,7 @@ public class Switch extends CompoundButton {
* @attr ref android.R.styleable#Switch_track
*/
public void setTrackResource(int resId) {
- setTrackDrawable(getContext().getResources().getDrawable(resId));
+ setTrackDrawable(getContext().getDrawable(resId));
}
/**
@@ -425,7 +453,7 @@ public class Switch extends CompoundButton {
* @attr ref android.R.styleable#Switch_thumb
*/
public void setThumbResource(int resId) {
- setThumbDrawable(getContext().getResources().getDrawable(resId));
+ setThumbDrawable(getContext().getDrawable(resId));
}
/**
@@ -483,15 +511,18 @@ public class Switch extends CompoundButton {
if (mOnLayout == null) {
mOnLayout = makeLayout(mTextOn);
}
+
if (mOffLayout == null) {
mOffLayout = makeLayout(mTextOff);
}
mTrackDrawable.getPadding(mTempRect);
+
final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth());
final int switchWidth = Math.max(mSwitchMinWidth,
maxTextWidth * 2 + mThumbTextPadding * 4 + mTempRect.left + mTempRect.right);
- final int switchHeight = mTrackDrawable.getIntrinsicHeight();
+ final int switchHeight = Math.max(mTrackDrawable.getIntrinsicHeight(),
+ mThumbDrawable.getIntrinsicHeight());
mThumbWidth = maxTextWidth + mThumbTextPadding * 2;
@@ -528,9 +559,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 +609,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 +662,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;
@@ -716,47 +775,51 @@ public class Switch extends CompoundButton {
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
- // Draw the switch
- int switchLeft = mSwitchLeft;
- int switchTop = mSwitchTop;
- int switchRight = mSwitchRight;
- int switchBottom = mSwitchBottom;
-
- mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
- mTrackDrawable.draw(canvas);
+ final Rect tempRect = mTempRect;
+ final Drawable trackDrawable = mTrackDrawable;
+ final Drawable thumbDrawable = mThumbDrawable;
- canvas.save();
-
- mTrackDrawable.getPadding(mTempRect);
- int switchInnerLeft = switchLeft + mTempRect.left;
- int switchInnerTop = switchTop + mTempRect.top;
- int switchInnerRight = switchRight - mTempRect.right;
- int switchInnerBottom = switchBottom - mTempRect.bottom;
+ // Draw the switch
+ final int switchLeft = mSwitchLeft;
+ final int switchTop = mSwitchTop;
+ final int switchRight = mSwitchRight;
+ final int switchBottom = mSwitchBottom;
+ trackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
+ trackDrawable.draw(canvas);
+
+ final int saveCount = canvas.save();
+
+ trackDrawable.getPadding(tempRect);
+ final int switchInnerLeft = switchLeft + tempRect.left;
+ final int switchInnerTop = switchTop + tempRect.top;
+ final int switchInnerRight = switchRight - tempRect.right;
+ final int switchInnerBottom = switchBottom - tempRect.bottom;
canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
- mThumbDrawable.getPadding(mTempRect);
- final int thumbPos = (int) (mThumbPosition + 0.5f);
- int thumbLeft = switchInnerLeft - mTempRect.left + thumbPos;
- int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + mTempRect.right;
+ // Relies on mTempRect, MUST be called first!
+ final int thumbPos = getThumbOffset();
- mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
- mThumbDrawable.draw(canvas);
+ thumbDrawable.getPadding(tempRect);
+ int thumbLeft = switchInnerLeft - tempRect.left + thumbPos;
+ int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + tempRect.right;
+ thumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+ thumbDrawable.draw(canvas);
- // mTextColors should not be null, but just in case
+ final int drawableState[] = getDrawableState();
if (mTextColors != null) {
- mTextPaint.setColor(mTextColors.getColorForState(getDrawableState(),
- mTextColors.getDefaultColor()));
+ mTextPaint.setColor(mTextColors.getColorForState(drawableState, 0));
}
- mTextPaint.drawableState = getDrawableState();
+ mTextPaint.drawableState = drawableState;
- Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
+ final Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
if (switchText != null) {
- canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2,
- (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2);
+ final int left = (thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2;
+ final int top = (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2;
+ canvas.translate(left, top);
switchText.draw(canvas);
}
- canvas.restore();
+ canvas.restoreToCount(saveCount);
}
@Override
@@ -783,6 +846,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 +927,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..47a5449 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));
@@ -116,28 +120,27 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
setChildrenDrawingOrderEnabled(true);
final Context context = mContext;
- final Resources resources = context.getResources();
// Tests the target Sdk version, as set in the Manifest. Could not be set using styles.xml
// in a values-v? directory which targets the current platform Sdk version instead.
if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
// Donut apps get old color scheme
if (mLeftStrip == null) {
- mLeftStrip = resources.getDrawable(
+ mLeftStrip = context.getDrawable(
com.android.internal.R.drawable.tab_bottom_left_v4);
}
if (mRightStrip == null) {
- mRightStrip = resources.getDrawable(
+ mRightStrip = context.getDrawable(
com.android.internal.R.drawable.tab_bottom_right_v4);
}
} else {
// Use modern color scheme for Eclair and beyond
if (mLeftStrip == null) {
- mLeftStrip = resources.getDrawable(
+ mLeftStrip = context.getDrawable(
com.android.internal.R.drawable.tab_bottom_left);
}
if (mRightStrip == null) {
- mRightStrip = resources.getDrawable(
+ mRightStrip = context.getDrawable(
com.android.internal.R.drawable.tab_bottom_right);
}
}
@@ -242,7 +245,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
* divider.
*/
public void setDividerDrawable(int resId) {
- setDividerDrawable(getResources().getDrawable(resId));
+ setDividerDrawable(mContext.getDrawable(resId));
}
/**
@@ -263,7 +266,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
* left strip drawable
*/
public void setLeftStripDrawable(int resId) {
- setLeftStripDrawable(getResources().getDrawable(resId));
+ setLeftStripDrawable(mContext.getDrawable(resId));
}
/**
@@ -284,7 +287,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
* right strip drawable
*/
public void setRightStripDrawable(int resId) {
- setRightStripDrawable(getResources().getDrawable(resId));
+ setRightStripDrawable(mContext.getDrawable(resId));
}
/**
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 37121e2..687036c 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;
@@ -2070,11 +2073,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@android.view.RemotableViewMethod
public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
- final Resources resources = getContext().getResources();
- setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
- top != 0 ? resources.getDrawable(top) : null,
- right != 0 ? resources.getDrawable(right) : null,
- bottom != 0 ? resources.getDrawable(bottom) : null);
+ final Context context = getContext();
+ setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
+ top != 0 ? context.getDrawable(top) : null,
+ right != 0 ? context.getDrawable(right) : null,
+ bottom != 0 ? context.getDrawable(bottom) : null);
}
/**
@@ -2244,12 +2247,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@android.view.RemotableViewMethod
public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end,
int bottom) {
- final Resources resources = getContext().getResources();
+ final Context context = getContext();
setCompoundDrawablesRelativeWithIntrinsicBounds(
- start != 0 ? resources.getDrawable(start) : null,
- top != 0 ? resources.getDrawable(top) : null,
- end != 0 ? resources.getDrawable(end) : null,
- bottom != 0 ? resources.getDrawable(bottom) : null);
+ start != 0 ? context.getDrawable(start) : null,
+ top != 0 ? context.getDrawable(top) : null,
+ end != 0 ? context.getDrawable(end) : null,
+ bottom != 0 ? context.getDrawable(bottom) : null);
}
/**
@@ -4382,8 +4385,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (error == null) {
setError(null, null);
} else {
- Drawable dr = getContext().getResources().
- getDrawable(com.android.internal.R.drawable.indicator_input_error);
+ Drawable dr = getContext().getDrawable(
+ com.android.internal.R.drawable.indicator_input_error);
dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
setError(error, dr);
@@ -4726,10 +4729,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mEditor != null) mEditor.onAttachedToWindow();
}
+ /** @hide */
@Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
+ protected void onDetachedFromWindowInternal() {
if (mPreDrawRegistered) {
getViewTreeObserver().removeOnPreDrawListener(this);
mPreDrawRegistered = false;
@@ -4738,6 +4740,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
resetResolvedDrawables();
if (mEditor != null) mEditor.onDetachedFromWindow();
+
+ super.onDetachedFromWindowInternal();
}
@Override
@@ -4811,6 +4815,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public void invalidateDrawable(Drawable drawable) {
+ boolean handled = false;
+
if (verifyDrawable(drawable)) {
final Rect dirty = drawable.getBounds();
int scrollX = mScrollX;
@@ -4828,6 +4834,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
scrollX += mPaddingLeft;
scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
+ handled = true;
} else if (drawable == drawables.mDrawableRight) {
final int compoundPaddingTop = getCompoundPaddingTop();
final int compoundPaddingBottom = getCompoundPaddingBottom();
@@ -4835,6 +4842,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
+ handled = true;
} else if (drawable == drawables.mDrawableTop) {
final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingRight = getCompoundPaddingRight();
@@ -4842,6 +4850,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
scrollY += mPaddingTop;
+ handled = true;
} else if (drawable == drawables.mDrawableBottom) {
final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingRight = getCompoundPaddingRight();
@@ -4849,11 +4858,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
+ handled = true;
}
}
- invalidate(dirty.left + scrollX, dirty.top + scrollY,
- dirty.right + scrollX, dirty.bottom + scrollY);
+ if (handled) {
+ invalidate(dirty.left + scrollX, dirty.top + scrollY,
+ dirty.right + scrollX, dirty.bottom + scrollY);
+ }
+ }
+
+ if (!handled) {
+ super.invalidateDrawable(drawable);
}
}
@@ -5794,6 +5810,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int end = text.partialEndOffset;
if (end > N) end = N;
removeParcelableSpans(content, start, end);
+ // If start > end, content.replace will swap them before using them.
content.replace(start, end, text.text);
}
}
@@ -8478,7 +8495,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return false;
}
- if (mText.length() > 0 && hasSelection()) {
+ if (mText.length() > 0 && hasSelection() && mEditor != null) {
return true;
}
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..c9a9894
--- /dev/null
+++ b/core/java/android/widget/TimePickerDelegate.java
@@ -0,0 +1,1401 @@
+/*
+ * 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.text.format.DateUtils;
+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;
+import java.util.Locale;
+
+/**
+ * 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;
+
+ private Calendar mTempCalendar;
+
+ 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 void setCurrentLocale(Locale locale) {
+ super.setCurrentLocale(locale);
+ mTempCalendar = Calendar.getInstance(locale);
+ }
+
+ @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 selectedDate = DateUtils.formatDateTime(mContext,
+ mTempCalendar.getTimeInMillis(), flags);
+ event.getText().add(selectedDate);
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ event.setClassName(TimePicker.class.getName());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ info.setClassName(TimePicker.class.getName());
+ }
+
+ /**
+ * 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;
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 066d6c3..cc51a8b 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -42,7 +42,6 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.util.Log;
import android.util.TypedValue;
import android.view.ActionMode;
import android.view.ContextThemeWrapper;
@@ -51,7 +50,6 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewGroup;
import android.view.Window;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AnimationUtils;
@@ -59,6 +57,7 @@ import android.widget.SpinnerAdapter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Map;
/**
* ActionBarImpl is the ActionBar implementation used
@@ -357,6 +356,10 @@ public class ActionBarImpl extends ActionBar {
setSubtitle(mContext.getString(resId));
}
+ public void captureSharedElements(Map<String, View> sharedElements) {
+ mContainerView.findSharedElements(sharedElements);
+ }
+
public void setSelectedNavigationItem(int position) {
switch (mActionView.getNavigationMode()) {
case NAVIGATION_MODE_TABS:
@@ -1083,7 +1086,7 @@ public class ActionBarImpl extends ActionBar {
@Override
public Tab setIcon(int resId) {
- return setIcon(mContext.getResources().getDrawable(resId));
+ return setIcon(mContext.getDrawable(resId));
}
@Override
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index fe532b0..7640749 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -70,6 +70,8 @@ public class AlertController {
private View mView;
+ private int mViewLayoutResId;
+
private int mViewSpacingLeft;
private int mViewSpacingTop;
@@ -126,16 +128,20 @@ public class AlertController {
private Handler mHandler;
- View.OnClickListener mButtonHandler = new View.OnClickListener() {
+ private final View.OnClickListener mButtonHandler = new View.OnClickListener() {
+ @Override
public void onClick(View v) {
- Message m = null;
+ final Message m;
if (v == mButtonPositive && mButtonPositiveMessage != null) {
m = Message.obtain(mButtonPositiveMessage);
} else if (v == mButtonNegative && mButtonNegativeMessage != null) {
m = Message.obtain(mButtonNegativeMessage);
} else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
m = Message.obtain(mButtonNeutralMessage);
+ } else {
+ m = null;
}
+
if (m != null) {
m.sendToTarget();
}
@@ -232,11 +238,6 @@ public class AlertController {
public void installContent() {
/* We use a custom title so never request a window title */
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
-
- if (mView == null || !canTextInput(mView)) {
- mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
- WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
- }
mWindow.setContentView(mAlertDialogLayout);
setupView();
}
@@ -263,10 +264,20 @@ public class AlertController {
}
/**
+ * Set the view resource to display in the dialog.
+ */
+ public void setView(int layoutResId) {
+ mView = null;
+ mViewLayoutResId = layoutResId;
+ mViewSpacingSpecified = false;
+ }
+
+ /**
* Set the view to display in the dialog.
*/
public void setView(View view) {
mView = view;
+ mViewLayoutResId = 0;
mViewSpacingSpecified = false;
}
@@ -276,6 +287,7 @@ public class AlertController {
public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
int viewSpacingBottom) {
mView = view;
+ mViewLayoutResId = 0;
mViewSpacingSpecified = true;
mViewSpacingLeft = viewSpacingLeft;
mViewSpacingTop = viewSpacingTop;
@@ -406,28 +418,44 @@ public class AlertController {
mWindow.setCloseOnTouchOutsideIfNotSet(true);
}
- FrameLayout customPanel = null;
+ final FrameLayout customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
+ final View customView;
if (mView != null) {
- customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
- FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
- custom.addView(mView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ customView = mView;
+ } else if (mViewLayoutResId != 0) {
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ customView = inflater.inflate(mViewLayoutResId, customPanel, false);
+ } else {
+ customView = null;
+ }
+
+ final boolean hasCustomView = customView != null;
+ if (!hasCustomView || !canTextInput(customView)) {
+ mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ }
+
+ if (hasCustomView) {
+ final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
+ custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
if (mViewSpacingSpecified) {
- custom.setPadding(mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
- mViewSpacingBottom);
+ custom.setPadding(
+ mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
}
+
if (mListView != null) {
((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
}
} else {
- mWindow.findViewById(R.id.customPanel).setVisibility(View.GONE);
+ customPanel.setVisibility(View.GONE);
}
-
- /* Only display the divider if we have a title and a
- * custom view or a message.
- */
+
+ // Only display the divider if we have a title and a custom view or a
+ // message.
if (hasTitle) {
- View divider = null;
- if (mMessage != null || mView != null || mListView != null) {
+ final View divider;
+ if (mMessage != null || customView != null || mListView != null) {
divider = mWindow.findViewById(R.id.titleDivider);
} else {
divider = mWindow.findViewById(R.id.titleDividerTop);
@@ -437,8 +465,9 @@ public class AlertController {
divider.setVisibility(View.VISIBLE);
}
}
-
- setBackground(topPanel, contentPanel, customPanel, hasButtons, a, hasTitle, buttonPanel);
+
+ setBackground(a, topPanel, contentPanel, customPanel, buttonPanel, hasTitle, hasCustomView,
+ hasButtons);
a.recycle();
}
@@ -596,76 +625,64 @@ public class AlertController {
}
}
- private void setBackground(LinearLayout topPanel, LinearLayout contentPanel,
- View customPanel, boolean hasButtons, TypedArray a, boolean hasTitle,
- View buttonPanel) {
-
- /* Get all the different background required */
- int fullDark = a.getResourceId(
- R.styleable.AlertDialog_fullDark, R.drawable.popup_full_dark);
- int topDark = a.getResourceId(
- R.styleable.AlertDialog_topDark, R.drawable.popup_top_dark);
- int centerDark = a.getResourceId(
- R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark);
- int bottomDark = a.getResourceId(
- R.styleable.AlertDialog_bottomDark, R.drawable.popup_bottom_dark);
- int fullBright = a.getResourceId(
- R.styleable.AlertDialog_fullBright, R.drawable.popup_full_bright);
- int topBright = a.getResourceId(
+ private void setBackground(TypedArray a, View topPanel, View contentPanel, View customPanel,
+ View buttonPanel, boolean hasTitle, boolean hasCustomView, boolean hasButtons) {
+ final int topBright = a.getResourceId(
R.styleable.AlertDialog_topBright, R.drawable.popup_top_bright);
- int centerBright = a.getResourceId(
+ final int topDark = a.getResourceId(
+ R.styleable.AlertDialog_topDark, R.drawable.popup_top_dark);
+ final int centerBright = a.getResourceId(
R.styleable.AlertDialog_centerBright, R.drawable.popup_center_bright);
- int bottomBright = a.getResourceId(
- R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright);
- int bottomMedium = a.getResourceId(
- R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium);
-
- /*
- * We now set the background of all of the sections of the alert.
+ final int centerDark = a.getResourceId(
+ R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark);
+
+ /* We now set the background of all of the sections of the alert.
* First collect together each section that is being displayed along
* with whether it is on a light or dark background, then run through
* them setting their backgrounds. This is complicated because we need
* to correctly use the full, top, middle, and bottom graphics depending
* on how many views they are and where they appear.
*/
-
- View[] views = new View[4];
- boolean[] light = new boolean[4];
+
+ final View[] views = new View[4];
+ final boolean[] light = new boolean[4];
View lastView = null;
boolean lastLight = false;
-
+
int pos = 0;
if (hasTitle) {
views[pos] = topPanel;
light[pos] = false;
pos++;
}
-
+
/* The contentPanel displays either a custom text message or
* a ListView. If it's text we should use the dark background
* for ListView we should use the light background. If neither
* are there the contentPanel will be hidden so set it as null.
*/
- views[pos] = (contentPanel.getVisibility() == View.GONE)
- ? null : contentPanel;
+ views[pos] = contentPanel.getVisibility() == View.GONE ? null : contentPanel;
light[pos] = mListView != null;
pos++;
- if (customPanel != null) {
+
+ if (hasCustomView) {
views[pos] = customPanel;
light[pos] = mForceInverseBackground;
pos++;
}
+
if (hasButtons) {
views[pos] = buttonPanel;
light[pos] = true;
}
-
+
boolean setView = false;
- for (pos=0; pos<views.length; pos++) {
- View v = views[pos];
+ for (pos = 0; pos < views.length; pos++) {
+ final View v = views[pos];
if (v == null) {
continue;
}
+
if (lastView != null) {
if (!setView) {
lastView.setBackgroundResource(lastLight ? topBright : topDark);
@@ -674,23 +691,34 @@ public class AlertController {
}
setView = true;
}
+
lastView = v;
lastLight = light[pos];
}
-
+
if (lastView != null) {
if (setView) {
-
- /* ListViews will use the Bright background but buttons use
- * the Medium background.
- */
+ final int bottomBright = a.getResourceId(
+ R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright);
+ final int bottomMedium = a.getResourceId(
+ R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium);
+ final int bottomDark = a.getResourceId(
+ R.styleable.AlertDialog_bottomDark, R.drawable.popup_bottom_dark);
+
+ // ListViews will use the Bright background, but buttons use the
+ // Medium background.
lastView.setBackgroundResource(
lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark);
} else {
+ final int fullBright = a.getResourceId(
+ R.styleable.AlertDialog_fullBright, R.drawable.popup_full_bright);
+ final int fullDark = a.getResourceId(
+ R.styleable.AlertDialog_fullDark, R.drawable.popup_full_dark);
+
lastView.setBackgroundResource(lastLight ? fullBright : fullDark);
}
}
-
+
/* TODO: uncomment section below. The logic for this should be if
* it's a Contextual menu being displayed AND only a Cancel button
* is shown then do this.
@@ -715,12 +743,14 @@ public class AlertController {
mListView.addFooterView(buttonPanel);
*/
// }
-
- if ((mListView != null) && (mAdapter != null)) {
- mListView.setAdapter(mAdapter);
- if (mCheckedItem > -1) {
- mListView.setItemChecked(mCheckedItem, true);
- mListView.setSelection(mCheckedItem);
+
+ final ListView listView = mListView;
+ if (listView != null && mAdapter != null) {
+ listView.setAdapter(mAdapter);
+ final int checkedItem = mCheckedItem;
+ if (checkedItem > -1) {
+ listView.setItemChecked(checkedItem, true);
+ listView.setSelection(checkedItem);
}
}
}
@@ -736,8 +766,13 @@ public class AlertController {
super(context, attrs);
}
- public RecycleListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public RecycleListView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public RecycleListView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
@@ -769,6 +804,7 @@ public class AlertController {
public CharSequence[] mItems;
public ListAdapter mAdapter;
public DialogInterface.OnClickListener mOnClickListener;
+ public int mViewLayoutResId;
public View mView;
public int mViewSpacingLeft;
public int mViewSpacingTop;
@@ -854,8 +890,10 @@ public class AlertController {
} else {
dialog.setView(mView);
}
+ } else if (mViewLayoutResId != 0) {
+ dialog.setView(mViewLayoutResId);
}
-
+
/*
dialog.setCancelable(mCancelable);
dialog.setOnCancelListener(mOnCancelListener);
@@ -937,7 +975,8 @@ public class AlertController {
if (mOnClickListener != null) {
listView.setOnItemClickListener(new OnItemClickListener() {
- public void onItemClick(AdapterView parent, View v, int position, long id) {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
mOnClickListener.onClick(dialog.mDialogInterface, position);
if (!mIsSingleChoice) {
dialog.mDialogInterface.dismiss();
@@ -946,7 +985,8 @@ public class AlertController {
});
} else if (mOnCheckboxClickListener != null) {
listView.setOnItemClickListener(new OnItemClickListener() {
- public void onItemClick(AdapterView parent, View v, int position, long id) {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
if (mCheckedItems != null) {
mCheckedItems[position] = listView.isItemChecked(position);
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 70f90d3..1eda373 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -27,8 +27,9 @@ public class ChooserActivity extends ResolverActivity {
Intent intent = getIntent();
Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
if (!(targetParcelable instanceof Intent)) {
- Log.w("ChooseActivity", "Target is not an intent: " + targetParcelable);
+ Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
finish();
+ super.onCreate(null);
return;
}
Intent target = (Intent)targetParcelable;
@@ -42,9 +43,10 @@ public class ChooserActivity extends ResolverActivity {
initialIntents = new Intent[pa.length];
for (int i=0; i<pa.length; i++) {
if (!(pa[i] instanceof Intent)) {
- Log.w("ChooseActivity", "Initial intent #" + i
+ Log.w("ChooserActivity", "Initial intent #" + i
+ " not an Intent: " + pa[i]);
finish();
+ super.onCreate(null);
return;
}
initialIntents[i] = (Intent)pa[i];
diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
index 3d46cdd..83ad9dc 100644
--- a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
+++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
@@ -32,7 +32,6 @@ import android.util.TypedValue;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
-import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 43c4b49..5ba5c57 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -22,19 +22,28 @@ import android.os.WorkSource;
import android.telephony.SignalStrength;
interface IBatteryStats {
- byte[] getStatistics();
- void noteStartWakelock(int uid, int pid, String name, int type);
- void noteStopWakelock(int uid, int pid, String name, int type);
-
- /* DO NOT CHANGE the position of noteStartSensor without updating
- SensorService.cpp */
+ // These first methods are also called by native code, so must
+ // be kept in sync with frameworks/native/include/binder/IBatteryStats.h
void noteStartSensor(int uid, int sensor);
-
- /* DO NOT CHANGE the position of noteStopSensor without updating
- SensorService.cpp */
void noteStopSensor(int uid, int sensor);
- void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, int type);
+ // Remaining methods are only used in Java.
+ byte[] getStatistics();
+
+ void addIsolatedUid(int isolatedUid, int appUid);
+ void removeIsolatedUid(int isolatedUid, int appUid);
+
+ void noteEvent(int code, String name, int uid);
+
+ void noteStartWakelock(int uid, int pid, String name, String historyName,
+ int type, boolean unimportantForLogging);
+ void noteStopWakelock(int uid, int pid, String name, int type);
+
+ void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, String historyName,
+ int type, boolean unimportantForLogging);
+ void noteChangeWakelockFromSource(in WorkSource ws, int pid, String name, int type,
+ in WorkSource newWs, int newPid, String newName,
+ String newHistoryName, int newType, boolean newUnimportantForLogging);
void noteStopWakelockFromSource(in WorkSource ws, int pid, String name, int type);
void noteVibratorOn(int uid, long durationMillis);
@@ -46,6 +55,7 @@ interface IBatteryStats {
void noteScreenOff();
void noteInputEvent();
void noteUserActivity(int uid, int event);
+ void noteDataConnectionActive(int type, boolean active);
void notePhoneOn();
void notePhoneOff();
void notePhoneSignalStrength(in SignalStrength signalStrength);
@@ -56,8 +66,10 @@ interface IBatteryStats {
void noteWifiRunning(in WorkSource ws);
void noteWifiRunningChanged(in WorkSource oldWs, in WorkSource newWs);
void noteWifiStopped(in WorkSource ws);
+ void noteWifiState(int wifiState, String accessPoint);
void noteBluetoothOn();
void noteBluetoothOff();
+ void noteBluetoothState(int bluetoothState);
void noteFullWifiLockAcquired(int uid);
void noteFullWifiLockReleased(int uid);
void noteWifiScanStarted(int uid);
diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java
index 043964f..ec2d654 100644
--- a/core/java/com/android/internal/app/LocalePicker.java
+++ b/core/java/com/android/internal/app/LocalePicker.java
@@ -117,7 +117,7 @@ public class LocalePicker extends ListFragment {
/** - TODO: Enable when zz_ZY Pseudolocale is complete
* if (!localeList.contains("zz_ZY")) {
* localeList.add("zz_ZY");
- * }
+ * }
*/
}
String[] locales = new String[localeList.size()];
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
index ae362af..237feed 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
@@ -21,7 +21,6 @@ import android.app.DialogFragment;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
-import android.view.View.OnClickListener;
/**
* Media route chooser dialog fragment.
diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
index 8fc99c7..b0e0373 100644
--- a/core/java/com/android/internal/app/MediaRouteControllerDialog.java
+++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java
@@ -256,13 +256,13 @@ public class MediaRouteControllerDialog extends Dialog {
private Drawable getIconDrawable() {
if (mRoute.isConnecting()) {
if (mMediaRouteConnectingDrawable == null) {
- mMediaRouteConnectingDrawable = getContext().getResources().getDrawable(
+ mMediaRouteConnectingDrawable = getContext().getDrawable(
R.drawable.ic_media_route_connecting_holo_dark);
}
return mMediaRouteConnectingDrawable;
} else {
if (mMediaRouteOnDrawable == null) {
- mMediaRouteOnDrawable = getContext().getResources().getDrawable(
+ mMediaRouteOnDrawable = getContext().getDrawable(
R.drawable.ic_media_route_on_holo_dark);
}
return mMediaRouteOnDrawable;
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index 40a705c..8cdaf91 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -18,7 +18,6 @@ package com.android.internal.app;
import android.app.Activity;
import android.content.ActivityNotFoundException;
-import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.provider.Settings;
@@ -26,19 +25,15 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.text.method.AllCapsTransformationMethod;
-import android.text.method.TransformationMethod;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.View;
-import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnticipateOvershootInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.TextView;
-import android.widget.Toast;
public class PlatLogoActivity extends Activity {
FrameLayout mContent;
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index a87992a..b1535e3 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -171,7 +171,7 @@ public final class ProcessStats implements Parcelable {
static final String CSV_SEP = "\t";
// Current version of the parcel format.
- private static final int PARCEL_VERSION = 13;
+ private static final int PARCEL_VERSION = 14;
// In-memory Parcel magic number, used to detect attempts to unmarshall bad data
private static final int MAGIC = 0x50535453;
@@ -189,7 +189,8 @@ public final class ProcessStats implements Parcelable {
public String mTimePeriodStartClockStr;
public int mFlags;
- public final ProcessMap<PackageState> mPackages = new ProcessMap<PackageState>();
+ public final ProcessMap<SparseArray<PackageState>> mPackages
+ = new ProcessMap<SparseArray<PackageState>>();
public final ProcessMap<ProcessState> mProcesses = new ProcessMap<ProcessState>();
public final long[] mMemFactorDurations = new long[ADJ_COUNT];
@@ -227,40 +228,45 @@ public final class ProcessStats implements Parcelable {
}
public void add(ProcessStats other) {
- ArrayMap<String, SparseArray<PackageState>> pkgMap = other.mPackages.getMap();
+ ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = other.mPackages.getMap();
for (int ip=0; ip<pkgMap.size(); ip++) {
- String pkgName = pkgMap.keyAt(ip);
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final String pkgName = pkgMap.keyAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
for (int iu=0; iu<uids.size(); iu++) {
- int uid = uids.keyAt(iu);
- PackageState otherState = uids.valueAt(iu);
- final int NPROCS = otherState.mProcesses.size();
- final int NSRVS = otherState.mServices.size();
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState otherProc = otherState.mProcesses.valueAt(iproc);
- if (otherProc.mCommonProcess != otherProc) {
- if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid
- + " proc " + otherProc.mName);
- ProcessState thisProc = getProcessStateLocked(pkgName, uid,
- otherProc.mName);
- if (thisProc.mCommonProcess == thisProc) {
- if (DEBUG) Slog.d(TAG, "Existing process is single-package, splitting");
- thisProc.mMultiPackage = true;
- long now = SystemClock.uptimeMillis();
- final PackageState pkgState = getPackageStateLocked(pkgName, uid);
- thisProc = thisProc.clone(thisProc.mPackage, now);
- pkgState.mProcesses.put(thisProc.mName, thisProc);
+ final int uid = uids.keyAt(iu);
+ final SparseArray<PackageState> versions = uids.valueAt(iu);
+ for (int iv=0; iv<versions.size(); iv++) {
+ final int vers = versions.keyAt(iv);
+ final PackageState otherState = versions.valueAt(iv);
+ final int NPROCS = otherState.mProcesses.size();
+ final int NSRVS = otherState.mServices.size();
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState otherProc = otherState.mProcesses.valueAt(iproc);
+ if (otherProc.mCommonProcess != otherProc) {
+ if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid
+ + " vers " + vers + " proc " + otherProc.mName);
+ ProcessState thisProc = getProcessStateLocked(pkgName, uid, vers,
+ otherProc.mName);
+ if (thisProc.mCommonProcess == thisProc) {
+ if (DEBUG) Slog.d(TAG, "Existing process is single-package, splitting");
+ thisProc.mMultiPackage = true;
+ long now = SystemClock.uptimeMillis();
+ final PackageState pkgState = getPackageStateLocked(pkgName, uid,
+ vers);
+ thisProc = thisProc.clone(thisProc.mPackage, now);
+ pkgState.mProcesses.put(thisProc.mName, thisProc);
+ }
+ thisProc.add(otherProc);
}
- thisProc.add(otherProc);
}
- }
- for (int isvc=0; isvc<NSRVS; isvc++) {
- ServiceState otherSvc = otherState.mServices.valueAt(isvc);
- if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid
- + " service " + otherSvc.mName);
- ServiceState thisSvc = getServiceStateLocked(pkgName, uid,
- otherSvc.mProcessName, otherSvc.mName);
- thisSvc.add(otherSvc);
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ ServiceState otherSvc = otherState.mServices.valueAt(isvc);
+ if (DEBUG) Slog.d(TAG, "Adding pkg " + pkgName + " uid " + uid
+ + " service " + otherSvc.mName);
+ ServiceState thisSvc = getServiceStateLocked(pkgName, uid, vers,
+ otherSvc.mProcessName, otherSvc.mName);
+ thisSvc.add(otherSvc);
+ }
}
}
}
@@ -275,9 +281,11 @@ public final class ProcessStats implements Parcelable {
if (DEBUG) Slog.d(TAG, "Adding uid " + uid + " proc " + otherProc.mName);
if (thisProc == null) {
if (DEBUG) Slog.d(TAG, "Creating new process!");
- thisProc = new ProcessState(this, otherProc.mPackage, uid, otherProc.mName);
+ thisProc = new ProcessState(this, otherProc.mPackage, uid, otherProc.mVersion,
+ otherProc.mName);
mProcesses.put(otherProc.mName, uid, thisProc);
- PackageState thisState = getPackageStateLocked(otherProc.mPackage, uid);
+ PackageState thisState = getPackageStateLocked(otherProc.mPackage, uid,
+ otherProc.mVersion);
if (!thisState.mProcesses.containsKey(otherProc.mName)) {
thisState.mProcesses.put(otherProc.mName, thisProc);
}
@@ -440,7 +448,7 @@ public final class ProcessStats implements Parcelable {
}
static void dumpServiceTimeCheckin(PrintWriter pw, String label, String packageName,
- int uid, String serviceName, ServiceState svc, int serviceType, int opCount,
+ int uid, int vers, String serviceName, ServiceState svc, int serviceType, int opCount,
int curState, long curStartTime, long now) {
if (opCount <= 0) {
return;
@@ -451,6 +459,8 @@ public final class ProcessStats implements Parcelable {
pw.print(",");
pw.print(uid);
pw.print(",");
+ pw.print(vers);
+ pw.print(",");
pw.print(serviceName);
pw.print(",");
pw.print(opCount);
@@ -775,7 +785,7 @@ public final class ProcessStats implements Parcelable {
static void dumpProcessSummaryLocked(PrintWriter pw, String prefix,
ArrayList<ProcessState> procs, int[] screenStates, int[] memStates, int[] procStates,
- long now, long totalTime) {
+ boolean inclUidVers, long now, long totalTime) {
for (int i=procs.size()-1; i>=0; i--) {
ProcessState proc = procs.get(i);
pw.print(prefix);
@@ -783,6 +793,8 @@ public final class ProcessStats implements Parcelable {
pw.print(proc.mName);
pw.print(" / ");
UserHandle.formatUid(pw, proc.mUid);
+ pw.print(" / v");
+ pw.print(proc.mVersion);
pw.println(":");
dumpProcessSummaryDetails(pw, proc, prefix, " TOTAL: ", screenStates, memStates,
procStates, now, totalTime, true);
@@ -869,6 +881,8 @@ public final class ProcessStats implements Parcelable {
pw.print("process");
pw.print(CSV_SEP);
pw.print("uid");
+ pw.print(CSV_SEP);
+ pw.print("vers");
dumpStateHeadersCsv(pw, CSV_SEP, sepScreenStates ? screenStates : null,
sepMemStates ? memStates : null,
sepProcStates ? procStates : null);
@@ -878,6 +892,8 @@ public final class ProcessStats implements Parcelable {
pw.print(proc.mName);
pw.print(CSV_SEP);
UserHandle.formatUid(pw, proc.mUid);
+ pw.print(CSV_SEP);
+ pw.print(proc.mVersion);
dumpProcessStateCsv(pw, proc, sepScreenStates, screenStates,
sepMemStates, memStates, sepProcStates, procStates, now);
pw.println();
@@ -979,53 +995,88 @@ public final class ProcessStats implements Parcelable {
public void resetSafely() {
if (DEBUG) Slog.d(TAG, "Safely resetting state of " + mTimePeriodStartClockStr);
resetCommon();
- long now = SystemClock.uptimeMillis();
- ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+
+ // First initialize use count of all common processes.
+ final long now = SystemClock.uptimeMillis();
+ final ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
for (int ip=procMap.size()-1; ip>=0; ip--) {
- SparseArray<ProcessState> uids = procMap.valueAt(ip);
+ final SparseArray<ProcessState> uids = procMap.valueAt(ip);
for (int iu=uids.size()-1; iu>=0; iu--) {
- ProcessState ps = uids.valueAt(iu);
- if (ps.isInUse()) {
- uids.valueAt(iu).resetSafely(now);
- } else {
- uids.valueAt(iu).makeDead();
+ uids.valueAt(iu).mTmpNumInUse = 0;
+ }
+ }
+
+ // Next reset or prune all per-package processes, and for the ones that are reset
+ // track this back to the common processes.
+ final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
+ for (int ip=pkgMap.size()-1; ip>=0; ip--) {
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
+ for (int iu=uids.size()-1; iu>=0; iu--) {
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ for (int iv=vpkgs.size()-1; iv>=0; iv--) {
+ final PackageState pkgState = vpkgs.valueAt(iv);
+ for (int iproc=pkgState.mProcesses.size()-1; iproc>=0; iproc--) {
+ final ProcessState ps = pkgState.mProcesses.valueAt(iproc);
+ if (ps.isInUse()) {
+ ps.resetSafely(now);
+ ps.mCommonProcess.mTmpNumInUse++;
+ ps.mCommonProcess.mTmpFoundSubProc = ps;
+ } else {
+ pkgState.mProcesses.valueAt(iproc).makeDead();
+ pkgState.mProcesses.removeAt(iproc);
+ }
+ }
+ for (int isvc=pkgState.mServices.size()-1; isvc>=0; isvc--) {
+ final ServiceState ss = pkgState.mServices.valueAt(isvc);
+ if (ss.isInUse()) {
+ ss.resetSafely(now);
+ } else {
+ pkgState.mServices.removeAt(isvc);
+ }
+ }
+ if (pkgState.mProcesses.size() <= 0 && pkgState.mServices.size() <= 0) {
+ vpkgs.removeAt(iv);
+ }
+ }
+ if (vpkgs.size() <= 0) {
uids.removeAt(iu);
}
}
if (uids.size() <= 0) {
- procMap.removeAt(ip);
+ pkgMap.removeAt(ip);
}
}
- ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
- for (int ip=pkgMap.size()-1; ip>=0; ip--) {
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+
+ // Finally prune out any common processes that are no longer in use.
+ for (int ip=procMap.size()-1; ip>=0; ip--) {
+ final SparseArray<ProcessState> uids = procMap.valueAt(ip);
for (int iu=uids.size()-1; iu>=0; iu--) {
- PackageState pkgState = uids.valueAt(iu);
- for (int iproc=pkgState.mProcesses.size()-1; iproc>=0; iproc--) {
- ProcessState ps = pkgState.mProcesses.valueAt(iproc);
- if (ps.isInUse() || ps.mCommonProcess.isInUse()) {
- pkgState.mProcesses.valueAt(iproc).resetSafely(now);
- } else {
- pkgState.mProcesses.valueAt(iproc).makeDead();
- pkgState.mProcesses.removeAt(iproc);
- }
- }
- for (int isvc=pkgState.mServices.size()-1; isvc>=0; isvc--) {
- ServiceState ss = pkgState.mServices.valueAt(isvc);
- if (ss.isInUse()) {
- pkgState.mServices.valueAt(isvc).resetSafely(now);
+ ProcessState ps = uids.valueAt(iu);
+ if (ps.isInUse() || ps.mTmpNumInUse > 0) {
+ // If this is a process for multiple packages, we could at this point
+ // be back down to one package. In that case, we want to revert back
+ // to a single shared ProcessState. We can do this by converting the
+ // current package-specific ProcessState up to the shared ProcessState,
+ // throwing away the current one we have here (because nobody else is
+ // using it).
+ if (!ps.mActive && ps.mMultiPackage && ps.mTmpNumInUse == 1) {
+ // Here we go...
+ ps = ps.mTmpFoundSubProc;
+ ps.mCommonProcess = ps;
+ uids.setValueAt(iu, ps);
} else {
- pkgState.mServices.removeAt(isvc);
+ ps.resetSafely(now);
}
- }
- if (pkgState.mProcesses.size() <= 0 && pkgState.mServices.size() <= 0) {
+ } else {
+ ps.makeDead();
uids.removeAt(iu);
}
}
if (uids.size() <= 0) {
- pkgMap.removeAt(ip);
+ procMap.removeAt(ip);
}
}
+
mStartTime = now;
if (DEBUG) Slog.d(TAG, "State reset; now " + mTimePeriodStartClockStr);
}
@@ -1193,23 +1244,27 @@ public final class ProcessStats implements Parcelable {
uids.valueAt(iu).commitStateTime(now);
}
}
- ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
+ final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
final int NPKG = pkgMap.size();
for (int ip=0; ip<NPKG; ip++) {
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
final int NUID = uids.size();
for (int iu=0; iu<NUID; iu++) {
- PackageState pkgState = uids.valueAt(iu);
- final int NPROCS = pkgState.mProcesses.size();
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (proc.mCommonProcess != proc) {
- proc.commitStateTime(now);
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ final int NVERS = vpkgs.size();
+ for (int iv=0; iv<NVERS; iv++) {
+ PackageState pkgState = vpkgs.valueAt(iv);
+ final int NPROCS = pkgState.mProcesses.size();
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (proc.mCommonProcess != proc) {
+ proc.commitStateTime(now);
+ }
+ }
+ final int NSRVS = pkgState.mServices.size();
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ pkgState.mServices.valueAt(isvc).commitStateTime(now);
}
- }
- final int NSRVS = pkgState.mServices.size();
- for (int isvc=0; isvc<NSRVS; isvc++) {
- pkgState.mServices.valueAt(isvc).commitStateTime(now);
}
}
}
@@ -1239,46 +1294,53 @@ public final class ProcessStats implements Parcelable {
out.writeInt(NPROC);
for (int ip=0; ip<NPROC; ip++) {
writeCommonString(out, procMap.keyAt(ip));
- SparseArray<ProcessState> uids = procMap.valueAt(ip);
+ final SparseArray<ProcessState> uids = procMap.valueAt(ip);
final int NUID = uids.size();
out.writeInt(NUID);
for (int iu=0; iu<NUID; iu++) {
out.writeInt(uids.keyAt(iu));
- ProcessState proc = uids.valueAt(iu);
+ final ProcessState proc = uids.valueAt(iu);
writeCommonString(out, proc.mPackage);
+ out.writeInt(proc.mVersion);
proc.writeToParcel(out, now);
}
}
out.writeInt(NPKG);
for (int ip=0; ip<NPKG; ip++) {
writeCommonString(out, pkgMap.keyAt(ip));
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
final int NUID = uids.size();
out.writeInt(NUID);
for (int iu=0; iu<NUID; iu++) {
out.writeInt(uids.keyAt(iu));
- PackageState pkgState = uids.valueAt(iu);
- final int NPROCS = pkgState.mProcesses.size();
- out.writeInt(NPROCS);
- for (int iproc=0; iproc<NPROCS; iproc++) {
- writeCommonString(out, pkgState.mProcesses.keyAt(iproc));
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (proc.mCommonProcess == proc) {
- // This is the same as the common process we wrote above.
- out.writeInt(0);
- } else {
- // There is separate data for this package's process.
- out.writeInt(1);
- proc.writeToParcel(out, now);
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ final int NVERS = vpkgs.size();
+ out.writeInt(NVERS);
+ for (int iv=0; iv<NVERS; iv++) {
+ out.writeInt(vpkgs.keyAt(iv));
+ final PackageState pkgState = vpkgs.valueAt(iv);
+ final int NPROCS = pkgState.mProcesses.size();
+ out.writeInt(NPROCS);
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ writeCommonString(out, pkgState.mProcesses.keyAt(iproc));
+ final ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (proc.mCommonProcess == proc) {
+ // This is the same as the common process we wrote above.
+ out.writeInt(0);
+ } else {
+ // There is separate data for this package's process.
+ out.writeInt(1);
+ proc.writeToParcel(out, now);
+ }
+ }
+ final int NSRVS = pkgState.mServices.size();
+ out.writeInt(NSRVS);
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ out.writeString(pkgState.mServices.keyAt(isvc));
+ final ServiceState svc = pkgState.mServices.valueAt(isvc);
+ writeCommonString(out, svc.mProcessName);
+ svc.writeToParcel(out, now);
}
- }
- final int NSRVS = pkgState.mServices.size();
- out.writeInt(NSRVS);
- for (int isvc=0; isvc<NSRVS; isvc++) {
- out.writeString(pkgState.mServices.keyAt(isvc));
- ServiceState svc = pkgState.mServices.valueAt(isvc);
- writeCommonString(out, svc.mProcessName);
- svc.writeToParcel(out, now);
}
}
}
@@ -1396,7 +1458,7 @@ public final class ProcessStats implements Parcelable {
}
while (NPROC > 0) {
NPROC--;
- String procName = readCommonString(in, version);
+ final String procName = readCommonString(in, version);
if (procName == null) {
mReadError = "bad process name";
return;
@@ -1408,23 +1470,24 @@ public final class ProcessStats implements Parcelable {
}
while (NUID > 0) {
NUID--;
- int uid = in.readInt();
+ final int uid = in.readInt();
if (uid < 0) {
mReadError = "bad uid: " + uid;
return;
}
- String pkgName = readCommonString(in, version);
+ final String pkgName = readCommonString(in, version);
if (pkgName == null) {
mReadError = "bad process package name";
return;
}
+ final int vers = in.readInt();
ProcessState proc = hadData ? mProcesses.get(procName, uid) : null;
if (proc != null) {
if (!proc.readFromParcel(in, false)) {
return;
}
} else {
- proc = new ProcessState(this, pkgName, uid, procName);
+ proc = new ProcessState(this, pkgName, uid, vers, procName);
if (!proc.readFromParcel(in, true)) {
return;
}
@@ -1444,7 +1507,7 @@ public final class ProcessStats implements Parcelable {
}
while (NPKG > 0) {
NPKG--;
- String pkgName = readCommonString(in, version);
+ final String pkgName = readCommonString(in, version);
if (pkgName == null) {
mReadError = "bad package name";
return;
@@ -1456,83 +1519,98 @@ public final class ProcessStats implements Parcelable {
}
while (NUID > 0) {
NUID--;
- int uid = in.readInt();
+ final int uid = in.readInt();
if (uid < 0) {
mReadError = "bad uid: " + uid;
return;
}
- PackageState pkgState = new PackageState(pkgName, uid);
- mPackages.put(pkgName, uid, pkgState);
- int NPROCS = in.readInt();
- if (NPROCS < 0) {
- mReadError = "bad package process count: " + NPROCS;
+ int NVERS = in.readInt();
+ if (NVERS < 0) {
+ mReadError = "bad versions count: " + NVERS;
return;
}
- while (NPROCS > 0) {
- NPROCS--;
- String procName = readCommonString(in, version);
- if (procName == null) {
- mReadError = "bad package process name";
- return;
+ while (NVERS > 0) {
+ NVERS--;
+ final int vers = in.readInt();
+ PackageState pkgState = new PackageState(pkgName, uid);
+ SparseArray<PackageState> vpkg = mPackages.get(pkgName, uid);
+ if (vpkg == null) {
+ vpkg = new SparseArray<PackageState>();
+ mPackages.put(pkgName, uid, vpkg);
}
- int hasProc = in.readInt();
- if (DEBUG_PARCEL) Slog.d(TAG, "Reading package " + pkgName + " " + uid
- + " process " + procName + " hasProc=" + hasProc);
- ProcessState commonProc = mProcesses.get(procName, uid);
- if (DEBUG_PARCEL) Slog.d(TAG, "Got common proc " + procName + " " + uid
- + ": " + commonProc);
- if (commonProc == null) {
- mReadError = "no common proc: " + procName;
+ vpkg.put(vers, pkgState);
+ int NPROCS = in.readInt();
+ if (NPROCS < 0) {
+ mReadError = "bad package process count: " + NPROCS;
return;
}
- if (hasProc != 0) {
- // The process for this package is unique to the package; we
- // need to load it. We don't need to do anything about it if
- // it is not unique because if someone later looks for it
- // they will find and use it from the global procs.
- ProcessState proc = hadData ? pkgState.mProcesses.get(procName) : null;
- if (proc != null) {
- if (!proc.readFromParcel(in, false)) {
- return;
+ while (NPROCS > 0) {
+ NPROCS--;
+ String procName = readCommonString(in, version);
+ if (procName == null) {
+ mReadError = "bad package process name";
+ return;
+ }
+ int hasProc = in.readInt();
+ if (DEBUG_PARCEL) Slog.d(TAG, "Reading package " + pkgName + " " + uid
+ + " process " + procName + " hasProc=" + hasProc);
+ ProcessState commonProc = mProcesses.get(procName, uid);
+ if (DEBUG_PARCEL) Slog.d(TAG, "Got common proc " + procName + " " + uid
+ + ": " + commonProc);
+ if (commonProc == null) {
+ mReadError = "no common proc: " + procName;
+ return;
+ }
+ if (hasProc != 0) {
+ // The process for this package is unique to the package; we
+ // need to load it. We don't need to do anything about it if
+ // it is not unique because if someone later looks for it
+ // they will find and use it from the global procs.
+ ProcessState proc = hadData ? pkgState.mProcesses.get(procName) : null;
+ if (proc != null) {
+ if (!proc.readFromParcel(in, false)) {
+ return;
+ }
+ } else {
+ proc = new ProcessState(commonProc, pkgName, uid, vers, procName,
+ 0);
+ if (!proc.readFromParcel(in, true)) {
+ return;
+ }
}
+ if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: "
+ + procName + " " + uid + " " + proc);
+ pkgState.mProcesses.put(procName, proc);
} else {
- proc = new ProcessState(commonProc, pkgName, uid, procName, 0);
- if (!proc.readFromParcel(in, true)) {
- return;
- }
+ if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: "
+ + procName + " " + uid + " " + commonProc);
+ pkgState.mProcesses.put(procName, commonProc);
}
- if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: "
- + procName + " " + uid + " " + proc);
- pkgState.mProcesses.put(procName, proc);
- } else {
- if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " process: "
- + procName + " " + uid + " " + commonProc);
- pkgState.mProcesses.put(procName, commonProc);
}
- }
- int NSRVS = in.readInt();
- if (NSRVS < 0) {
- mReadError = "bad package service count: " + NSRVS;
- return;
- }
- while (NSRVS > 0) {
- NSRVS--;
- String serviceName = in.readString();
- if (serviceName == null) {
- mReadError = "bad package service name";
+ int NSRVS = in.readInt();
+ if (NSRVS < 0) {
+ mReadError = "bad package service count: " + NSRVS;
return;
}
- String processName = version > 9 ? readCommonString(in, version) : null;
- ServiceState serv = hadData ? pkgState.mServices.get(serviceName) : null;
- if (serv == null) {
- serv = new ServiceState(this, pkgName, serviceName, processName, null);
- }
- if (!serv.readFromParcel(in)) {
- return;
+ while (NSRVS > 0) {
+ NSRVS--;
+ String serviceName = in.readString();
+ if (serviceName == null) {
+ mReadError = "bad package service name";
+ return;
+ }
+ String processName = version > 9 ? readCommonString(in, version) : null;
+ ServiceState serv = hadData ? pkgState.mServices.get(serviceName) : null;
+ if (serv == null) {
+ serv = new ServiceState(this, pkgName, serviceName, processName, null);
+ }
+ if (!serv.readFromParcel(in)) {
+ return;
+ }
+ if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " service: "
+ + serviceName + " " + uid + " " + serv);
+ pkgState.mServices.put(serviceName, serv);
}
- if (DEBUG_PARCEL) Slog.d(TAG, "Adding package " + pkgName + " service: "
- + serviceName + " " + uid + " " + serv);
- pkgState.mServices.put(serviceName, serv);
}
}
}
@@ -1627,30 +1705,36 @@ public final class ProcessStats implements Parcelable {
return ~lo; // value not present
}
- public PackageState getPackageStateLocked(String packageName, int uid) {
- PackageState as = mPackages.get(packageName, uid);
+ public PackageState getPackageStateLocked(String packageName, int uid, int vers) {
+ SparseArray<PackageState> vpkg = mPackages.get(packageName, uid);
+ if (vpkg == null) {
+ vpkg = new SparseArray<PackageState>();
+ mPackages.put(packageName, uid, vpkg);
+ }
+ PackageState as = vpkg.get(vers);
if (as != null) {
return as;
}
as = new PackageState(packageName, uid);
- mPackages.put(packageName, uid, as);
+ vpkg.put(vers, as);
return as;
}
- public ProcessState getProcessStateLocked(String packageName, int uid, String processName) {
- final PackageState pkgState = getPackageStateLocked(packageName, uid);
+ public ProcessState getProcessStateLocked(String packageName, int uid, int vers,
+ String processName) {
+ final PackageState pkgState = getPackageStateLocked(packageName, uid, vers);
ProcessState ps = pkgState.mProcesses.get(processName);
if (ps != null) {
return ps;
}
ProcessState commonProc = mProcesses.get(processName, uid);
if (commonProc == null) {
- commonProc = new ProcessState(this, packageName, uid, processName);
+ commonProc = new ProcessState(this, packageName, uid, vers, processName);
mProcesses.put(processName, uid, commonProc);
if (DEBUG) Slog.d(TAG, "GETPROC created new common " + commonProc);
}
if (!commonProc.mMultiPackage) {
- if (packageName.equals(commonProc.mPackage)) {
+ if (packageName.equals(commonProc.mPackage) && vers == commonProc.mVersion) {
// This common process is not in use by multiple packages, and
// is for the calling package, so we can just use it directly.
ps = commonProc;
@@ -1668,7 +1752,8 @@ public final class ProcessStats implements Parcelable {
long now = SystemClock.uptimeMillis();
// First let's make a copy of the current process state and put
// that under the now unique state for its original package name.
- final PackageState commonPkgState = getPackageStateLocked(commonProc.mPackage, uid);
+ final PackageState commonPkgState = getPackageStateLocked(commonProc.mPackage,
+ uid, commonProc.mVersion);
if (commonPkgState != null) {
ProcessState cloned = commonProc.clone(commonProc.mPackage, now);
if (DEBUG) Slog.d(TAG, "GETPROC setting clone to pkg " + commonProc.mPackage
@@ -1691,13 +1776,13 @@ public final class ProcessStats implements Parcelable {
+ "/" + uid + " for proc " + commonProc.mName);
}
// And now make a fresh new process state for the new package name.
- ps = new ProcessState(commonProc, packageName, uid, processName, now);
+ ps = new ProcessState(commonProc, packageName, uid, vers, processName, now);
if (DEBUG) Slog.d(TAG, "GETPROC created new pkg " + ps);
}
} else {
// The common process is for multiple packages, we need to create a
// separate object for the per-package data.
- ps = new ProcessState(commonProc, packageName, uid, processName,
+ ps = new ProcessState(commonProc, packageName, uid, vers, processName,
SystemClock.uptimeMillis());
if (DEBUG) Slog.d(TAG, "GETPROC created new pkg " + ps);
}
@@ -1706,16 +1791,16 @@ public final class ProcessStats implements Parcelable {
return ps;
}
- public ProcessStats.ServiceState getServiceStateLocked(String packageName, int uid,
+ public ProcessStats.ServiceState getServiceStateLocked(String packageName, int uid, int vers,
String processName, String className) {
- final ProcessStats.PackageState as = getPackageStateLocked(packageName, uid);
+ final ProcessStats.PackageState as = getPackageStateLocked(packageName, uid, vers);
ProcessStats.ServiceState ss = as.mServices.get(className);
if (ss != null) {
if (DEBUG) Slog.d(TAG, "GETSVC: returning existing " + ss);
return ss;
}
final ProcessStats.ProcessState ps = processName != null
- ? getProcessStateLocked(packageName, uid, processName) : null;
+ ? getProcessStateLocked(packageName, uid, vers, processName) : null;
ss = new ProcessStats.ServiceState(this, packageName, className, processName, ps);
as.mServices.put(className, ss);
if (DEBUG) Slog.d(TAG, "GETSVC: creating " + ss + " in " + ps);
@@ -1756,119 +1841,124 @@ public final class ProcessStats implements Parcelable {
boolean dumpAll, boolean activeOnly) {
long totalTime = dumpSingleTime(null, null, mMemFactorDurations, mMemFactor,
mStartTime, now);
- ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
+ ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
boolean printedHeader = false;
boolean sepNeeded = false;
for (int ip=0; ip<pkgMap.size(); ip++) {
final String pkgName = pkgMap.keyAt(ip);
- final SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
for (int iu=0; iu<uids.size(); iu++) {
final int uid = uids.keyAt(iu);
- final PackageState pkgState = uids.valueAt(iu);
- final int NPROCS = pkgState.mProcesses.size();
- final int NSRVS = pkgState.mServices.size();
- final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
- if (!pkgMatch) {
- boolean procMatch = false;
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (reqPackage.equals(proc.mName)) {
- procMatch = true;
- break;
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ for (int iv=0; iv<vpkgs.size(); iv++) {
+ final int vers = vpkgs.keyAt(iv);
+ final PackageState pkgState = vpkgs.valueAt(iv);
+ final int NPROCS = pkgState.mProcesses.size();
+ final int NSRVS = pkgState.mServices.size();
+ final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
+ if (!pkgMatch) {
+ boolean procMatch = false;
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (reqPackage.equals(proc.mName)) {
+ procMatch = true;
+ break;
+ }
+ }
+ if (!procMatch) {
+ continue;
}
}
- if (!procMatch) {
- continue;
+ if (NPROCS > 0 || NSRVS > 0) {
+ if (!printedHeader) {
+ pw.println("Per-Package Stats:");
+ printedHeader = true;
+ sepNeeded = true;
+ }
+ pw.print(" * "); pw.print(pkgName); pw.print(" / ");
+ UserHandle.formatUid(pw, uid); pw.print(" / v");
+ pw.print(vers); pw.println(":");
}
- }
- if (NPROCS > 0 || NSRVS > 0) {
- if (!printedHeader) {
- pw.println("Per-Package Stats:");
- printedHeader = true;
- sepNeeded = true;
+ if (!dumpSummary || dumpAll) {
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+ continue;
+ }
+ if (activeOnly && !proc.isInUse()) {
+ pw.print(" (Not active: ");
+ pw.print(pkgState.mProcesses.keyAt(iproc)); pw.println(")");
+ continue;
+ }
+ pw.print(" Process ");
+ pw.print(pkgState.mProcesses.keyAt(iproc));
+ if (proc.mCommonProcess.mMultiPackage) {
+ pw.print(" (multi, ");
+ } else {
+ pw.print(" (unique, ");
+ }
+ pw.print(proc.mDurationsTableSize);
+ pw.print(" entries)");
+ pw.println(":");
+ dumpProcessState(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
+ ALL_PROC_STATES, now);
+ dumpProcessPss(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
+ ALL_PROC_STATES);
+ dumpProcessInternalLocked(pw, " ", proc, dumpAll);
+ }
+ } else {
+ ArrayList<ProcessState> procs = new ArrayList<ProcessState>();
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+ continue;
+ }
+ if (activeOnly && !proc.isInUse()) {
+ continue;
+ }
+ procs.add(proc);
+ }
+ dumpProcessSummaryLocked(pw, " ", procs, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
+ NON_CACHED_PROC_STATES, false, now, totalTime);
}
- pw.print(" * "); pw.print(pkgName); pw.print(" / ");
- UserHandle.formatUid(pw, uid); pw.println(":");
- }
- if (!dumpSummary || dumpAll) {
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ ServiceState svc = pkgState.mServices.valueAt(isvc);
+ if (!pkgMatch && !reqPackage.equals(svc.mProcessName)) {
continue;
}
- if (activeOnly && !proc.isInUse()) {
+ if (activeOnly && !svc.isInUse()) {
pw.print(" (Not active: ");
- pw.print(pkgState.mProcesses.keyAt(iproc)); pw.println(")");
+ pw.print(pkgState.mServices.keyAt(isvc)); pw.println(")");
continue;
}
- pw.print(" Process ");
- pw.print(pkgState.mProcesses.keyAt(iproc));
- if (proc.mCommonProcess.mMultiPackage) {
- pw.print(" (multi, ");
+ if (dumpAll) {
+ pw.print(" Service ");
} else {
- pw.print(" (unique, ");
+ pw.print(" * ");
}
- pw.print(proc.mDurationsTableSize);
- pw.print(" entries)");
+ pw.print(pkgState.mServices.keyAt(isvc));
pw.println(":");
- dumpProcessState(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
- ALL_PROC_STATES, now);
- dumpProcessPss(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
- ALL_PROC_STATES);
- dumpProcessInternalLocked(pw, " ", proc, dumpAll);
- }
- } else {
- ArrayList<ProcessState> procs = new ArrayList<ProcessState>();
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- if (!pkgMatch && !reqPackage.equals(proc.mName)) {
- continue;
- }
- if (activeOnly && !proc.isInUse()) {
- continue;
- }
- procs.add(proc);
- }
- dumpProcessSummaryLocked(pw, " ", procs, ALL_SCREEN_ADJ, ALL_MEM_ADJ,
- NON_CACHED_PROC_STATES, now, totalTime);
- }
- for (int isvc=0; isvc<NSRVS; isvc++) {
- ServiceState svc = pkgState.mServices.valueAt(isvc);
- if (!pkgMatch && !reqPackage.equals(svc.mProcessName)) {
- continue;
- }
- if (activeOnly && !svc.isInUse()) {
- pw.print(" (Not active: ");
- pw.print(pkgState.mServices.keyAt(isvc)); pw.println(")");
- continue;
- }
- if (dumpAll) {
- pw.print(" Service ");
- } else {
- pw.print(" * ");
- }
- pw.print(pkgState.mServices.keyAt(isvc));
- pw.println(":");
- pw.print(" Process: "); pw.println(svc.mProcessName);
- dumpServiceStats(pw, " ", " ", " ", "Running", svc,
- svc.mRunCount, ServiceState.SERVICE_RUN, svc.mRunState,
- svc.mRunStartTime, now, totalTime, !dumpSummary || dumpAll);
- dumpServiceStats(pw, " ", " ", " ", "Started", svc,
- svc.mStartedCount, ServiceState.SERVICE_STARTED, svc.mStartedState,
- svc.mStartedStartTime, now, totalTime, !dumpSummary || dumpAll);
- dumpServiceStats(pw, " ", " ", " ", "Bound", svc,
- svc.mBoundCount, ServiceState.SERVICE_BOUND, svc.mBoundState,
- svc.mBoundStartTime, now, totalTime, !dumpSummary || dumpAll);
- dumpServiceStats(pw, " ", " ", " ", "Executing", svc,
- svc.mExecCount, ServiceState.SERVICE_EXEC, svc.mExecState,
- svc.mExecStartTime, now, totalTime, !dumpSummary || dumpAll);
- if (dumpAll) {
- if (svc.mOwner != null) {
- pw.print(" mOwner="); pw.println(svc.mOwner);
- }
- if (svc.mStarted || svc.mRestarting) {
- pw.print(" mStarted="); pw.print(svc.mStarted);
- pw.print(" mRestarting="); pw.println(svc.mRestarting);
+ pw.print(" Process: "); pw.println(svc.mProcessName);
+ dumpServiceStats(pw, " ", " ", " ", "Running", svc,
+ svc.mRunCount, ServiceState.SERVICE_RUN, svc.mRunState,
+ svc.mRunStartTime, now, totalTime, !dumpSummary || dumpAll);
+ dumpServiceStats(pw, " ", " ", " ", "Started", svc,
+ svc.mStartedCount, ServiceState.SERVICE_STARTED, svc.mStartedState,
+ svc.mStartedStartTime, now, totalTime, !dumpSummary || dumpAll);
+ dumpServiceStats(pw, " ", " ", " ", "Bound", svc,
+ svc.mBoundCount, ServiceState.SERVICE_BOUND, svc.mBoundState,
+ svc.mBoundStartTime, now, totalTime, !dumpSummary || dumpAll);
+ dumpServiceStats(pw, " ", " ", " ", "Executing", svc,
+ svc.mExecCount, ServiceState.SERVICE_EXEC, svc.mExecState,
+ svc.mExecStartTime, now, totalTime, !dumpSummary || dumpAll);
+ if (dumpAll) {
+ if (svc.mOwner != null) {
+ pw.print(" mOwner="); pw.println(svc.mOwner);
+ }
+ if (svc.mStarted || svc.mRestarting) {
+ pw.print(" mStarted="); pw.print(svc.mStarted);
+ pw.print(" mRestarting="); pw.println(svc.mRestarting);
+ }
}
}
}
@@ -2059,7 +2149,7 @@ public final class ProcessStats implements Parcelable {
pw.println(header);
}
dumpProcessSummaryLocked(pw, prefix, procs, screenStates, memStates,
- sortProcStates, now, totalTime);
+ sortProcStates, true, now, totalTime);
}
}
@@ -2067,23 +2157,27 @@ public final class ProcessStats implements Parcelable {
int[] procStates, int sortProcStates[], long now, String reqPackage,
boolean activeOnly) {
final ArraySet<ProcessState> foundProcs = new ArraySet<ProcessState>();
- final ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
+ final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
for (int ip=0; ip<pkgMap.size(); ip++) {
final String pkgName = pkgMap.keyAt(ip);
- final SparseArray<PackageState> procs = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> procs = pkgMap.valueAt(ip);
for (int iu=0; iu<procs.size(); iu++) {
- final PackageState state = procs.valueAt(iu);
- final int NPROCS = state.mProcesses.size();
- final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
- for (int iproc=0; iproc<NPROCS; iproc++) {
- final ProcessState proc = state.mProcesses.valueAt(iproc);
- if (!pkgMatch && !reqPackage.equals(proc.mName)) {
- continue;
- }
- if (activeOnly && !proc.isInUse()) {
- continue;
+ final SparseArray<PackageState> vpkgs = procs.valueAt(iu);
+ final int NVERS = vpkgs.size();
+ for (int iv=0; iv<NVERS; iv++) {
+ final PackageState state = vpkgs.valueAt(iv);
+ final int NPROCS = state.mProcesses.size();
+ final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ final ProcessState proc = state.mProcesses.valueAt(iproc);
+ if (!pkgMatch && !reqPackage.equals(proc.mName)) {
+ continue;
+ }
+ if (activeOnly && !proc.isInUse()) {
+ continue;
+ }
+ foundProcs.add(proc.mCommonProcess);
}
- foundProcs.add(proc.mCommonProcess);
}
}
}
@@ -2128,8 +2222,8 @@ public final class ProcessStats implements Parcelable {
public void dumpCheckinLocked(PrintWriter pw, String reqPackage) {
final long now = SystemClock.uptimeMillis();
- ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
- pw.println("vers,3");
+ final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
+ pw.println("vers,4");
pw.print("period,"); pw.print(mTimePeriodStartClockStr);
pw.print(","); pw.print(mTimePeriodStartRealtime); pw.print(",");
pw.print(mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime);
@@ -2152,75 +2246,85 @@ public final class ProcessStats implements Parcelable {
pw.println();
pw.print("config,"); pw.print(mRuntime); pw.print(','); pw.println(mWebView);
for (int ip=0; ip<pkgMap.size(); ip++) {
- String pkgName = pkgMap.keyAt(ip);
+ final String pkgName = pkgMap.keyAt(ip);
if (reqPackage != null && !reqPackage.equals(pkgName)) {
continue;
}
- SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+ final SparseArray<SparseArray<PackageState>> uids = pkgMap.valueAt(ip);
for (int iu=0; iu<uids.size(); iu++) {
- int uid = uids.keyAt(iu);
- PackageState pkgState = uids.valueAt(iu);
- final int NPROCS = pkgState.mProcesses.size();
- final int NSRVS = pkgState.mServices.size();
- for (int iproc=0; iproc<NPROCS; iproc++) {
- ProcessState proc = pkgState.mProcesses.valueAt(iproc);
- pw.print("pkgproc,");
- pw.print(pkgName);
- pw.print(",");
- pw.print(uid);
- pw.print(",");
- pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
- dumpAllProcessStateCheckin(pw, proc, now);
- pw.println();
- if (proc.mPssTableSize > 0) {
- pw.print("pkgpss,");
+ final int uid = uids.keyAt(iu);
+ final SparseArray<PackageState> vpkgs = uids.valueAt(iu);
+ for (int iv=0; iv<vpkgs.size(); iv++) {
+ final int vers = vpkgs.keyAt(iv);
+ final PackageState pkgState = vpkgs.valueAt(iv);
+ final int NPROCS = pkgState.mProcesses.size();
+ final int NSRVS = pkgState.mServices.size();
+ for (int iproc=0; iproc<NPROCS; iproc++) {
+ ProcessState proc = pkgState.mProcesses.valueAt(iproc);
+ pw.print("pkgproc,");
pw.print(pkgName);
pw.print(",");
pw.print(uid);
pw.print(",");
- pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
- dumpAllProcessPssCheckin(pw, proc);
- pw.println();
- }
- if (proc.mNumExcessiveWake > 0 || proc.mNumExcessiveCpu > 0
- || proc.mNumCachedKill > 0) {
- pw.print("pkgkills,");
- pw.print(pkgName);
- pw.print(",");
- pw.print(uid);
+ pw.print(vers);
pw.print(",");
pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
- pw.print(",");
- pw.print(proc.mNumExcessiveWake);
- pw.print(",");
- pw.print(proc.mNumExcessiveCpu);
- pw.print(",");
- pw.print(proc.mNumCachedKill);
- pw.print(",");
- pw.print(proc.mMinCachedKillPss);
- pw.print(":");
- pw.print(proc.mAvgCachedKillPss);
- pw.print(":");
- pw.print(proc.mMaxCachedKillPss);
+ dumpAllProcessStateCheckin(pw, proc, now);
pw.println();
+ if (proc.mPssTableSize > 0) {
+ pw.print("pkgpss,");
+ pw.print(pkgName);
+ pw.print(",");
+ pw.print(uid);
+ pw.print(",");
+ pw.print(vers);
+ pw.print(",");
+ pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
+ dumpAllProcessPssCheckin(pw, proc);
+ pw.println();
+ }
+ if (proc.mNumExcessiveWake > 0 || proc.mNumExcessiveCpu > 0
+ || proc.mNumCachedKill > 0) {
+ pw.print("pkgkills,");
+ pw.print(pkgName);
+ pw.print(",");
+ pw.print(uid);
+ pw.print(",");
+ pw.print(vers);
+ pw.print(",");
+ pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
+ pw.print(",");
+ pw.print(proc.mNumExcessiveWake);
+ pw.print(",");
+ pw.print(proc.mNumExcessiveCpu);
+ pw.print(",");
+ pw.print(proc.mNumCachedKill);
+ pw.print(",");
+ pw.print(proc.mMinCachedKillPss);
+ pw.print(":");
+ pw.print(proc.mAvgCachedKillPss);
+ pw.print(":");
+ pw.print(proc.mMaxCachedKillPss);
+ pw.println();
+ }
+ }
+ for (int isvc=0; isvc<NSRVS; isvc++) {
+ String serviceName = collapseString(pkgName,
+ pkgState.mServices.keyAt(isvc));
+ ServiceState svc = pkgState.mServices.valueAt(isvc);
+ dumpServiceTimeCheckin(pw, "pkgsvc-run", pkgName, uid, vers, serviceName,
+ svc, ServiceState.SERVICE_RUN, svc.mRunCount,
+ svc.mRunState, svc.mRunStartTime, now);
+ dumpServiceTimeCheckin(pw, "pkgsvc-start", pkgName, uid, vers, serviceName,
+ svc, ServiceState.SERVICE_STARTED, svc.mStartedCount,
+ svc.mStartedState, svc.mStartedStartTime, now);
+ dumpServiceTimeCheckin(pw, "pkgsvc-bound", pkgName, uid, vers, serviceName,
+ svc, ServiceState.SERVICE_BOUND, svc.mBoundCount,
+ svc.mBoundState, svc.mBoundStartTime, now);
+ dumpServiceTimeCheckin(pw, "pkgsvc-exec", pkgName, uid, vers, serviceName,
+ svc, ServiceState.SERVICE_EXEC, svc.mExecCount,
+ svc.mExecState, svc.mExecStartTime, now);
}
- }
- for (int isvc=0; isvc<NSRVS; isvc++) {
- String serviceName = collapseString(pkgName,
- pkgState.mServices.keyAt(isvc));
- ServiceState svc = pkgState.mServices.valueAt(isvc);
- dumpServiceTimeCheckin(pw, "pkgsvc-run", pkgName, uid, serviceName,
- svc, ServiceState.SERVICE_RUN, svc.mRunCount,
- svc.mRunState, svc.mRunStartTime, now);
- dumpServiceTimeCheckin(pw, "pkgsvc-start", pkgName, uid, serviceName,
- svc, ServiceState.SERVICE_STARTED, svc.mStartedCount,
- svc.mStartedState, svc.mStartedStartTime, now);
- dumpServiceTimeCheckin(pw, "pkgsvc-bound", pkgName, uid, serviceName,
- svc, ServiceState.SERVICE_BOUND, svc.mBoundCount,
- svc.mBoundState, svc.mBoundStartTime, now);
- dumpServiceTimeCheckin(pw, "pkgsvc-exec", pkgName, uid, serviceName,
- svc, ServiceState.SERVICE_EXEC, svc.mExecCount,
- svc.mExecState, svc.mExecStartTime, now);
}
}
}
@@ -2364,9 +2468,10 @@ public final class ProcessStats implements Parcelable {
}
public static final class ProcessState extends DurationsTable {
- public final ProcessState mCommonProcess;
+ public ProcessState mCommonProcess;
public final String mPackage;
public final int mUid;
+ public final int mVersion;
//final long[] mDurations = new long[STATE_COUNT*ADJ_COUNT];
int mCurState = STATE_NOTHING;
@@ -2393,16 +2498,19 @@ public final class ProcessStats implements Parcelable {
boolean mDead;
public long mTmpTotalTime;
+ int mTmpNumInUse;
+ ProcessState mTmpFoundSubProc;
/**
* Create a new top-level process state, for the initial case where there is only
* a single package running in a process. The initial state is not running.
*/
- public ProcessState(ProcessStats processStats, String pkg, int uid, String name) {
+ public ProcessState(ProcessStats processStats, String pkg, int uid, int vers, String name) {
super(processStats, name);
mCommonProcess = this;
mPackage = pkg;
mUid = uid;
+ mVersion = vers;
}
/**
@@ -2410,18 +2518,19 @@ public final class ProcessStats implements Parcelable {
* state. The current running state of the top-level process is also copied,
* marked as started running at 'now'.
*/
- public ProcessState(ProcessState commonProcess, String pkg, int uid, String name,
+ public ProcessState(ProcessState commonProcess, String pkg, int uid, int vers, String name,
long now) {
super(commonProcess.mStats, name);
mCommonProcess = commonProcess;
mPackage = pkg;
mUid = uid;
+ mVersion = vers;
mCurState = commonProcess.mCurState;
mStartTime = now;
}
ProcessState clone(String pkg, long now) {
- ProcessState pnew = new ProcessState(this, pkg, mUid, mName, now);
+ ProcessState pnew = new ProcessState(this, pkg, mUid, mVersion, mName, now);
copyDurationsTo(pnew);
if (mPssTable != null) {
mStats.mAddLongTable = new int[mPssTable.length];
@@ -2811,9 +2920,20 @@ public final class ProcessStats implements Parcelable {
// The array map is still pointing to a common process state
// that is now shared across packages. Update it to point to
// the new per-package state.
- ProcessState proc = mStats.mPackages.get(pkgName, mUid).mProcesses.get(mName);
+ SparseArray<PackageState> vpkg = mStats.mPackages.get(pkgName, mUid);
+ if (vpkg == null) {
+ throw new IllegalStateException("Didn't find package " + pkgName
+ + " / " + mUid);
+ }
+ PackageState pkg = vpkg.get(mVersion);
+ if (pkg == null) {
+ throw new IllegalStateException("Didn't find package " + pkgName
+ + " / " + mUid + " vers " + mVersion);
+ }
+ ProcessState proc = pkg.mProcesses.get(mName);
if (proc == null) {
- throw new IllegalStateException("Didn't create per-package process");
+ throw new IllegalStateException("Didn't create per-package process "
+ + mName + " in pkg " + pkgName + " / " + mUid + " vers " + mVersion);
}
return proc;
}
@@ -2829,18 +2949,26 @@ public final class ProcessStats implements Parcelable {
// are losing whatever data we had in the old process state.
Log.wtf(TAG, "Pulling dead proc: name=" + mName + " pkg=" + mPackage
+ " uid=" + mUid + " common.name=" + mCommonProcess.mName);
- proc = mStats.getProcessStateLocked(proc.mPackage, proc.mUid, proc.mName);
+ proc = mStats.getProcessStateLocked(proc.mPackage, proc.mUid, proc.mVersion,
+ proc.mName);
}
if (proc.mMultiPackage) {
// The array map is still pointing to a common process state
// that is now shared across packages. Update it to point to
// the new per-package state.
- PackageState pkg = mStats.mPackages.get(pkgList.keyAt(index), proc.mUid);
- if (pkg == null) {
+ SparseArray<PackageState> vpkg = mStats.mPackages.get(pkgList.keyAt(index),
+ proc.mUid);
+ if (vpkg == null) {
throw new IllegalStateException("No existing package "
+ pkgList.keyAt(index) + "/" + proc.mUid
+ " for multi-proc " + proc.mName);
}
+ PackageState pkg = vpkg.get(proc.mVersion);
+ if (pkg == null) {
+ throw new IllegalStateException("No existing package "
+ + pkgList.keyAt(index) + "/" + proc.mUid
+ + " for multi-proc " + proc.mName + " version " + proc.mVersion);
+ }
proc = pkg.mProcesses.get(proc.mName);
if (proc == null) {
throw new IllegalStateException("Didn't create per-package process "
@@ -3014,7 +3142,7 @@ public final class ProcessStats implements Parcelable {
}
public boolean isInUse() {
- return mOwner != null;
+ return mOwner != null || mRestarting;
}
void add(ServiceState other) {
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 494bc78..a604d84 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -23,22 +23,22 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
import android.os.SELinux;
import android.util.Log;
import com.android.org.bouncycastle.util.encoders.Base64;
import java.io.File;
-import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.util.ArrayList;
+
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+import libcore.io.StructStat;
+import static libcore.io.OsConstants.*;
/**
* Backup transport for stashing stuff into a known location on disk, and
@@ -101,7 +101,16 @@ public class LocalTransport extends IBackupTransport.Stub {
}
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
- if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
+ if (DEBUG) {
+ try {
+ StructStat ss = Libcore.os.fstat(data.getFileDescriptor());
+ Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName
+ + " size=" + ss.st_size);
+ } catch (ErrnoException e) {
+ Log.w(TAG, "Unable to stat input file in performBackup() on "
+ + packageInfo.packageName);
+ }
+ }
File packageDir = new File(mDataDir, packageInfo.packageName);
packageDir.mkdirs();
@@ -135,7 +144,16 @@ public class LocalTransport extends IBackupTransport.Stub {
buf = new byte[bufSize];
}
changeSet.readEntityData(buf, 0, dataSize);
- if (DEBUG) Log.v(TAG, " data size " + dataSize);
+ if (DEBUG) {
+ try {
+ long cur = Libcore.os.lseek(data.getFileDescriptor(), 0, SEEK_CUR);
+ Log.v(TAG, " read entity data; new pos=" + cur);
+ }
+ catch (ErrnoException e) {
+ Log.w(TAG, "Unable to stat input file in performBackup() on "
+ + packageInfo.packageName);
+ }
+ }
try {
entity.write(buf, 0, dataSize);
@@ -215,7 +233,9 @@ public class LocalTransport extends IBackupTransport.Stub {
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
while (++mRestorePackage < mRestorePackages.length) {
String name = mRestorePackages[mRestorePackage].packageName;
- if (new File(mDataDir, name).isDirectory()) {
+ // skip packages where we have a data dir but no actual contents
+ String[] contents = (new File(mDataDir, name)).list();
+ if (contents != null && contents.length > 0) {
if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name);
return name;
}
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 942995b..31ca3de 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -22,7 +22,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.Looper;
import android.os.UserHandle;
import com.android.internal.os.BackgroundThread;
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
new file mode 100644
index 0000000..dcc0a4c
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -0,0 +1,299 @@
+/*
+ * 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 com.android.internal.inputmethod;
+
+import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.TreeMap;
+
+/**
+ * InputMethodSubtypeSwitchingController controls the switching behavior of the subtypes.
+ */
+public class InputMethodSubtypeSwitchingController {
+ private static final String TAG = InputMethodSubtypeSwitchingController.class.getSimpleName();
+ private static final boolean DEBUG = false;
+ // TODO: Turn on this flag and add CTS when the platform starts expecting that all IMEs return
+ // true for supportsSwitchingToNextInputMethod().
+ private static final boolean REQUIRE_SWITCHING_SUPPORT = false;
+ private static final int MAX_HISTORY_SIZE = 4;
+ private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
+
+ private static class SubtypeParams {
+ public final InputMethodInfo mImi;
+ public final InputMethodSubtype mSubtype;
+ public final long mTime;
+
+ public SubtypeParams(InputMethodInfo imi, InputMethodSubtype subtype) {
+ mImi = imi;
+ mSubtype = subtype;
+ mTime = System.currentTimeMillis();
+ }
+ }
+
+ public static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> {
+ public final CharSequence mImeName;
+ public final CharSequence mSubtypeName;
+ public final InputMethodInfo mImi;
+ public final int mSubtypeId;
+ private final boolean mIsSystemLocale;
+ private final boolean mIsSystemLanguage;
+
+ public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName,
+ InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) {
+ mImeName = imeName;
+ mSubtypeName = subtypeName;
+ mImi = imi;
+ mSubtypeId = subtypeId;
+ if (TextUtils.isEmpty(subtypeLocale)) {
+ mIsSystemLocale = false;
+ mIsSystemLanguage = false;
+ } else {
+ mIsSystemLocale = subtypeLocale.equals(systemLocale);
+ mIsSystemLanguage = mIsSystemLocale
+ || subtypeLocale.startsWith(systemLocale.substring(0, 2));
+ }
+ }
+
+ @Override
+ public int compareTo(ImeSubtypeListItem other) {
+ if (TextUtils.isEmpty(mImeName)) {
+ return 1;
+ }
+ if (TextUtils.isEmpty(other.mImeName)) {
+ return -1;
+ }
+ if (!TextUtils.equals(mImeName, other.mImeName)) {
+ return mImeName.toString().compareTo(other.mImeName.toString());
+ }
+ if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) {
+ return 0;
+ }
+ if (mIsSystemLocale) {
+ return -1;
+ }
+ if (other.mIsSystemLocale) {
+ return 1;
+ }
+ if (mIsSystemLanguage) {
+ return -1;
+ }
+ if (other.mIsSystemLanguage) {
+ return 1;
+ }
+ if (TextUtils.isEmpty(mSubtypeName)) {
+ return 1;
+ }
+ if (TextUtils.isEmpty(other.mSubtypeName)) {
+ return -1;
+ }
+ return mSubtypeName.toString().compareTo(other.mSubtypeName.toString());
+ }
+ }
+
+ private static class InputMethodAndSubtypeCircularList {
+ private final Context mContext;
+ // Used to load label
+ private final PackageManager mPm;
+ private final String mSystemLocaleStr;
+ private final InputMethodSettings mSettings;
+
+ public InputMethodAndSubtypeCircularList(Context context, InputMethodSettings settings) {
+ mContext = context;
+ mSettings = settings;
+ mPm = context.getPackageManager();
+ final Locale locale = context.getResources().getConfiguration().locale;
+ mSystemLocaleStr = locale != null ? locale.toString() : "";
+ }
+
+ private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis =
+ new TreeMap<InputMethodInfo, List<InputMethodSubtype>>(
+ new Comparator<InputMethodInfo>() {
+ @Override
+ public int compare(InputMethodInfo imi1, InputMethodInfo imi2) {
+ if (imi2 == null)
+ return 0;
+ if (imi1 == null)
+ return 1;
+ if (mPm == null) {
+ return imi1.getId().compareTo(imi2.getId());
+ }
+ CharSequence imiId1 = imi1.loadLabel(mPm) + "/" + imi1.getId();
+ CharSequence imiId2 = imi2.loadLabel(mPm) + "/" + imi2.getId();
+ return imiId1.toString().compareTo(imiId2.toString());
+ }
+ });
+
+ public ImeSubtypeListItem getNextInputMethod(
+ boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) {
+ if (imi == null) {
+ return null;
+ }
+ final List<ImeSubtypeListItem> imList =
+ getSortedInputMethodAndSubtypeList();
+ if (imList.size() <= 1) {
+ return null;
+ }
+ final int N = imList.size();
+ final int currentSubtypeId =
+ subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi,
+ subtype.hashCode()) : NOT_A_SUBTYPE_ID;
+ for (int i = 0; i < N; ++i) {
+ final ImeSubtypeListItem isli = imList.get(i);
+ if (isli.mImi.equals(imi) && isli.mSubtypeId == currentSubtypeId) {
+ if (!onlyCurrentIme) {
+ return imList.get((i + 1) % N);
+ }
+ for (int j = 0; j < N - 1; ++j) {
+ final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N);
+ if (candidate.mImi.equals(imi)) {
+ return candidate;
+ }
+ }
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList() {
+ return getSortedInputMethodAndSubtypeList(true, false, false);
+ }
+
+ public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(
+ boolean showSubtypes, boolean inputShown, boolean isScreenLocked) {
+ final ArrayList<ImeSubtypeListItem> imList =
+ new ArrayList<ImeSubtypeListItem>();
+ final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
+ mSettings.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(
+ mContext);
+ if (immis == null || immis.size() == 0) {
+ return Collections.emptyList();
+ }
+ mSortedImmis.clear();
+ mSortedImmis.putAll(immis);
+ for (InputMethodInfo imi : mSortedImmis.keySet()) {
+ if (imi == null) {
+ continue;
+ }
+ List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi);
+ HashSet<String> enabledSubtypeSet = new HashSet<String>();
+ for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) {
+ enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
+ }
+ final CharSequence imeLabel = imi.loadLabel(mPm);
+ if (showSubtypes && enabledSubtypeSet.size() > 0) {
+ final int subtypeCount = imi.getSubtypeCount();
+ if (DEBUG) {
+ Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
+ }
+ for (int j = 0; j < subtypeCount; ++j) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(j);
+ final String subtypeHashCode = String.valueOf(subtype.hashCode());
+ // We show all enabled IMEs and subtypes when an IME is shown.
+ if (enabledSubtypeSet.contains(subtypeHashCode)
+ && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) {
+ final CharSequence subtypeLabel =
+ subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
+ .getDisplayName(mContext, imi.getPackageName(),
+ imi.getServiceInfo().applicationInfo);
+ imList.add(new ImeSubtypeListItem(imeLabel,
+ subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
+
+ // Removing this subtype from enabledSubtypeSet because we no
+ // longer need to add an entry of this subtype to imList to avoid
+ // duplicated entries.
+ enabledSubtypeSet.remove(subtypeHashCode);
+ }
+ }
+ } else {
+ imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
+ mSystemLocaleStr));
+ }
+ }
+ Collections.sort(imList);
+ return imList;
+ }
+ }
+
+ private final ArrayDeque<SubtypeParams> mTypedSubtypeHistory = new ArrayDeque<SubtypeParams>();
+ private final Object mLock = new Object();
+ private final InputMethodSettings mSettings;
+ private InputMethodAndSubtypeCircularList mSubtypeList;
+
+ public InputMethodSubtypeSwitchingController(InputMethodSettings settings) {
+ mSettings = settings;
+ }
+
+ // TODO: write unit tests for this method and the logic that determines the next subtype
+ public void onCommitText(InputMethodInfo imi, InputMethodSubtype subtype) {
+ synchronized (mTypedSubtypeHistory) {
+ if (subtype == null) {
+ Slog.w(TAG, "Invalid InputMethodSubtype: " + imi.getId() + ", " + subtype);
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "onCommitText: " + imi.getId() + ", " + subtype);
+ }
+ if (REQUIRE_SWITCHING_SUPPORT) {
+ if (!imi.supportsSwitchingToNextInputMethod()) {
+ Slog.w(TAG, imi.getId() + " doesn't support switching to next input method.");
+ return;
+ }
+ }
+ if (mTypedSubtypeHistory.size() >= MAX_HISTORY_SIZE) {
+ mTypedSubtypeHistory.poll();
+ }
+ mTypedSubtypeHistory.addFirst(new SubtypeParams(imi, subtype));
+ }
+ }
+
+ public void resetCircularListLocked(Context context) {
+ synchronized(mLock) {
+ mSubtypeList = new InputMethodAndSubtypeCircularList(context, mSettings);
+ }
+ }
+
+ public ImeSubtypeListItem getNextInputMethod(
+ boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) {
+ synchronized(mLock) {
+ return mSubtypeList.getNextInputMethod(onlyCurrentIme, imi, subtype);
+ }
+ }
+
+ public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(boolean showSubtypes,
+ boolean inputShown, boolean isScreenLocked) {
+ synchronized(mLock) {
+ return mSubtypeList.getSortedInputMethodAndSubtypeList(
+ showSubtypes, inputShown, isScreenLocked);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index 63d018f..03a053c 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -504,6 +504,7 @@ public class InputMethodUtils {
private String mEnabledInputMethodsStrCache;
private int mCurrentUserId;
+ private int[] mRelatedUserIds = new int[0];
private static void buildEnabledInputMethodsSettingString(
StringBuilder builder, Pair<String, ArrayList<String>> pair) {
@@ -536,6 +537,22 @@ public class InputMethodUtils {
mCurrentUserId = userId;
}
+ public void setRelatedUserIds(int[] relatedUserIds) {
+ synchronized (this) {
+ mRelatedUserIds = relatedUserIds;
+ }
+ }
+
+ public boolean isRelatedToOrCurrentUser(int userId) {
+ synchronized (this) {
+ if (userId == mCurrentUserId) return true;
+ for (int i = 0; i < mRelatedUserIds.length; i++) {
+ if (userId == mRelatedUserIds[i]) return true;
+ }
+ return false;
+ }
+ }
+
public List<InputMethodInfo> getEnabledInputMethodListLocked() {
return createEnabledInputMethodListLocked(
getEnabledInputMethodsAndSubtypeListLocked());
@@ -959,5 +976,16 @@ public class InputMethodUtils {
addSubtypeToHistory(curMethodId, subtypeId);
}
}
+
+ public HashMap<InputMethodInfo, List<InputMethodSubtype>>
+ getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(Context context) {
+ HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
+ new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
+ for (InputMethodInfo imi: getEnabledInputMethodListLocked()) {
+ enabledInputMethodAndSubtypes.put(
+ imi, getEnabledInputMethodSubtypeListLocked(context, imi, true));
+ }
+ return enabledInputMethodAndSubtypes;
+ }
}
}
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index 8282d23..e2a2b1e 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -17,6 +17,7 @@
package com.android.internal.net;
import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.TAG_ALL;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
@@ -26,6 +27,7 @@ import android.os.StrictMode;
import android.os.SystemClock;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ProcFileReader;
import java.io.File;
@@ -165,22 +167,32 @@ public class NetworkStatsFactory {
}
public NetworkStats readNetworkStatsDetail() throws IOException {
- return readNetworkStatsDetail(UID_ALL);
+ return readNetworkStatsDetail(UID_ALL, null, TAG_ALL, null);
}
- public NetworkStats readNetworkStatsDetail(int limitUid) throws IOException {
+ public NetworkStats readNetworkStatsDetail(int limitUid, String[] limitIfaces, int limitTag,
+ NetworkStats lastStats)
+ throws IOException {
if (USE_NATIVE_PARSING) {
- final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
- if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid) != 0) {
+ final NetworkStats stats;
+ if (lastStats != null) {
+ stats = lastStats;
+ stats.setElapsedRealtime(SystemClock.elapsedRealtime());
+ } else {
+ stats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
+ }
+ if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid,
+ limitIfaces, limitTag) != 0) {
throw new IOException("Failed to parse network stats");
}
if (SANITY_CHECK_NATIVE) {
- final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid);
+ final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid,
+ limitIfaces, limitTag);
assertEquals(javaStats, stats);
}
return stats;
} else {
- return javaReadNetworkStatsDetail(mStatsXtUid, limitUid);
+ return javaReadNetworkStatsDetail(mStatsXtUid, limitUid, limitIfaces, limitTag);
}
}
@@ -189,7 +201,8 @@ public class NetworkStatsFactory {
* expected to monotonically increase since device boot.
*/
@VisibleForTesting
- public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid)
+ public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid,
+ String[] limitIfaces, int limitTag)
throws IOException {
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
@@ -222,7 +235,9 @@ public class NetworkStatsFactory {
entry.txBytes = reader.nextLong();
entry.txPackets = reader.nextLong();
- if (limitUid == UID_ALL || limitUid == entry.uid) {
+ if ((limitIfaces == null || ArrayUtils.contains(limitIfaces, entry.iface))
+ && (limitUid == UID_ALL || limitUid == entry.uid)
+ && (limitTag == TAG_ALL || limitTag == entry.tag)) {
stats.addValues(entry);
}
@@ -264,5 +279,5 @@ public class NetworkStatsFactory {
*/
@VisibleForTesting
public static native int nativeReadNetworkStatsDetail(
- NetworkStats stats, String path, int limitUid);
+ NetworkStats stats, String path, int limitUid, String[] limitIfaces, int limitTag);
}
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index 98599d0..0d00f41 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -25,8 +25,6 @@ import android.os.UserHandle;
import android.net.RouteInfo;
import android.net.LinkAddress;
-import com.android.internal.util.Preconditions;
-
import java.net.InetAddress;
import java.util.List;
import java.util.ArrayList;
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
new file mode 100644
index 0000000..6ca24d7
--- /dev/null
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import android.os.BatteryStats.Uid;
+
+/**
+ * Contains power usage of an application, system service, or hardware type.
+ */
+public class BatterySipper implements Comparable<BatterySipper> {
+ public int userId;
+ public Uid uidObj;
+ public double value;
+ public double[] values;
+ public DrainType drainType;
+ public long usageTime;
+ public long cpuTime;
+ public long gpsTime;
+ public long wifiRunningTime;
+ public long cpuFgTime;
+ public long wakeLockTime;
+ public long mobileRxPackets;
+ public long mobileTxPackets;
+ public long mobileActive;
+ public int mobileActiveCount;
+ public double mobilemspp; // milliseconds per packet
+ public long wifiRxPackets;
+ public long wifiTxPackets;
+ public long mobileRxBytes;
+ public long mobileTxBytes;
+ public long wifiRxBytes;
+ public long wifiTxBytes;
+ public double percent;
+ public double noCoveragePercent;
+ public String[] mPackages;
+ public String packageWithHighestDrain;
+
+ public enum DrainType {
+ IDLE,
+ CELL,
+ PHONE,
+ WIFI,
+ BLUETOOTH,
+ SCREEN,
+ APP,
+ USER,
+ UNACCOUNTED,
+ OVERCOUNTED
+ }
+
+ public BatterySipper(DrainType drainType, Uid uid, double[] values) {
+ this.values = values;
+ if (values != null) value = values[0];
+ this.drainType = drainType;
+ uidObj = uid;
+ }
+
+ public double[] getValues() {
+ return values;
+ }
+
+ public void computeMobilemspp() {
+ long packets = mobileRxPackets+mobileTxPackets;
+ mobilemspp = packets > 0 ? (mobileActive / (double)packets) : 0;
+ }
+
+ @Override
+ public int compareTo(BatterySipper other) {
+ // Return the flipped value because we want the items in descending order
+ return Double.compare(other.value, value);
+ }
+
+ /**
+ * Gets a list of packages associated with the current user
+ */
+ public String[] getPackages() {
+ return mPackages;
+ }
+
+ public int getUid() {
+ // Bail out if the current sipper is not an App sipper.
+ if (uidObj == null) {
+ return 0;
+ }
+ return uidObj.getUid();
+ }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
new file mode 100644
index 0000000..1dd1f5e
--- /dev/null
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -0,0 +1,807 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static android.os.BatteryStats.NETWORK_MOBILE_RX_DATA;
+import static android.os.BatteryStats.NETWORK_MOBILE_TX_DATA;
+import static android.os.BatteryStats.NETWORK_WIFI_RX_DATA;
+import static android.os.BatteryStats.NETWORK_WIFI_TX_DATA;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.net.ConnectivityManager;
+import android.os.BatteryStats;
+import android.os.BatteryStats.Uid;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.telephony.SignalStrength;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.BatterySipper.DrainType;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A helper class for retrieving the power usage information for all applications and services.
+ *
+ * The caller must initialize this class as soon as activity object is ready to use (for example, in
+ * onAttach() for Fragment), call create() in onCreate() and call destroy() in onDestroy().
+ */
+public class BatteryStatsHelper {
+
+ private static final boolean DEBUG = false;
+
+ private static final String TAG = BatteryStatsHelper.class.getSimpleName();
+
+ private static BatteryStats sStatsXfer;
+
+ final private Context mContext;
+
+ private IBatteryStats mBatteryInfo;
+ private BatteryStats mStats;
+ private PowerProfile mPowerProfile;
+
+ private final List<BatterySipper> mUsageList = new ArrayList<BatterySipper>();
+ private final List<BatterySipper> mWifiSippers = new ArrayList<BatterySipper>();
+ private final List<BatterySipper> mBluetoothSippers = new ArrayList<BatterySipper>();
+ private final SparseArray<List<BatterySipper>> mUserSippers
+ = new SparseArray<List<BatterySipper>>();
+ private final SparseArray<Double> mUserPower = new SparseArray<Double>();
+
+ private final List<BatterySipper> mMobilemsppList = new ArrayList<BatterySipper>();
+
+ private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
+ private int mAsUser = 0;
+
+ long mRawRealtime;
+ long mRawUptime;
+ long mBatteryRealtime;
+ long mBatteryUptime;
+ long mTypeBatteryRealtime;
+ long mTypeBatteryUptime;
+
+ private long mStatsPeriod = 0;
+ private double mMaxPower = 1;
+ private double mComputedPower;
+ private double mTotalPower;
+ private double mWifiPower;
+ private double mBluetoothPower;
+ private double mMinDrainedPower;
+ private double mMaxDrainedPower;
+
+ // How much the apps together have kept the mobile radio active.
+ private long mAppMobileActive;
+
+ // How much the apps together have left WIFI running.
+ private long mAppWifiRunning;
+
+ public BatteryStatsHelper(Context context) {
+ mContext = context;
+ }
+
+ /** Clears the current stats and forces recreating for future use. */
+ public void clearStats() {
+ mStats = null;
+ }
+
+ public BatteryStats getStats() {
+ if (mStats == null) {
+ load();
+ }
+ return mStats;
+ }
+
+ public PowerProfile getPowerProfile() {
+ return mPowerProfile;
+ }
+
+ public void create(BatteryStats stats) {
+ mPowerProfile = new PowerProfile(mContext);
+ mStats = stats;
+ }
+
+ public void create(Bundle icicle) {
+ if (icicle != null) {
+ mStats = sStatsXfer;
+ }
+ mBatteryInfo = IBatteryStats.Stub.asInterface(
+ ServiceManager.getService(BatteryStats.SERVICE_NAME));
+ mPowerProfile = new PowerProfile(mContext);
+ }
+
+ public void storeState() {
+ sStatsXfer = mStats;
+ }
+
+ public static String makemAh(double power) {
+ if (power < .00001) return String.format("%.8f", power);
+ else if (power < .0001) return String.format("%.7f", power);
+ else if (power < .001) return String.format("%.6f", power);
+ else if (power < .01) return String.format("%.5f", power);
+ else if (power < .1) return String.format("%.4f", power);
+ else if (power < 1) return String.format("%.3f", power);
+ else if (power < 10) return String.format("%.2f", power);
+ else if (power < 100) return String.format("%.1f", power);
+ else return String.format("%.0f", power);
+ }
+
+ /**
+ * Refreshes the power usage list.
+ */
+ public void refreshStats(int statsType, int asUser) {
+ refreshStats(statsType, asUser, SystemClock.elapsedRealtime() * 1000,
+ SystemClock.uptimeMillis() * 1000);
+ }
+
+ public void refreshStats(int statsType, int asUser, long rawRealtimeUs, long rawUptimeUs) {
+ // Initialize mStats if necessary.
+ getStats();
+
+ mMaxPower = 0;
+ mComputedPower = 0;
+ mTotalPower = 0;
+ mWifiPower = 0;
+ mBluetoothPower = 0;
+ mAppMobileActive = 0;
+ mAppWifiRunning = 0;
+
+ mUsageList.clear();
+ mWifiSippers.clear();
+ mBluetoothSippers.clear();
+ mUserSippers.clear();
+ mUserPower.clear();
+ mMobilemsppList.clear();
+
+ if (mStats == null) {
+ return;
+ }
+
+ mStatsType = statsType;
+ mAsUser = asUser;
+ mRawUptime = rawUptimeUs;
+ mRawRealtime = rawRealtimeUs;
+ mBatteryUptime = mStats.getBatteryUptime(rawUptimeUs);
+ mBatteryRealtime = mStats.getBatteryRealtime(rawRealtimeUs);
+ mTypeBatteryUptime = mStats.computeBatteryUptime(rawUptimeUs, mStatsType);
+ mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType);
+
+ if (DEBUG) {
+ Log.d(TAG, "Raw time: realtime=" + (rawRealtimeUs/1000) + " uptime="
+ + (rawUptimeUs/1000));
+ Log.d(TAG, "Battery time: realtime=" + (mBatteryRealtime/1000) + " uptime="
+ + (mBatteryUptime/1000));
+ Log.d(TAG, "Battery type time: realtime=" + (mTypeBatteryRealtime/1000) + " uptime="
+ + (mTypeBatteryUptime/1000));
+ }
+ mMinDrainedPower = (mStats.getLowDischargeAmountSinceCharge()
+ * mPowerProfile.getBatteryCapacity()) / 100;
+ mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge()
+ * mPowerProfile.getBatteryCapacity()) / 100;
+
+ processAppUsage();
+
+ // Before aggregating apps in to users, collect all apps to sort by their ms per packet.
+ for (int i=0; i<mUsageList.size(); i++) {
+ BatterySipper bs = mUsageList.get(i);
+ bs.computeMobilemspp();
+ if (bs.mobilemspp != 0) {
+ mMobilemsppList.add(bs);
+ }
+ }
+ for (int i=0; i<mUserSippers.size(); i++) {
+ List<BatterySipper> user = mUserSippers.valueAt(i);
+ for (int j=0; j<user.size(); j++) {
+ BatterySipper bs = user.get(j);
+ bs.computeMobilemspp();
+ if (bs.mobilemspp != 0) {
+ mMobilemsppList.add(bs);
+ }
+ }
+ }
+ Collections.sort(mMobilemsppList, new Comparator<BatterySipper>() {
+ @Override
+ public int compare(BatterySipper lhs, BatterySipper rhs) {
+ if (lhs.mobilemspp < rhs.mobilemspp) {
+ return 1;
+ } else if (lhs.mobilemspp > rhs.mobilemspp) {
+ return -1;
+ }
+ return 0;
+ }
+ });
+
+ processMiscUsage();
+
+ if (DEBUG) {
+ Log.d(TAG, "Accuracy: total computed=" + makemAh(mComputedPower) + ", min discharge="
+ + makemAh(mMinDrainedPower) + ", max discharge=" + makemAh(mMaxDrainedPower));
+ }
+ mTotalPower = mComputedPower;
+ if (mStats.getLowDischargeAmountSinceCharge() > 1) {
+ if (mMinDrainedPower > mComputedPower) {
+ double amount = mMinDrainedPower - mComputedPower;
+ mTotalPower = mMinDrainedPower;
+ addEntryNoTotal(BatterySipper.DrainType.UNACCOUNTED, 0, amount);
+ } else if (mMaxDrainedPower < mComputedPower) {
+ double amount = mComputedPower - mMaxDrainedPower;
+ addEntryNoTotal(BatterySipper.DrainType.OVERCOUNTED, 0, amount);
+ }
+ }
+
+ Collections.sort(mUsageList);
+ }
+
+ private void processAppUsage() {
+ SensorManager sensorManager = (SensorManager) mContext.getSystemService(
+ Context.SENSOR_SERVICE);
+ final int which = mStatsType;
+ final int speedSteps = mPowerProfile.getNumSpeedSteps();
+ final double[] powerCpuNormal = new double[speedSteps];
+ final long[] cpuSpeedStepTimes = new long[speedSteps];
+ for (int p = 0; p < speedSteps; p++) {
+ powerCpuNormal[p] = mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
+ }
+ final double mobilePowerPerPacket = getMobilePowerPerPacket();
+ final double mobilePowerPerMs = getMobilePowerPerMs();
+ final double wifiPowerPerPacket = getWifiPowerPerPacket();
+ long appWakelockTimeUs = 0;
+ BatterySipper osApp = null;
+ mStatsPeriod = mTypeBatteryRealtime;
+ SparseArray<? extends Uid> uidStats = mStats.getUidStats();
+ final int NU = uidStats.size();
+ for (int iu = 0; iu < NU; iu++) {
+ Uid u = uidStats.valueAt(iu);
+ double p; // in mAs
+ double power = 0; // in mAs
+ double highestDrain = 0;
+ String packageWithHighestDrain = null;
+ Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
+ long cpuTime = 0;
+ long cpuFgTime = 0;
+ long wakelockTime = 0;
+ long gpsTime = 0;
+ if (processStats.size() > 0) {
+ // Process CPU time
+ for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
+ : processStats.entrySet()) {
+ Uid.Proc ps = ent.getValue();
+ final long userTime = ps.getUserTime(which);
+ final long systemTime = ps.getSystemTime(which);
+ final long foregroundTime = ps.getForegroundTime(which);
+ cpuFgTime += foregroundTime * 10; // convert to millis
+ final long tmpCpuTime = (userTime + systemTime) * 10; // convert to millis
+ int totalTimeAtSpeeds = 0;
+ // Get the total first
+ for (int step = 0; step < speedSteps; step++) {
+ cpuSpeedStepTimes[step] = ps.getTimeAtCpuSpeedStep(step, which);
+ totalTimeAtSpeeds += cpuSpeedStepTimes[step];
+ }
+ if (totalTimeAtSpeeds == 0) totalTimeAtSpeeds = 1;
+ // Then compute the ratio of time spent at each speed
+ double processPower = 0;
+ for (int step = 0; step < speedSteps; step++) {
+ double ratio = (double) cpuSpeedStepTimes[step] / totalTimeAtSpeeds;
+ if (DEBUG && ratio != 0) Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
+ + step + " ratio=" + makemAh(ratio) + " power="
+ + makemAh(ratio*tmpCpuTime*powerCpuNormal[step] / (60*60*1000)));
+ processPower += ratio * tmpCpuTime * powerCpuNormal[step];
+ }
+ cpuTime += tmpCpuTime;
+ if (DEBUG && processPower != 0) {
+ Log.d(TAG, String.format("process %s, cpu power=%s",
+ ent.getKey(), makemAh(processPower / (60*60*1000))));
+ }
+ power += processPower;
+ if (packageWithHighestDrain == null
+ || packageWithHighestDrain.startsWith("*")) {
+ highestDrain = processPower;
+ packageWithHighestDrain = ent.getKey();
+ } else if (highestDrain < processPower
+ && !ent.getKey().startsWith("*")) {
+ highestDrain = processPower;
+ packageWithHighestDrain = ent.getKey();
+ }
+ }
+ }
+ if (cpuFgTime > cpuTime) {
+ if (DEBUG && cpuFgTime > cpuTime + 10000) {
+ Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
+ }
+ cpuTime = cpuFgTime; // Statistics may not have been gathered yet.
+ }
+ power /= (60*60*1000);
+
+ // Process wake lock usage
+ Map<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats = u.getWakelockStats();
+ for (Map.Entry<String, ? extends BatteryStats.Uid.Wakelock> wakelockEntry
+ : wakelockStats.entrySet()) {
+ Uid.Wakelock wakelock = wakelockEntry.getValue();
+ // Only care about partial wake locks since full wake locks
+ // are canceled when the user turns the screen off.
+ BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
+ if (timer != null) {
+ wakelockTime += timer.getTotalTimeLocked(mRawRealtime, which);
+ }
+ }
+ appWakelockTimeUs += wakelockTime;
+ wakelockTime /= 1000; // convert to millis
+
+ // Add cost of holding a wake lock
+ p = (wakelockTime
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE)) / (60*60*1000);
+ if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wake "
+ + wakelockTime + " power=" + makemAh(p));
+ power += p;
+
+ // Add cost of mobile traffic
+ final long mobileRx = u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
+ final long mobileTx = u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
+ final long mobileRxB = u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, mStatsType);
+ final long mobileTxB = u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, mStatsType);
+ final long mobileActive = u.getMobileRadioActiveTime(mStatsType);
+ if (mobileActive > 0) {
+ // We are tracking when the radio is up, so can use the active time to
+ // determine power use.
+ mAppMobileActive += mobileActive;
+ p = (mobilePowerPerMs * mobileActive) / 1000;
+ } else {
+ // We are not tracking when the radio is up, so must approximate power use
+ // based on the number of packets.
+ p = (mobileRx + mobileTx) * mobilePowerPerPacket;
+ }
+ if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": mobile packets "
+ + (mobileRx+mobileTx) + " active time " + mobileActive
+ + " power=" + makemAh(p));
+ power += p;
+
+ // Add cost of wifi traffic
+ final long wifiRx = u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, mStatsType);
+ final long wifiTx = u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, mStatsType);
+ final long wifiRxB = u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, mStatsType);
+ final long wifiTxB = u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, mStatsType);
+ p = (wifiRx + wifiTx) * wifiPowerPerPacket;
+ if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi packets "
+ + (mobileRx+mobileTx) + " power=" + makemAh(p));
+ power += p;
+
+ // Add cost of keeping WIFI running.
+ long wifiRunningTimeMs = u.getWifiRunningTime(mRawRealtime, which) / 1000;
+ mAppWifiRunning += wifiRunningTimeMs;
+ p = (wifiRunningTimeMs
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)) / (60*60*1000);
+ if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi running "
+ + wifiRunningTimeMs + " power=" + makemAh(p));
+ power += p;
+
+ // Add cost of WIFI scans
+ long wifiScanTimeMs = u.getWifiScanTime(mRawRealtime, which) / 1000;
+ p = (wifiScanTimeMs
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)) / (60*60*1000);
+ if (DEBUG) Log.d(TAG, "UID " + u.getUid() + ": wifi scan " + wifiScanTimeMs
+ + " power=" + makemAh(p));
+ power += p;
+ for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) {
+ long batchScanTimeMs = u.getWifiBatchedScanTime(bin, mRawRealtime, which) / 1000;
+ p = ((batchScanTimeMs
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, bin))
+ ) / (60*60*1000);
+ if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": wifi batched scan # " + bin
+ + " time=" + batchScanTimeMs + " power=" + makemAh(p));
+ power += p;
+ }
+
+ // Process Sensor usage
+ Map<Integer, ? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
+ for (Map.Entry<Integer, ? extends BatteryStats.Uid.Sensor> sensorEntry
+ : sensorStats.entrySet()) {
+ Uid.Sensor sensor = sensorEntry.getValue();
+ int sensorHandle = sensor.getHandle();
+ BatteryStats.Timer timer = sensor.getSensorTime();
+ long sensorTime = timer.getTotalTimeLocked(mRawRealtime, which) / 1000;
+ double multiplier = 0;
+ switch (sensorHandle) {
+ case Uid.Sensor.GPS:
+ multiplier = mPowerProfile.getAveragePower(PowerProfile.POWER_GPS_ON);
+ gpsTime = sensorTime;
+ break;
+ default:
+ List<Sensor> sensorList = sensorManager.getSensorList(
+ android.hardware.Sensor.TYPE_ALL);
+ for (android.hardware.Sensor s : sensorList) {
+ if (s.getHandle() == sensorHandle) {
+ multiplier = s.getPower();
+ break;
+ }
+ }
+ }
+ p = (multiplier * sensorTime) / (60*60*1000);
+ if (DEBUG && p != 0) Log.d(TAG, "UID " + u.getUid() + ": sensor #" + sensorHandle
+ + " time=" + sensorTime + " power=" + makemAh(p));
+ power += p;
+ }
+
+ if (DEBUG && power != 0) Log.d(TAG, String.format("UID %d: total power=%s",
+ u.getUid(), makemAh(power)));
+
+ // Add the app to the list if it is consuming power
+ final int userId = UserHandle.getUserId(u.getUid());
+ if (power != 0 || u.getUid() == 0) {
+ BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u,
+ new double[] {power});
+ app.cpuTime = cpuTime;
+ app.gpsTime = gpsTime;
+ app.wifiRunningTime = wifiRunningTimeMs;
+ app.cpuFgTime = cpuFgTime;
+ app.wakeLockTime = wakelockTime;
+ app.mobileRxPackets = mobileRx;
+ app.mobileTxPackets = mobileTx;
+ app.mobileActive = mobileActive / 1000;
+ app.mobileActiveCount = u.getMobileRadioActiveCount(mStatsType);
+ app.wifiRxPackets = wifiRx;
+ app.wifiTxPackets = wifiTx;
+ app.mobileRxBytes = mobileRxB;
+ app.mobileTxBytes = mobileTxB;
+ app.wifiRxBytes = wifiRxB;
+ app.wifiTxBytes = wifiTxB;
+ app.packageWithHighestDrain = packageWithHighestDrain;
+ if (u.getUid() == Process.WIFI_UID) {
+ mWifiSippers.add(app);
+ mWifiPower += power;
+ } else if (u.getUid() == Process.BLUETOOTH_UID) {
+ mBluetoothSippers.add(app);
+ mBluetoothPower += power;
+ } else if (mAsUser != UserHandle.USER_ALL && userId != mAsUser
+ && UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) {
+ List<BatterySipper> list = mUserSippers.get(userId);
+ if (list == null) {
+ list = new ArrayList<BatterySipper>();
+ mUserSippers.put(userId, list);
+ }
+ list.add(app);
+ if (power != 0) {
+ Double userPower = mUserPower.get(userId);
+ if (userPower == null) {
+ userPower = power;
+ } else {
+ userPower += power;
+ }
+ mUserPower.put(userId, userPower);
+ }
+ } else {
+ mUsageList.add(app);
+ if (power > mMaxPower) mMaxPower = power;
+ mComputedPower += power;
+ }
+ if (u.getUid() == 0) {
+ osApp = app;
+ }
+ }
+ }
+
+ // The device has probably been awake for longer than the screen on
+ // time and application wake lock time would account for. Assign
+ // this remainder to the OS, if possible.
+ if (osApp != null) {
+ long wakeTimeMillis = mBatteryUptime / 1000;
+ wakeTimeMillis -= (appWakelockTimeUs / 1000)
+ + (mStats.getScreenOnTime(mRawRealtime, which) / 1000);
+ if (wakeTimeMillis > 0) {
+ double power = (wakeTimeMillis
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_AWAKE))
+ / (60*60*1000);
+ if (DEBUG) Log.d(TAG, "OS wakeLockTime " + wakeTimeMillis + " power "
+ + makemAh(power));
+ osApp.wakeLockTime += wakeTimeMillis;
+ osApp.value += power;
+ osApp.values[0] += power;
+ if (osApp.value > mMaxPower) mMaxPower = osApp.value;
+ mComputedPower += power;
+ }
+ }
+ }
+
+ private void addPhoneUsage() {
+ long phoneOnTimeMs = mStats.getPhoneOnTime(mRawRealtime, mStatsType) / 1000;
+ double phoneOnPower = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
+ * phoneOnTimeMs / (60*60*1000);
+ if (phoneOnPower != 0) {
+ BatterySipper bs = addEntry(BatterySipper.DrainType.PHONE, phoneOnTimeMs, phoneOnPower);
+ }
+ }
+
+ private void addScreenUsage() {
+ double power = 0;
+ long screenOnTimeMs = mStats.getScreenOnTime(mRawRealtime, mStatsType) / 1000;
+ power += screenOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON);
+ final double screenFullPower =
+ mPowerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL);
+ for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+ double screenBinPower = screenFullPower * (i + 0.5f)
+ / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
+ long brightnessTime = mStats.getScreenBrightnessTime(i, mRawRealtime, mStatsType)
+ / 1000;
+ double p = screenBinPower*brightnessTime;
+ if (DEBUG && p != 0) {
+ Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
+ + " power=" + makemAh(p / (60 * 60 * 1000)));
+ }
+ power += p;
+ }
+ power /= (60*60*1000); // To hours
+ if (power != 0) {
+ addEntry(BatterySipper.DrainType.SCREEN, screenOnTimeMs, power);
+ }
+ }
+
+ private void addRadioUsage() {
+ double power = 0;
+ final int BINS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
+ long signalTimeMs = 0;
+ long noCoverageTimeMs = 0;
+ for (int i = 0; i < BINS; i++) {
+ long strengthTimeMs = mStats.getPhoneSignalStrengthTime(i, mRawRealtime, mStatsType)
+ / 1000;
+ double p = (strengthTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ON, i))
+ / (60*60*1000);
+ if (DEBUG && p != 0) {
+ Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
+ + makemAh(p));
+ }
+ power += p;
+ signalTimeMs += strengthTimeMs;
+ if (i == 0) {
+ noCoverageTimeMs = strengthTimeMs;
+ }
+ }
+ long scanningTimeMs = mStats.getPhoneSignalScanningTime(mRawRealtime, mStatsType)
+ / 1000;
+ double p = (scanningTimeMs * mPowerProfile.getAveragePower(
+ PowerProfile.POWER_RADIO_SCANNING))
+ / (60*60*1000);
+ if (DEBUG && p != 0) {
+ Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs + " power=" + makemAh(p));
+ }
+ power += p;
+ long radioActiveTimeUs = mStats.getMobileRadioActiveTime(mRawRealtime, mStatsType);
+ long remainingActiveTime = (radioActiveTimeUs - mAppMobileActive) / 1000;
+ if (remainingActiveTime > 0) {
+ power += getMobilePowerPerMs() * remainingActiveTime;
+ }
+ if (power != 0) {
+ BatterySipper bs =
+ addEntry(BatterySipper.DrainType.CELL, signalTimeMs, power);
+ if (signalTimeMs != 0) {
+ bs.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs;
+ }
+ bs.mobileActive = remainingActiveTime;
+ bs.mobileActiveCount = mStats.getMobileRadioActiveUnknownCount(mStatsType);
+ }
+ }
+
+ private void aggregateSippers(BatterySipper bs, List<BatterySipper> from, String tag) {
+ for (int i=0; i<from.size(); i++) {
+ BatterySipper wbs = from.get(i);
+ if (DEBUG) Log.d(TAG, tag + " adding sipper " + wbs + ": cpu=" + wbs.cpuTime);
+ bs.cpuTime += wbs.cpuTime;
+ bs.gpsTime += wbs.gpsTime;
+ bs.wifiRunningTime += wbs.wifiRunningTime;
+ bs.cpuFgTime += wbs.cpuFgTime;
+ bs.wakeLockTime += wbs.wakeLockTime;
+ bs.mobileRxPackets += wbs.mobileRxPackets;
+ bs.mobileTxPackets += wbs.mobileTxPackets;
+ bs.mobileActive += wbs.mobileActive;
+ bs.mobileActiveCount += wbs.mobileActiveCount;
+ bs.wifiRxPackets += wbs.wifiRxPackets;
+ bs.wifiTxPackets += wbs.wifiTxPackets;
+ bs.mobileRxBytes += wbs.mobileRxBytes;
+ bs.mobileTxBytes += wbs.mobileTxBytes;
+ bs.wifiRxBytes += wbs.wifiRxBytes;
+ bs.wifiTxBytes += wbs.wifiTxBytes;
+ }
+ bs.computeMobilemspp();
+ }
+
+ private void addWiFiUsage() {
+ long onTimeMs = mStats.getWifiOnTime(mRawRealtime, mStatsType) / 1000;
+ long runningTimeMs = mStats.getGlobalWifiRunningTime(mRawRealtime, mStatsType) / 1000;
+ if (DEBUG) Log.d(TAG, "WIFI runningTime=" + runningTimeMs
+ + " app runningTime=" + mAppWifiRunning);
+ runningTimeMs -= mAppWifiRunning;
+ if (runningTimeMs < 0) runningTimeMs = 0;
+ double wifiPower = (onTimeMs * 0 /* TODO */
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON)
+ + runningTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ON))
+ / (60*60*1000);
+ if (DEBUG && wifiPower != 0) {
+ Log.d(TAG, "Wifi: time=" + runningTimeMs + " power=" + makemAh(wifiPower));
+ }
+ if ((wifiPower+mWifiPower) != 0) {
+ BatterySipper bs = addEntry(BatterySipper.DrainType.WIFI, runningTimeMs,
+ wifiPower + mWifiPower);
+ aggregateSippers(bs, mWifiSippers, "WIFI");
+ }
+ }
+
+ private void addIdleUsage() {
+ long idleTimeMs = (mTypeBatteryRealtime
+ - mStats.getScreenOnTime(mRawRealtime, mStatsType)) / 1000;
+ double idlePower = (idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE))
+ / (60*60*1000);
+ if (DEBUG && idlePower != 0) {
+ Log.d(TAG, "Idle: time=" + idleTimeMs + " power=" + makemAh(idlePower));
+ }
+ if (idlePower != 0) {
+ addEntry(BatterySipper.DrainType.IDLE, idleTimeMs, idlePower);
+ }
+ }
+
+ private void addBluetoothUsage() {
+ long btOnTimeMs = mStats.getBluetoothOnTime(mRawRealtime, mStatsType) / 1000;
+ double btPower = btOnTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_ON)
+ / (60*60*1000);
+ if (DEBUG && btPower != 0) {
+ Log.d(TAG, "Bluetooth: time=" + btOnTimeMs + " power=" + makemAh(btPower));
+ }
+ int btPingCount = mStats.getBluetoothPingCount();
+ double pingPower = (btPingCount
+ * mPowerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_AT_CMD))
+ / (60*60*1000);
+ if (DEBUG && pingPower != 0) {
+ Log.d(TAG, "Bluetooth ping: count=" + btPingCount + " power=" + makemAh(pingPower));
+ }
+ btPower += pingPower;
+ if ((btPower+mBluetoothPower) != 0) {
+ BatterySipper bs = addEntry(BatterySipper.DrainType.BLUETOOTH, btOnTimeMs,
+ btPower + mBluetoothPower);
+ aggregateSippers(bs, mBluetoothSippers, "Bluetooth");
+ }
+ }
+
+ private void addUserUsage() {
+ for (int i=0; i<mUserSippers.size(); i++) {
+ final int userId = mUserSippers.keyAt(i);
+ final List<BatterySipper> sippers = mUserSippers.valueAt(i);
+ Double userPower = mUserPower.get(userId);
+ double power = (userPower != null) ? userPower : 0.0;
+ BatterySipper bs = addEntry(BatterySipper.DrainType.USER, 0, power);
+ bs.userId = userId;
+ aggregateSippers(bs, sippers, "User");
+ }
+ }
+
+ /**
+ * Return estimated power (in mAs) of sending or receiving a packet with the mobile radio.
+ */
+ private double getMobilePowerPerPacket() {
+ final long MOBILE_BPS = 200000; // TODO: Extract average bit rates from system
+ final double MOBILE_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE)
+ / 3600;
+
+ final long mobileRx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, mStatsType);
+ final long mobileTx = mStats.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, mStatsType);
+ final long mobileData = mobileRx + mobileTx;
+
+ final long radioDataUptimeMs
+ = mStats.getMobileRadioActiveTime(mRawRealtime, mStatsType) / 1000;
+ final double mobilePps = (mobileData != 0 && radioDataUptimeMs != 0)
+ ? (mobileData / (double)radioDataUptimeMs)
+ : (((double)MOBILE_BPS) / 8 / 2048);
+
+ return (MOBILE_POWER / mobilePps) / (60*60);
+ }
+
+ /**
+ * Return estimated power (in mAs) of keeping the radio up
+ */
+ private double getMobilePowerPerMs() {
+ return mPowerProfile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE) / (60*60*1000);
+ }
+
+ /**
+ * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio.
+ */
+ private double getWifiPowerPerPacket() {
+ final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
+ final double WIFI_POWER = mPowerProfile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
+ / 3600;
+ return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60);
+ }
+
+ private void processMiscUsage() {
+ addUserUsage();
+ addPhoneUsage();
+ addScreenUsage();
+ addWiFiUsage();
+ addBluetoothUsage();
+ addIdleUsage(); // Not including cellular idle power
+ // Don't compute radio usage if it's a wifi-only device
+ ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
+ addRadioUsage();
+ }
+ }
+
+ private BatterySipper addEntry(DrainType drainType, long time, double power) {
+ mComputedPower += power;
+ return addEntryNoTotal(drainType, time, power);
+ }
+
+ private BatterySipper addEntryNoTotal(DrainType drainType, long time, double power) {
+ if (power > mMaxPower) mMaxPower = power;
+ BatterySipper bs = new BatterySipper(drainType, null, new double[] {power});
+ bs.usageTime = time;
+ mUsageList.add(bs);
+ return bs;
+ }
+
+ public List<BatterySipper> getUsageList() {
+ return mUsageList;
+ }
+
+ public List<BatterySipper> getMobilemsppList() {
+ return mMobilemsppList;
+ }
+
+ public long getStatsPeriod() { return mStatsPeriod; }
+
+ public int getStatsType() { return mStatsType; };
+
+ public double getMaxPower() { return mMaxPower; }
+
+ public double getTotalPower() { return mTotalPower; }
+
+ public double getComputedPower() { return mComputedPower; }
+
+ public double getMinDrainedPower() {
+ return mMinDrainedPower;
+ }
+
+ public double getMaxDrainedPower() {
+ return mMaxDrainedPower;
+ }
+
+ private void load() {
+ if (mBatteryInfo == null) {
+ return;
+ }
+ try {
+ byte[] data = mBatteryInfo.getStatistics();
+ Parcel parcel = Parcel.obtain();
+ parcel.unmarshall(data, 0, data.length);
+ parcel.setDataPosition(0);
+ BatteryStatsImpl stats = com.android.internal.os.BatteryStatsImpl.CREATOR
+ .createFromParcel(parcel);
+ stats.distributeWorkLocked(BatteryStats.STATS_SINCE_CHARGED);
+ mStats = stats;
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException:", e);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 8728610..26d7f5f 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16,12 +16,15 @@
package com.android.internal.os;
+import static android.net.NetworkStats.UID_ALL;
import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
+import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkStats;
+import android.os.BadParcelableException;
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.FileUtils;
@@ -44,24 +47,23 @@ import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.JournaledFile;
-import com.google.android.collect.Sets;
-import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -85,7 +87,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 67 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 99 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -141,6 +143,11 @@ public final class BatteryStatsImpl extends BatteryStats {
private BatteryCallback mCallback;
/**
+ * Mapping isolated uids to the actual owning app uid.
+ */
+ final SparseIntArray mIsolatedUids = new SparseIntArray();
+
+ /**
* The statistics we have collected organized by uids.
*/
final SparseArray<BatteryStatsImpl.Uid> mUidStats =
@@ -167,10 +174,21 @@ public final class BatteryStatsImpl extends BatteryStats {
// These are the objects that will want to do something when the device
// is unplugged from power.
- final ArrayList<Unpluggable> mUnpluggables = new ArrayList<Unpluggable>();
+ final TimeBase mOnBatteryTimeBase = new TimeBase();
+
+ // These are the objects that will want to do something when the device
+ // is unplugged from power *and* the screen is off.
+ final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase();
+
+ // Set to true when we want to distribute CPU across wakelocks for the next
+ // CPU update, even if we aren't currently running wake locks.
+ boolean mDistributeWakelockCpu;
boolean mShuttingDown;
+ HashMap<String, SparseBooleanArray>[] mActiveEvents
+ = (HashMap<String, SparseBooleanArray>[]) new HashMap[HistoryItem.EVENT_COUNT];
+
long mHistoryBaseTime;
boolean mHaveBatteryLevel = false;
boolean mRecordingHistory = true;
@@ -182,6 +200,12 @@ public final class BatteryStatsImpl extends BatteryStats {
final HistoryItem mHistoryLastWritten = new HistoryItem();
final HistoryItem mHistoryLastLastWritten = new HistoryItem();
final HistoryItem mHistoryReadTmp = new HistoryItem();
+ final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<HistoryTag, Integer>();
+ String[] mReadHistoryStrings;
+ int[] mReadHistoryUids;
+ int mReadHistoryChars;
+ int mNextHistoryTagIdx = 0;
+ int mNumHistoryTagChars = 0;
int mHistoryBufferLastPos = -1;
boolean mHistoryOverflow = false;
long mLastHistoryTime = 0;
@@ -199,10 +223,7 @@ public final class BatteryStatsImpl extends BatteryStats {
int mStartCount;
- long mBatteryUptime;
- long mBatteryLastUptime;
- long mBatteryRealtime;
- long mBatteryLastRealtime;
+ long mStartClockTime;
long mUptime;
long mUptimeStart;
@@ -211,6 +232,9 @@ public final class BatteryStatsImpl extends BatteryStats {
long mRealtimeStart;
long mLastRealtime;
+ int mWakeLockNesting;
+ boolean mWakeLockImportant;
+
boolean mScreenOn;
StopwatchTimer mScreenOnTimer;
@@ -239,19 +263,32 @@ public final class BatteryStatsImpl extends BatteryStats {
final StopwatchTimer[] mPhoneDataConnectionsTimer =
new StopwatchTimer[NUM_DATA_CONNECTION_TYPES];
- final LongSamplingCounter[] mNetworkActivityCounters =
+ final LongSamplingCounter[] mNetworkByteActivityCounters =
+ new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+ final LongSamplingCounter[] mNetworkPacketActivityCounters =
new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
boolean mWifiOn;
StopwatchTimer mWifiOnTimer;
- int mWifiOnUid = -1;
boolean mGlobalWifiRunning;
StopwatchTimer mGlobalWifiRunningTimer;
+ int mWifiState = -1;
+ final StopwatchTimer[] mWifiStateTimer = new StopwatchTimer[NUM_WIFI_STATES];
+
boolean mBluetoothOn;
StopwatchTimer mBluetoothOnTimer;
+ int mBluetoothState = -1;
+ final StopwatchTimer[] mBluetoothStateTimer = new StopwatchTimer[NUM_BLUETOOTH_STATES];
+
+ boolean mMobileRadioActive;
+ StopwatchTimer mMobileRadioActiveTimer;
+ StopwatchTimer mMobileRadioActivePerAppTimer;
+ LongSamplingCounter mMobileRadioActiveUnknownTime;
+ LongSamplingCounter mMobileRadioActiveUnknownCount;
+
/** Bluetooth headset object */
BluetoothHeadset mBtHeadset;
@@ -261,13 +298,6 @@ public final class BatteryStatsImpl extends BatteryStats {
*/
boolean mOnBattery;
boolean mOnBatteryInternal;
- long mTrackBatteryPastUptime;
- long mTrackBatteryUptimeStart;
- long mTrackBatteryPastRealtime;
- long mTrackBatteryRealtimeStart;
-
- long mUnpluggedBatteryUptime;
- long mUnpluggedBatteryRealtime;
/*
* These keep track of battery levels (1-100) at the last plug event and the last unplug event.
@@ -286,9 +316,6 @@ public final class BatteryStatsImpl extends BatteryStats {
long mLastWriteTime = 0; // Milliseconds
- private long mRadioDataUptime;
- private long mRadioDataStart;
-
private int mBluetoothPingCount;
private int mBluetoothPingStart = -1;
@@ -340,15 +367,18 @@ public final class BatteryStatsImpl extends BatteryStats {
private final Map<String, KernelWakelockStats> mProcWakelockFileStats =
new HashMap<String, KernelWakelockStats>();
- private HashMap<String, Integer> mUidCache = new HashMap<String, Integer>();
-
private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory();
- private NetworkStats mLastSnapshot;
+ private NetworkStats mCurMobileSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
+ private NetworkStats mLastMobileSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
+ private NetworkStats mCurWifiSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
+ private NetworkStats mLastWifiSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), 50);
+ private NetworkStats mTmpNetworkStats;
+ private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry();
@GuardedBy("this")
- private HashSet<String> mMobileIfaces = Sets.newHashSet();
+ private String[] mMobileIfaces = new String[0];
@GuardedBy("this")
- private HashSet<String> mWifiIfaces = Sets.newHashSet();
+ private String[] mWifiIfaces = new String[0];
// For debugging
public BatteryStatsImpl() {
@@ -356,35 +386,235 @@ public final class BatteryStatsImpl extends BatteryStats {
mHandler = null;
}
- public static interface Unpluggable {
- void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime);
- void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime);
+ public static interface TimeBaseObs {
+ void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime);
+ void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime);
+ }
+
+ static class TimeBase {
+ private final ArrayList<TimeBaseObs> mObservers = new ArrayList<TimeBaseObs>();
+
+ private long mUptime;
+ private long mLastUptime;
+ private long mRealtime;
+ private long mLastRealtime;
+
+ private boolean mRunning;
+
+ private long mPastUptime;
+ private long mUptimeStart;
+ private long mPastRealtime;
+ private long mRealtimeStart;
+ private long mUnpluggedUptime;
+ private long mUnpluggedRealtime;
+
+ public void dump(PrintWriter pw, String prefix) {
+ StringBuilder sb = new StringBuilder(128);
+ pw.print(prefix); pw.print("mRunning="); pw.println(mRunning);
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append("mUptime=");
+ formatTimeMs(sb, mUptime / 1000); sb.append("mLastUptime=");
+ formatTimeMs(sb, mLastUptime / 1000);
+ pw.println(sb.toString());
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append("mRealtime=");
+ formatTimeMs(sb, mRealtime / 1000); sb.append("mLastRealtime=");
+ formatTimeMs(sb, mLastRealtime / 1000);
+ pw.println(sb.toString());
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append("mPastUptime=");
+ formatTimeMs(sb, mPastUptime / 1000); sb.append("mUptimeStart=");
+ formatTimeMs(sb, mUptimeStart / 1000);
+ sb.append("mUnpluggedUptime="); formatTimeMs(sb, mUnpluggedUptime / 1000);
+ pw.println(sb.toString());
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append("mPastRealtime=");
+ formatTimeMs(sb, mPastRealtime / 1000); sb.append("mRealtimeStart=");
+ formatTimeMs(sb, mRealtimeStart / 1000);
+ sb.append("mUnpluggedRealtime="); formatTimeMs(sb, mUnpluggedRealtime / 1000);
+ pw.println(sb.toString());
+ }
+
+ public void add(TimeBaseObs observer) {
+ mObservers.add(observer);
+ }
+
+ public void remove(TimeBaseObs observer) {
+ if (!mObservers.remove(observer)) {
+ Slog.wtf(TAG, "Removed unknown observer: " + observer);
+ }
+ }
+
+ public void init(long uptime, long realtime) {
+ mRealtime = 0;
+ mUptime = 0;
+ mPastUptime = 0;
+ mPastRealtime = 0;
+ mUptimeStart = uptime;
+ mRealtimeStart = realtime;
+ mUnpluggedUptime = getUptime(mUptimeStart);
+ mUnpluggedRealtime = getRealtime(mRealtimeStart);
+ }
+
+ public void reset(long uptime, long realtime) {
+ if (!mRunning) {
+ mPastUptime = 0;
+ mPastRealtime = 0;
+ } else {
+ mUptimeStart = uptime;
+ mRealtimeStart = realtime;
+ mUnpluggedUptime = getUptime(uptime);
+ mUnpluggedRealtime = getRealtime(realtime);
+ }
+ }
+
+ public long computeUptime(long curTime, int which) {
+ switch (which) {
+ case STATS_SINCE_CHARGED:
+ return mUptime + getUptime(curTime);
+ case STATS_LAST:
+ return mLastUptime;
+ case STATS_CURRENT:
+ return getUptime(curTime);
+ case STATS_SINCE_UNPLUGGED:
+ return getUptime(curTime) - mUnpluggedUptime;
+ }
+ return 0;
+ }
+
+ public long computeRealtime(long curTime, int which) {
+ switch (which) {
+ case STATS_SINCE_CHARGED:
+ return mRealtime + getRealtime(curTime);
+ case STATS_LAST:
+ return mLastRealtime;
+ case STATS_CURRENT:
+ return getRealtime(curTime);
+ case STATS_SINCE_UNPLUGGED:
+ return getRealtime(curTime) - mUnpluggedRealtime;
+ }
+ return 0;
+ }
+
+ public long getUptime(long curTime) {
+ long time = mPastUptime;
+ if (mRunning) {
+ time += curTime - mUptimeStart;
+ }
+ return time;
+ }
+
+ public long getRealtime(long curTime) {
+ long time = mPastRealtime;
+ if (mRunning) {
+ time += curTime - mRealtimeStart;
+ }
+ return time;
+ }
+
+ public long getUptimeStart() {
+ return mUptimeStart;
+ }
+
+ public long getRealtimeStart() {
+ return mRealtimeStart;
+ }
+
+ public boolean isRunning() {
+ return mRunning;
+ }
+
+ public boolean setRunning(boolean running, long uptime, long realtime) {
+ if (mRunning != running) {
+ mRunning = running;
+ if (running) {
+ mUptimeStart = uptime;
+ mRealtimeStart = realtime;
+ long batteryUptime = mUnpluggedUptime = getUptime(uptime);
+ long batteryRealtime = mUnpluggedRealtime = getRealtime(realtime);
+
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onTimeStarted(realtime, batteryUptime, batteryRealtime);
+ }
+ } else {
+ mPastUptime += uptime - mUptimeStart;
+ mPastRealtime += realtime - mRealtimeStart;
+
+ long batteryUptime = getUptime(uptime);
+ long batteryRealtime = getRealtime(realtime);
+
+ for (int i = mObservers.size() - 1; i >= 0; i--) {
+ mObservers.get(i).onTimeStopped(realtime, batteryUptime, batteryRealtime);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void readSummaryFromParcel(Parcel in) {
+ mUptime = in.readLong();
+ mRealtime = in.readLong();
+ }
+
+ public void writeSummaryToParcel(Parcel out, long uptime, long realtime) {
+ out.writeLong(computeUptime(uptime, STATS_SINCE_CHARGED));
+ out.writeLong(computeRealtime(realtime, STATS_SINCE_CHARGED));
+ }
+
+ public void readFromParcel(Parcel in) {
+ mRunning = false;
+ mUptime = in.readLong();
+ mLastUptime = 0;
+ mPastUptime = in.readLong();
+ mUptimeStart = in.readLong();
+ mPastRealtime = in.readLong();
+ mRealtimeStart = in.readLong();
+ mUnpluggedUptime = in.readLong();
+ mUnpluggedRealtime = in.readLong();
+ }
+
+ public void writeToParcel(Parcel out, long uptime, long realtime) {
+ final long runningUptime = getUptime(uptime);
+ final long runningRealtime = getRealtime(realtime);
+ out.writeLong(mUptime);
+ out.writeLong(runningUptime);
+ out.writeLong(mUptimeStart);
+ out.writeLong(runningRealtime);
+ out.writeLong(mRealtimeStart);
+ out.writeLong(mUnpluggedUptime);
+ out.writeLong(mUnpluggedRealtime);
+ }
}
/**
* State for keeping track of counting information.
*/
- public static class Counter extends BatteryStats.Counter implements Unpluggable {
+ public static class Counter extends BatteryStats.Counter implements TimeBaseObs {
final AtomicInteger mCount = new AtomicInteger();
- final ArrayList<Unpluggable> mUnpluggables;
+ final TimeBase mTimeBase;
int mLoadedCount;
int mLastCount;
int mUnpluggedCount;
int mPluggedCount;
- Counter(ArrayList<Unpluggable> unpluggables, Parcel in) {
- mUnpluggables = unpluggables;
+ Counter(TimeBase timeBase, Parcel in) {
+ mTimeBase = timeBase;
mPluggedCount = in.readInt();
mCount.set(mPluggedCount);
mLoadedCount = in.readInt();
mLastCount = 0;
mUnpluggedCount = in.readInt();
- unpluggables.add(this);
+ timeBase.add(this);
}
- Counter(ArrayList<Unpluggable> unpluggables) {
- mUnpluggables = unpluggables;
- unpluggables.add(this);
+ Counter(TimeBase timeBase) {
+ mTimeBase = timeBase;
+ timeBase.add(this);
}
public void writeToParcel(Parcel out) {
@@ -393,12 +623,12 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mUnpluggedCount);
}
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
mUnpluggedCount = mPluggedCount;
mCount.set(mPluggedCount);
}
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
mPluggedCount = mCount.get();
}
@@ -458,7 +688,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
void detach() {
- mUnpluggables.remove(this);
+ mTimeBase.remove(this);
}
void writeSummaryFromParcelLocked(Parcel out) {
@@ -475,12 +705,12 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public static class SamplingCounter extends Counter {
- SamplingCounter(ArrayList<Unpluggable> unpluggables, Parcel in) {
- super(unpluggables, in);
+ SamplingCounter(TimeBase timeBase, Parcel in) {
+ super(timeBase, in);
}
- SamplingCounter(ArrayList<Unpluggable> unpluggables) {
- super(unpluggables);
+ SamplingCounter(TimeBase timeBase) {
+ super(timeBase);
}
public void addCountAtomic(long count) {
@@ -488,27 +718,27 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- public static class LongSamplingCounter implements Unpluggable {
- final ArrayList<Unpluggable> mUnpluggables;
+ public static class LongSamplingCounter implements TimeBaseObs {
+ final TimeBase mTimeBase;
long mCount;
long mLoadedCount;
long mLastCount;
long mUnpluggedCount;
long mPluggedCount;
- LongSamplingCounter(ArrayList<Unpluggable> unpluggables, Parcel in) {
- mUnpluggables = unpluggables;
+ LongSamplingCounter(TimeBase timeBase, Parcel in) {
+ mTimeBase = timeBase;
mPluggedCount = in.readLong();
mCount = mPluggedCount;
mLoadedCount = in.readLong();
mLastCount = 0;
mUnpluggedCount = in.readLong();
- unpluggables.add(this);
+ timeBase.add(this);
}
- LongSamplingCounter(ArrayList<Unpluggable> unpluggables) {
- mUnpluggables = unpluggables;
- unpluggables.add(this);
+ LongSamplingCounter(TimeBase timeBase) {
+ mTimeBase = timeBase;
+ timeBase.add(this);
}
public void writeToParcel(Parcel out) {
@@ -518,13 +748,13 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
mUnpluggedCount = mPluggedCount;
mCount = mPluggedCount;
}
@Override
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
mPluggedCount = mCount;
}
@@ -560,7 +790,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
void detach() {
- mUnpluggables.remove(this);
+ mTimeBase.remove(this);
}
void writeSummaryFromParcelLocked(Parcel out) {
@@ -578,9 +808,9 @@ public final class BatteryStatsImpl extends BatteryStats {
/**
* State for keeping track of timing information.
*/
- public static abstract class Timer extends BatteryStats.Timer implements Unpluggable {
+ public static abstract class Timer extends BatteryStats.Timer implements TimeBaseObs {
final int mType;
- final ArrayList<Unpluggable> mUnpluggables;
+ final TimeBase mTimeBase;
int mCount;
int mLoadedCount;
@@ -619,12 +849,12 @@ public final class BatteryStatsImpl extends BatteryStats {
/**
* Constructs from a parcel.
* @param type
- * @param unpluggables
+ * @param timeBase
* @param in
*/
- Timer(int type, ArrayList<Unpluggable> unpluggables, Parcel in) {
+ Timer(int type, TimeBase timeBase, Parcel in) {
mType = type;
- mUnpluggables = unpluggables;
+ mTimeBase = timeBase;
mCount = in.readInt();
mLoadedCount = in.readInt();
@@ -634,13 +864,13 @@ public final class BatteryStatsImpl extends BatteryStats {
mLoadedTime = in.readLong();
mLastTime = 0;
mUnpluggedTime = in.readLong();
- unpluggables.add(this);
+ timeBase.add(this);
}
- Timer(int type, ArrayList<Unpluggable> unpluggables) {
+ Timer(int type, TimeBase timeBase) {
mType = type;
- mUnpluggables = unpluggables;
- unpluggables.add(this);
+ mTimeBase = timeBase;
+ timeBase.add(this);
}
protected abstract long computeRunTimeLocked(long curBatteryRealtime);
@@ -651,7 +881,7 @@ public final class BatteryStatsImpl extends BatteryStats {
* Clear state of this timer. Returns true if the timer is inactive
* so can be completely dropped.
*/
- boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
+ boolean reset(boolean detachIfReset) {
mTotalTime = mLoadedTime = mLastTime = 0;
mCount = mLoadedCount = mLastCount = 0;
if (detachIfReset) {
@@ -661,25 +891,25 @@ public final class BatteryStatsImpl extends BatteryStats {
}
void detach() {
- mUnpluggables.remove(this);
+ mTimeBase.remove(this);
}
- public void writeToParcel(Parcel out, long batteryRealtime) {
+ public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
out.writeInt(mCount);
out.writeInt(mLoadedCount);
out.writeInt(mUnpluggedCount);
- out.writeLong(computeRunTimeLocked(batteryRealtime));
+ out.writeLong(computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs)));
out.writeLong(mLoadedTime);
out.writeLong(mUnpluggedTime);
}
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStarted(long elapsedRealtime, long timeBaseUptime, long baseRealtime) {
if (DEBUG && mType < 0) {
- Log.v(TAG, "unplug #" + mType + ": realtime=" + batteryRealtime
+ Log.v(TAG, "unplug #" + mType + ": realtime=" + baseRealtime
+ " old mUnpluggedTime=" + mUnpluggedTime
+ " old mUnpluggedCount=" + mUnpluggedCount);
}
- mUnpluggedTime = computeRunTimeLocked(batteryRealtime);
+ mUnpluggedTime = computeRunTimeLocked(baseRealtime);
mUnpluggedCount = mCount;
if (DEBUG && mType < 0) {
Log.v(TAG, "unplug #" + mType
@@ -688,12 +918,12 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
if (DEBUG && mType < 0) {
- Log.v(TAG, "plug #" + mType + ": realtime=" + batteryRealtime
+ Log.v(TAG, "plug #" + mType + ": realtime=" + baseRealtime
+ " old mTotalTime=" + mTotalTime);
}
- mTotalTime = computeRunTimeLocked(batteryRealtime);
+ mTotalTime = computeRunTimeLocked(baseRealtime);
mCount = computeCurrentCountLocked();
if (DEBUG && mType < 0) {
Log.v(TAG, "plug #" + mType
@@ -707,24 +937,23 @@ public final class BatteryStatsImpl extends BatteryStats {
* @param out the Parcel to be written to.
* @param timer a Timer, or null.
*/
- public static void writeTimerToParcel(Parcel out, Timer timer,
- long batteryRealtime) {
+ public static void writeTimerToParcel(Parcel out, Timer timer, long elapsedRealtimeUs) {
if (timer == null) {
out.writeInt(0); // indicates null
return;
}
out.writeInt(1); // indicates non-null
- timer.writeToParcel(out, batteryRealtime);
+ timer.writeToParcel(out, elapsedRealtimeUs);
}
@Override
- public long getTotalTimeLocked(long batteryRealtime, int which) {
+ public long getTotalTimeLocked(long elapsedRealtimeUs, int which) {
long val;
if (which == STATS_LAST) {
val = mLastTime;
} else {
- val = computeRunTimeLocked(batteryRealtime);
+ val = computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs));
if (which == STATS_SINCE_UNPLUGGED) {
val -= mUnpluggedTime;
} else if (which != STATS_SINCE_CHARGED) {
@@ -763,16 +992,15 @@ public final class BatteryStatsImpl extends BatteryStats {
}
- void writeSummaryFromParcelLocked(Parcel out, long batteryRealtime) {
- long runTime = computeRunTimeLocked(batteryRealtime);
- // Divide by 1000 for backwards compatibility
- out.writeLong((runTime + 500) / 1000);
+ void writeSummaryFromParcelLocked(Parcel out, long elapsedRealtimeUs) {
+ long runTime = computeRunTimeLocked(mTimeBase.getRealtime(elapsedRealtimeUs));
+ out.writeLong(runTime);
out.writeInt(mCount);
}
void readSummaryFromParcelLocked(Parcel in) {
// Multiply by 1000 for backwards compatibility
- mTotalTime = mLoadedTime = in.readLong() * 1000;
+ mTotalTime = mLoadedTime = in.readLong();
mLastTime = 0;
mUnpluggedTime = mTotalTime;
mCount = mLoadedCount = in.readInt();
@@ -809,7 +1037,7 @@ public final class BatteryStatsImpl extends BatteryStats {
/**
* Whether we are currently in a discharge cycle.
*/
- boolean mInDischarge;
+ boolean mTimeBaseRunning;
/**
* Whether we are currently recording reported values.
@@ -821,21 +1049,20 @@ public final class BatteryStatsImpl extends BatteryStats {
*/
int mUpdateVersion;
- SamplingTimer(ArrayList<Unpluggable> unpluggables, boolean inDischarge, Parcel in) {
- super(0, unpluggables, in);
+ SamplingTimer(TimeBase timeBase, Parcel in) {
+ super(0, timeBase, in);
mCurrentReportedCount = in.readInt();
mUnpluggedReportedCount = in.readInt();
mCurrentReportedTotalTime = in.readLong();
mUnpluggedReportedTotalTime = in.readLong();
mTrackingReportedValues = in.readInt() == 1;
- mInDischarge = inDischarge;
+ mTimeBaseRunning = timeBase.isRunning();
}
- SamplingTimer(ArrayList<Unpluggable> unpluggables, boolean inDischarge,
- boolean trackReportedValues) {
- super(0, unpluggables);
+ SamplingTimer(TimeBase timeBase, boolean trackReportedValues) {
+ super(0, timeBase);
mTrackingReportedValues = trackReportedValues;
- mInDischarge = inDischarge;
+ mTimeBaseRunning = timeBase.isRunning();
}
public void setStale() {
@@ -853,7 +1080,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void updateCurrentReportedCount(int count) {
- if (mInDischarge && mUnpluggedReportedCount == 0) {
+ if (mTimeBaseRunning && mUnpluggedReportedCount == 0) {
// Updating the reported value for the first time.
mUnpluggedReportedCount = count;
// If we are receiving an update update mTrackingReportedValues;
@@ -863,7 +1090,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void updateCurrentReportedTotalTime(long totalTime) {
- if (mInDischarge && mUnpluggedReportedTotalTime == 0) {
+ if (mTimeBaseRunning && mUnpluggedReportedTotalTime == 0) {
// Updating the reported value for the first time.
mUnpluggedReportedTotalTime = totalTime;
// If we are receiving an update update mTrackingReportedValues;
@@ -872,18 +1099,18 @@ public final class BatteryStatsImpl extends BatteryStats {
mCurrentReportedTotalTime = totalTime;
}
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
- super.unplug(elapsedRealtime, batteryUptime, batteryRealtime);
+ public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
+ super.onTimeStarted(elapsedRealtime, baseUptime, baseRealtime);
if (mTrackingReportedValues) {
mUnpluggedReportedTotalTime = mCurrentReportedTotalTime;
mUnpluggedReportedCount = mCurrentReportedCount;
}
- mInDischarge = true;
+ mTimeBaseRunning = true;
}
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
- super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
- mInDischarge = false;
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
+ super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime);
+ mTimeBaseRunning = false;
}
public void logState(Printer pw, String prefix) {
@@ -895,17 +1122,17 @@ public final class BatteryStatsImpl extends BatteryStats {
}
protected long computeRunTimeLocked(long curBatteryRealtime) {
- return mTotalTime + (mInDischarge && mTrackingReportedValues
+ return mTotalTime + (mTimeBaseRunning && mTrackingReportedValues
? mCurrentReportedTotalTime - mUnpluggedReportedTotalTime : 0);
}
protected int computeCurrentCountLocked() {
- return mCount + (mInDischarge && mTrackingReportedValues
+ return mCount + (mTimeBaseRunning && mTrackingReportedValues
? mCurrentReportedCount - mUnpluggedReportedCount : 0);
}
- public void writeToParcel(Parcel out, long batteryRealtime) {
- super.writeToParcel(out, batteryRealtime);
+ public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+ super.writeToParcel(out, elapsedRealtimeUs);
out.writeInt(mCurrentReportedCount);
out.writeInt(mUnpluggedReportedCount);
out.writeLong(mCurrentReportedTotalTime);
@@ -913,8 +1140,8 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mTrackingReportedValues ? 1 : 0);
}
- boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
- super.reset(stats, detachIfReset);
+ boolean reset(boolean detachIfReset) {
+ super.reset(detachIfReset);
setStale();
return true;
}
@@ -956,45 +1183,43 @@ public final class BatteryStatsImpl extends BatteryStats {
*/
boolean mInDischarge;
- BatchTimer(Uid uid, int type, ArrayList<Unpluggable> unpluggables,
- boolean inDischarge, Parcel in) {
- super(type, unpluggables, in);
+ BatchTimer(Uid uid, int type, TimeBase timeBase, Parcel in) {
+ super(type, timeBase, in);
mUid = uid;
mLastAddedTime = in.readLong();
mLastAddedDuration = in.readLong();
- mInDischarge = inDischarge;
+ mInDischarge = timeBase.isRunning();
}
- BatchTimer(Uid uid, int type, ArrayList<Unpluggable> unpluggables,
- boolean inDischarge) {
- super(type, unpluggables);
+ BatchTimer(Uid uid, int type, TimeBase timeBase) {
+ super(type, timeBase);
mUid = uid;
- mInDischarge = inDischarge;
+ mInDischarge = timeBase.isRunning();
}
@Override
- public void writeToParcel(Parcel out, long batteryRealtime) {
- super.writeToParcel(out, batteryRealtime);
+ public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+ super.writeToParcel(out, elapsedRealtimeUs);
out.writeLong(mLastAddedTime);
out.writeLong(mLastAddedDuration);
}
@Override
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
recomputeLastDuration(SystemClock.elapsedRealtime() * 1000, false);
mInDischarge = false;
- super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
+ super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime);
}
@Override
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
recomputeLastDuration(elapsedRealtime, false);
mInDischarge = true;
// If we are still within the last added duration, then re-added whatever remains.
if (mLastAddedTime == elapsedRealtime) {
mTotalTime += mLastAddedDuration;
}
- super.unplug(elapsedRealtime, batteryUptime, batteryRealtime);
+ super.onTimeStarted(elapsedRealtime, baseUptime, baseRealtime);
}
@Override
@@ -1060,11 +1285,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
- boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
+ boolean reset(boolean detachIfReset) {
final long now = SystemClock.elapsedRealtime() * 1000;
recomputeLastDuration(now, true);
boolean stillActive = mLastAddedTime == now;
- super.reset(stats, !stillActive && detachIfReset);
+ super.reset(!stillActive && detachIfReset);
return !stillActive;
}
}
@@ -1100,16 +1325,16 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mInList;
StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
- ArrayList<Unpluggable> unpluggables, Parcel in) {
- super(type, unpluggables, in);
+ TimeBase timeBase, Parcel in) {
+ super(type, timeBase, in);
mUid = uid;
mTimerPool = timerPool;
mUpdateTime = in.readLong();
}
StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
- ArrayList<Unpluggable> unpluggables) {
- super(type, unpluggables);
+ TimeBase timeBase) {
+ super(type, timeBase);
mUid = uid;
mTimerPool = timerPool;
}
@@ -1118,18 +1343,18 @@ public final class BatteryStatsImpl extends BatteryStats {
mTimeout = timeout;
}
- public void writeToParcel(Parcel out, long batteryRealtime) {
- super.writeToParcel(out, batteryRealtime);
+ public void writeToParcel(Parcel out, long elapsedRealtimeUs) {
+ super.writeToParcel(out, elapsedRealtimeUs);
out.writeLong(mUpdateTime);
}
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
if (mNesting > 0) {
if (DEBUG && mType < 0) {
Log.v(TAG, "old mUpdateTime=" + mUpdateTime);
}
- super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
- mUpdateTime = batteryRealtime;
+ super.onTimeStopped(elapsedRealtime, baseUptime, baseRealtime);
+ mUpdateTime = baseRealtime;
if (DEBUG && mType < 0) {
Log.v(TAG, "new mUpdateTime=" + mUpdateTime);
}
@@ -1142,14 +1367,14 @@ public final class BatteryStatsImpl extends BatteryStats {
+ " mAcquireTime=" + mAcquireTime);
}
- void startRunningLocked(BatteryStatsImpl stats) {
+ void startRunningLocked(long elapsedRealtimeMs) {
if (mNesting++ == 0) {
- mUpdateTime = stats.getBatteryRealtimeLocked(
- SystemClock.elapsedRealtime() * 1000);
+ final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
+ mUpdateTime = batteryRealtime;
if (mTimerPool != null) {
// Accumulate time to all currently active timers before adding
// this new one to the pool.
- refreshTimersLocked(stats, mTimerPool);
+ refreshTimersLocked(batteryRealtime, mTimerPool, null);
// Add this timer to the active pool
mTimerPool.add(this);
}
@@ -1168,21 +1393,35 @@ public final class BatteryStatsImpl extends BatteryStats {
return mNesting > 0;
}
- void stopRunningLocked(BatteryStatsImpl stats) {
+ long checkpointRunningLocked(long elapsedRealtimeMs) {
+ if (mNesting > 0) {
+ // We are running...
+ final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
+ if (mTimerPool != null) {
+ return refreshTimersLocked(batteryRealtime, mTimerPool, this);
+ }
+ final long heldTime = batteryRealtime - mUpdateTime;
+ mUpdateTime = batteryRealtime;
+ mTotalTime += heldTime;
+ return heldTime;
+ }
+ return 0;
+ }
+
+ void stopRunningLocked(long elapsedRealtimeMs) {
// Ignore attempt to stop a timer that isn't running
if (mNesting == 0) {
return;
}
if (--mNesting == 0) {
+ final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
if (mTimerPool != null) {
// Accumulate time to all active counters, scaled by the total
// active in the pool, before taking this one out of the pool.
- refreshTimersLocked(stats, mTimerPool);
+ refreshTimersLocked(batteryRealtime, mTimerPool, null);
// Remove this timer from the active pool
mTimerPool.remove(this);
} else {
- final long realtime = SystemClock.elapsedRealtime() * 1000;
- final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime);
mNesting = 1;
mTotalTime = computeRunTimeLocked(batteryRealtime);
mNesting = 0;
@@ -1204,19 +1443,23 @@ public final class BatteryStatsImpl extends BatteryStats {
// Update the total time for all other running Timers with the same type as this Timer
// due to a change in timer count
- private static void refreshTimersLocked(final BatteryStatsImpl stats,
- final ArrayList<StopwatchTimer> pool) {
- final long realtime = SystemClock.elapsedRealtime() * 1000;
- final long batteryRealtime = stats.getBatteryRealtimeLocked(realtime);
+ private static long refreshTimersLocked(long batteryRealtime,
+ final ArrayList<StopwatchTimer> pool, StopwatchTimer self) {
+ long selfTime = 0;
final int N = pool.size();
for (int i=N-1; i>= 0; i--) {
final StopwatchTimer t = pool.get(i);
long heldTime = batteryRealtime - t.mUpdateTime;
if (heldTime > 0) {
- t.mTotalTime += heldTime / N;
+ final long myTime = heldTime / N;
+ if (t == self) {
+ selfTime = myTime;
+ }
+ t.mTotalTime += myTime;
}
t.mUpdateTime = batteryRealtime;
}
+ return selfTime;
}
@Override
@@ -1235,12 +1478,11 @@ public final class BatteryStatsImpl extends BatteryStats {
return mCount;
}
- boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
+ boolean reset(boolean detachIfReset) {
boolean canDetach = mNesting <= 0;
- super.reset(stats, canDetach && detachIfReset);
+ super.reset(canDetach && detachIfReset);
if (mNesting > 0) {
- mUpdateTime = stats.getBatteryRealtimeLocked(
- SystemClock.elapsedRealtime() * 1000);
+ mUpdateTime = mTimeBase.getRealtime(SystemClock.elapsedRealtime() * 1000);
}
mAcquireTime = mTotalTime;
return canDetach;
@@ -1403,51 +1645,12 @@ public final class BatteryStatsImpl extends BatteryStats {
public SamplingTimer getKernelWakelockTimerLocked(String name) {
SamplingTimer kwlt = mKernelWakelockStats.get(name);
if (kwlt == null) {
- kwlt = new SamplingTimer(mUnpluggables, mOnBatteryInternal,
- true /* track reported values */);
+ kwlt = new SamplingTimer(mOnBatteryScreenOffTimeBase, true /* track reported values */);
mKernelWakelockStats.put(name, kwlt);
}
return kwlt;
}
- /**
- * Radio uptime in microseconds when transferring data. This value is very approximate.
- * @return
- */
- private long getCurrentRadioDataUptime() {
- try {
- File awakeTimeFile = new File("/sys/devices/virtual/net/rmnet0/awake_time_ms");
- if (!awakeTimeFile.exists()) return 0;
- BufferedReader br = new BufferedReader(new FileReader(awakeTimeFile));
- String line = br.readLine();
- br.close();
- return Long.parseLong(line) * 1000;
- } catch (NumberFormatException nfe) {
- // Nothing
- } catch (IOException ioe) {
- // Nothing
- }
- return 0;
- }
-
- /**
- * @deprecated use getRadioDataUptime
- */
- public long getRadioDataUptimeMs() {
- return getRadioDataUptime() / 1000;
- }
-
- /**
- * Returns the duration that the cell radio was up for data transfers.
- */
- public long getRadioDataUptime() {
- if (mRadioDataStart == -1) {
- return mRadioDataUptime;
- } else {
- return getCurrentRadioDataUptime() - mRadioDataStart;
- }
- }
-
private int getCurrentBluetoothPingCount() {
if (mBtHeadset != null) {
List<BluetoothDevice> deviceList = mBtHeadset.getConnectedDevices();
@@ -1474,7 +1677,285 @@ public final class BatteryStatsImpl extends BatteryStats {
mBtHeadset = headset;
}
- int mChangedBufferStates = 0;
+ private int writeHistoryTag(HistoryTag tag) {
+ Integer idxObj = mHistoryTagPool.get(tag);
+ int idx;
+ if (idxObj != null) {
+ idx = idxObj;
+ } else {
+ idx = mNextHistoryTagIdx;
+ HistoryTag key = new HistoryTag();
+ key.setTo(tag);
+ tag.poolIdx = idx;
+ mHistoryTagPool.put(key, idx);
+ mNextHistoryTagIdx++;
+ mNumHistoryTagChars += key.string.length() + 1;
+ }
+ return idx;
+ }
+
+ private void readHistoryTag(int index, HistoryTag tag) {
+ tag.string = mReadHistoryStrings[index];
+ tag.uid = mReadHistoryUids[index];
+ tag.poolIdx = index;
+ }
+
+ // Part of initial delta int that specifies the time delta.
+ static final int DELTA_TIME_MASK = 0x7ffff;
+ static final int DELTA_TIME_LONG = 0x7ffff; // The delta is a following long
+ static final int DELTA_TIME_INT = 0x7fffe; // The delta is a following int
+ static final int DELTA_TIME_ABS = 0x7fffd; // Following is an entire abs update.
+ // Flag in delta int: a new battery level int follows.
+ static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000;
+ // Flag in delta int: a new full state and battery status int follows.
+ static final int DELTA_STATE_FLAG = 0x00100000;
+ // Flag in delta int: a new full state2 int follows.
+ static final int DELTA_STATE2_FLAG = 0x00200000;
+ // Flag in delta int: contains a wakelock or wakeReason tag.
+ static final int DELTA_WAKELOCK_FLAG = 0x00400000;
+ // Flag in delta int: contains an event description.
+ static final int DELTA_EVENT_FLAG = 0x00800000;
+ // These upper bits are the frequently changing state bits.
+ static final int DELTA_STATE_MASK = 0xff000000;
+
+ // These are the pieces of battery state that are packed in to the upper bits of
+ // the state int that have been packed in to the first delta int. They must fit
+ // in DELTA_STATE_MASK.
+ static final int STATE_BATTERY_STATUS_MASK = 0x00000007;
+ static final int STATE_BATTERY_STATUS_SHIFT = 29;
+ static final int STATE_BATTERY_HEALTH_MASK = 0x00000007;
+ static final int STATE_BATTERY_HEALTH_SHIFT = 26;
+ static final int STATE_BATTERY_PLUG_MASK = 0x00000003;
+ static final int STATE_BATTERY_PLUG_SHIFT = 24;
+
+ public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
+ if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
+ dest.writeInt(DELTA_TIME_ABS);
+ cur.writeToParcel(dest, 0);
+ return;
+ }
+
+ final long deltaTime = cur.time - last.time;
+ final int lastBatteryLevelInt = buildBatteryLevelInt(last);
+ final int lastStateInt = buildStateInt(last);
+
+ int deltaTimeToken;
+ if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
+ deltaTimeToken = DELTA_TIME_LONG;
+ } else if (deltaTime >= DELTA_TIME_ABS) {
+ deltaTimeToken = DELTA_TIME_INT;
+ } else {
+ deltaTimeToken = (int)deltaTime;
+ }
+ int firstToken = deltaTimeToken | (cur.states&DELTA_STATE_MASK);
+ final int batteryLevelInt = buildBatteryLevelInt(cur);
+ final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
+ if (batteryLevelIntChanged) {
+ firstToken |= DELTA_BATTERY_LEVEL_FLAG;
+ }
+ final int stateInt = buildStateInt(cur);
+ final boolean stateIntChanged = stateInt != lastStateInt;
+ if (stateIntChanged) {
+ firstToken |= DELTA_STATE_FLAG;
+ }
+ if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+ firstToken |= DELTA_WAKELOCK_FLAG;
+ }
+ if (cur.eventCode != HistoryItem.EVENT_NONE) {
+ firstToken |= DELTA_EVENT_FLAG;
+ }
+ dest.writeInt(firstToken);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+ + " deltaTime=" + deltaTime);
+
+ if (deltaTimeToken >= DELTA_TIME_INT) {
+ if (deltaTimeToken == DELTA_TIME_INT) {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int)deltaTime);
+ dest.writeInt((int)deltaTime);
+ } else {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
+ dest.writeLong(deltaTime);
+ }
+ }
+ if (batteryLevelIntChanged) {
+ dest.writeInt(batteryLevelInt);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
+ + Integer.toHexString(batteryLevelInt)
+ + " batteryLevel=" + cur.batteryLevel
+ + " batteryTemp=" + cur.batteryTemperature
+ + " batteryVolt=" + (int)cur.batteryVoltage);
+ }
+ if (stateIntChanged) {
+ dest.writeInt(stateInt);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: stateToken=0x"
+ + Integer.toHexString(stateInt)
+ + " batteryStatus=" + cur.batteryStatus
+ + " batteryHealth=" + cur.batteryHealth
+ + " batteryPlugType=" + cur.batteryPlugType
+ + " states=0x" + Integer.toHexString(cur.states));
+ }
+ if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+ int wakeLockIndex;
+ int wakeReasonIndex;
+ if (cur.wakelockTag != null) {
+ wakeLockIndex = writeHistoryTag(cur.wakelockTag);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
+ + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
+ } else {
+ wakeLockIndex = 0xffff;
+ }
+ if (cur.wakeReasonTag != null) {
+ wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
+ + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
+ } else {
+ wakeReasonIndex = 0xffff;
+ }
+ dest.writeInt((wakeReasonIndex<<16) | wakeLockIndex);
+ }
+ if (cur.eventCode != HistoryItem.EVENT_NONE) {
+ int index = writeHistoryTag(cur.eventTag);
+ int codeAndIndex = (cur.eventCode&0xffff) | (index<<16);
+ dest.writeInt(codeAndIndex);
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#"
+ + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
+ + cur.eventTag.string);
+ }
+ }
+
+ private int buildBatteryLevelInt(HistoryItem h) {
+ return ((((int)h.batteryLevel)<<25)&0xfe000000)
+ | ((((int)h.batteryTemperature)<<14)&0x01ffc000)
+ | (((int)h.batteryVoltage)&0x00003fff);
+ }
+
+ private int buildStateInt(HistoryItem h) {
+ int plugType = 0;
+ if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_AC) != 0) {
+ plugType = 1;
+ } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_USB) != 0) {
+ plugType = 2;
+ } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) {
+ plugType = 3;
+ }
+ return ((h.batteryStatus&STATE_BATTERY_STATUS_MASK)<<STATE_BATTERY_STATUS_SHIFT)
+ | ((h.batteryHealth&STATE_BATTERY_HEALTH_MASK)<<STATE_BATTERY_HEALTH_SHIFT)
+ | ((plugType&STATE_BATTERY_PLUG_MASK)<<STATE_BATTERY_PLUG_SHIFT)
+ | (h.states&(~DELTA_STATE_MASK));
+ }
+
+ public void readHistoryDelta(Parcel src, HistoryItem cur) {
+ int firstToken = src.readInt();
+ int deltaTimeToken = firstToken&DELTA_TIME_MASK;
+ cur.cmd = HistoryItem.CMD_UPDATE;
+ cur.numReadInts = 1;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+ + " deltaTimeToken=" + deltaTimeToken);
+
+ if (deltaTimeToken < DELTA_TIME_ABS) {
+ cur.time += deltaTimeToken;
+ } else if (deltaTimeToken == DELTA_TIME_ABS) {
+ cur.time = src.readLong();
+ cur.numReadInts += 2;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: ABS time=" + cur.time);
+ cur.readFromParcel(src);
+ return;
+ } else if (deltaTimeToken == DELTA_TIME_INT) {
+ int delta = src.readInt();
+ cur.time += delta;
+ cur.numReadInts += 1;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + cur.time);
+ } else {
+ long delta = src.readLong();
+ if (DEBUG) Slog.i(TAG, "READ DELTA: time delta=" + delta + " new time=" + cur.time);
+ cur.time += delta;
+ cur.numReadInts += 2;
+ }
+
+ if ((firstToken&DELTA_BATTERY_LEVEL_FLAG) != 0) {
+ int batteryLevelInt = src.readInt();
+ cur.batteryLevel = (byte)((batteryLevelInt>>25)&0x7f);
+ cur.batteryTemperature = (short)((batteryLevelInt<<7)>>21);
+ cur.batteryVoltage = (char)(batteryLevelInt&0x3fff);
+ cur.numReadInts += 1;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: batteryToken=0x"
+ + Integer.toHexString(batteryLevelInt)
+ + " batteryLevel=" + cur.batteryLevel
+ + " batteryTemp=" + cur.batteryTemperature
+ + " batteryVolt=" + (int)cur.batteryVoltage);
+ }
+
+ if ((firstToken&DELTA_STATE_FLAG) != 0) {
+ int stateInt = src.readInt();
+ cur.states = (firstToken&DELTA_STATE_MASK) | (stateInt&(~DELTA_STATE_MASK));
+ cur.batteryStatus = (byte)((stateInt>>STATE_BATTERY_STATUS_SHIFT)
+ & STATE_BATTERY_STATUS_MASK);
+ cur.batteryHealth = (byte)((stateInt>>STATE_BATTERY_HEALTH_SHIFT)
+ & STATE_BATTERY_HEALTH_MASK);
+ cur.batteryPlugType = (byte)((stateInt>>STATE_BATTERY_PLUG_SHIFT)
+ & STATE_BATTERY_PLUG_MASK);
+ switch (cur.batteryPlugType) {
+ case 1:
+ cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_AC;
+ break;
+ case 2:
+ cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_USB;
+ break;
+ case 3:
+ cur.batteryPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
+ break;
+ }
+ cur.numReadInts += 1;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: stateToken=0x"
+ + Integer.toHexString(stateInt)
+ + " batteryStatus=" + cur.batteryStatus
+ + " batteryHealth=" + cur.batteryHealth
+ + " batteryPlugType=" + cur.batteryPlugType
+ + " states=0x" + Integer.toHexString(cur.states));
+ } else {
+ cur.states = (firstToken&DELTA_STATE_MASK) | (cur.states&(~DELTA_STATE_MASK));
+ }
+
+ if ((firstToken&DELTA_WAKELOCK_FLAG) != 0) {
+ int indexes = src.readInt();
+ int wakeLockIndex = indexes&0xffff;
+ int wakeReasonIndex = (indexes>>16)&0xffff;
+ if (wakeLockIndex != 0xffff) {
+ cur.wakelockTag = cur.localWakelockTag;
+ readHistoryTag(wakeLockIndex, cur.wakelockTag);
+ if (DEBUG) Slog.i(TAG, "READ DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
+ + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
+ } else {
+ cur.wakelockTag = null;
+ }
+ if (wakeReasonIndex != 0xffff) {
+ cur.wakeReasonTag = cur.localWakeReasonTag;
+ readHistoryTag(wakeReasonIndex, cur.wakeReasonTag);
+ if (DEBUG) Slog.i(TAG, "READ DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
+ + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
+ } else {
+ cur.wakeReasonTag = null;
+ }
+ cur.numReadInts += 1;
+ } else {
+ cur.wakelockTag = null;
+ cur.wakeReasonTag = null;
+ }
+
+ if ((firstToken&DELTA_EVENT_FLAG) != 0) {
+ cur.eventTag = cur.localEventTag;
+ final int codeAndIndex = src.readInt();
+ cur.eventCode = (codeAndIndex&0xffff);
+ final int index = ((codeAndIndex>>16)&0xffff);
+ readHistoryTag(index, cur.eventTag);
+ cur.numReadInts += 1;
+ if (DEBUG) Slog.i(TAG, "READ DELTA: event=" + cur.eventCode + " tag=#"
+ + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
+ + cur.eventTag.string);
+ } else {
+ cur.eventCode = HistoryItem.EVENT_NONE;
+ }
+ }
void addHistoryBufferLocked(long curTime) {
if (!mHaveBatteryLevel || !mRecordingHistory) {
@@ -1482,35 +1963,64 @@ public final class BatteryStatsImpl extends BatteryStats {
}
final long timeDiff = (mHistoryBaseTime+curTime) - mHistoryLastWritten.time;
+ final int diffStates = mHistoryLastWritten.states^mHistoryCur.states;
+ final int lastDiffStates = mHistoryLastWritten.states^mHistoryLastLastWritten.states;
+ if (DEBUG) Slog.i(TAG, "ADD: tdelta=" + timeDiff + " diff="
+ + Integer.toHexString(diffStates) + " lastDiff="
+ + Integer.toHexString(lastDiffStates));
if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
- && timeDiff < 2000
- && ((mHistoryLastWritten.states^mHistoryCur.states)&mChangedBufferStates) == 0) {
- // If the current is the same as the one before, then we no
- // longer need the entry.
+ && timeDiff < 1000 && (diffStates&lastDiffStates) == 0
+ && (mHistoryLastWritten.wakelockTag == null || mHistoryCur.wakelockTag == null)
+ && (mHistoryLastWritten.wakeReasonTag == null || mHistoryCur.wakeReasonTag == null)
+ && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE
+ || mHistoryCur.eventCode == HistoryItem.EVENT_NONE)
+ && mHistoryLastWritten.batteryLevel == mHistoryCur.batteryLevel
+ && mHistoryLastWritten.batteryStatus == mHistoryCur.batteryStatus
+ && mHistoryLastWritten.batteryHealth == mHistoryCur.batteryHealth
+ && mHistoryLastWritten.batteryPlugType == mHistoryCur.batteryPlugType
+ && mHistoryLastWritten.batteryTemperature == mHistoryCur.batteryTemperature
+ && mHistoryLastWritten.batteryVoltage == mHistoryCur.batteryVoltage) {
+ // We can merge this new change in with the last one. Merging is
+ // allows as long as only the states have changed, and within those states
+ // as long as no bit has changed both between now and the last entry, as
+ // well as the last entry and the one before it (so we capture any toggles).
+ if (DEBUG) Slog.i(TAG, "ADD: rewinding back to " + mHistoryBufferLastPos);
mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
mHistoryBufferLastPos = -1;
- if (mHistoryLastLastWritten.cmd == HistoryItem.CMD_UPDATE
- && timeDiff < 500 && mHistoryLastLastWritten.same(mHistoryCur)) {
- // If this results in us returning to the state written
- // prior to the last one, then we can just delete the last
- // written one and drop the new one. Nothing more to do.
- mHistoryLastWritten.setTo(mHistoryLastLastWritten);
- mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL;
- return;
- }
- mChangedBufferStates |= mHistoryLastWritten.states^mHistoryCur.states;
curTime = mHistoryLastWritten.time - mHistoryBaseTime;
+ // If the last written history had a wakelock tag, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have a wakelock tag.
+ if (mHistoryLastWritten.wakelockTag != null) {
+ mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+ mHistoryCur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag);
+ }
+ // If the last written history had a wake reason tag, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have a wakelock tag.
+ if (mHistoryLastWritten.wakeReasonTag != null) {
+ mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
+ mHistoryCur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag);
+ }
+ // If the last written history had an event, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have an event.
+ if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) {
+ mHistoryCur.eventCode = mHistoryLastWritten.eventCode;
+ mHistoryCur.eventTag = mHistoryCur.localEventTag;
+ mHistoryCur.eventTag.setTo(mHistoryLastWritten.eventTag);
+ }
mHistoryLastWritten.setTo(mHistoryLastLastWritten);
- } else {
- mChangedBufferStates = 0;
}
final int dataSize = mHistoryBuffer.dataSize();
if (dataSize >= MAX_HISTORY_BUFFER) {
if (!mHistoryOverflow) {
mHistoryOverflow = true;
+ addHistoryBufferLocked(curTime, HistoryItem.CMD_UPDATE);
addHistoryBufferLocked(curTime, HistoryItem.CMD_OVERFLOW);
+ return;
}
// Once we've reached the maximum number of items, we only
@@ -1523,28 +2033,30 @@ public final class BatteryStatsImpl extends BatteryStats {
& HistoryItem.MOST_INTERESTING_STATES) == 0)) {
return;
}
+
+ addHistoryBufferLocked(curTime, HistoryItem.CMD_UPDATE);
+ return;
}
addHistoryBufferLocked(curTime, HistoryItem.CMD_UPDATE);
}
- void addHistoryBufferLocked(long curTime, byte cmd) {
- int origPos = 0;
+ private void addHistoryBufferLocked(long curTime, byte cmd) {
if (mIteratingHistory) {
- origPos = mHistoryBuffer.dataPosition();
- mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+ throw new IllegalStateException("Can't do this while iterating history!");
}
mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
mHistoryLastLastWritten.setTo(mHistoryLastWritten);
mHistoryLastWritten.setTo(mHistoryBaseTime + curTime, cmd, mHistoryCur);
- mHistoryLastWritten.writeDelta(mHistoryBuffer, mHistoryLastLastWritten);
+ writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
mLastHistoryTime = curTime;
+ mHistoryCur.wakelockTag = null;
+ mHistoryCur.wakeReasonTag = null;
+ mHistoryCur.eventCode = HistoryItem.EVENT_NONE;
+ mHistoryCur.eventTag = null;
if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+ " now " + mHistoryBuffer.dataPosition()
+ " size is now " + mHistoryBuffer.dataSize());
- if (mIteratingHistory) {
- mHistoryBuffer.setDataPosition(origPos);
- }
}
int mChangedStates = 0;
@@ -1571,7 +2083,7 @@ public final class BatteryStatsImpl extends BatteryStats {
// longer need the entry.
if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE
&& (mHistoryBaseTime+curTime) < (mHistoryEnd.time+500)
- && mHistoryLastEnd.same(mHistoryCur)) {
+ && mHistoryLastEnd.sameNonEvent(mHistoryCur)) {
mHistoryLastEnd.next = null;
mHistoryEnd.next = mHistoryCache;
mHistoryCache = mHistoryEnd;
@@ -1608,6 +2120,14 @@ public final class BatteryStatsImpl extends BatteryStats {
addHistoryRecordLocked(curTime, HistoryItem.CMD_UPDATE);
}
+ void addHistoryEventLocked(long curTime, int code, String name, int uid) {
+ mHistoryCur.eventCode = code;
+ mHistoryCur.eventTag = mHistoryCur.localEventTag;
+ mHistoryCur.eventTag.string = name;
+ mHistoryCur.eventTag.uid = uid;
+ addHistoryBufferLocked(curTime);
+ }
+
void addHistoryRecordLocked(long curTime, byte cmd) {
HistoryItem rec = mHistoryCache;
if (rec != null) {
@@ -1648,44 +2168,113 @@ public final class BatteryStatsImpl extends BatteryStats {
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
- mHistoryBuffer.setDataCapacity(MAX_HISTORY_BUFFER/2);
- mHistoryLastLastWritten.cmd = HistoryItem.CMD_NULL;
- mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+ mHistoryBuffer.setDataCapacity(MAX_HISTORY_BUFFER / 2);
+ mHistoryLastLastWritten.clear();
+ mHistoryLastWritten.clear();
+ mHistoryTagPool.clear();
+ mNextHistoryTagIdx = 0;
+ mNumHistoryTagChars = 0;
mHistoryBufferLastPos = -1;
mHistoryOverflow = false;
}
- public void doUnplugLocked(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
- for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
- mUnpluggables.get(i).unplug(elapsedRealtime, batteryUptime, batteryRealtime);
+ public void updateTimeBasesLocked(boolean unplugged, boolean screenOff, long uptime,
+ long realtime) {
+ if (mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime)) {
+ if (unplugged) {
+ // Track bt headset ping count
+ mBluetoothPingStart = getCurrentBluetoothPingCount();
+ mBluetoothPingCount = 0;
+ } else {
+ // Track bt headset ping count
+ mBluetoothPingCount = getBluetoothPingCount();
+ mBluetoothPingStart = -1;
+ }
}
- // Track radio awake time
- mRadioDataStart = getCurrentRadioDataUptime();
- mRadioDataUptime = 0;
+ boolean unpluggedScreenOff = unplugged && screenOff;
+ if (unpluggedScreenOff != mOnBatteryScreenOffTimeBase.isRunning()) {
+ updateKernelWakelocksLocked();
+ requestWakelockCpuUpdate();
+ if (!unpluggedScreenOff) {
+ // We are switching to no longer tracking wake locks, but we want
+ // the next CPU update we receive to take them in to account.
+ mDistributeWakelockCpu = true;
+ }
+ mOnBatteryScreenOffTimeBase.setRunning(unpluggedScreenOff, uptime, realtime);
+ }
+ }
- // Track bt headset ping count
- mBluetoothPingStart = getCurrentBluetoothPingCount();
- mBluetoothPingCount = 0;
+ public void addIsolatedUidLocked(int isolatedUid, int appUid) {
+ mIsolatedUids.put(isolatedUid, appUid);
}
- public void doPlugLocked(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
- for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
- mUnpluggables.get(i).plug(elapsedRealtime, batteryUptime, batteryRealtime);
+ public void removeIsolatedUidLocked(int isolatedUid, int appUid) {
+ int curUid = mIsolatedUids.get(isolatedUid, -1);
+ if (curUid == appUid) {
+ mIsolatedUids.delete(isolatedUid);
}
+ }
- // Track radio awake time
- mRadioDataUptime = getRadioDataUptime();
- mRadioDataStart = -1;
+ public int mapUid(int uid) {
+ int isolated = mIsolatedUids.get(uid, -1);
+ return isolated > 0 ? isolated : uid;
+ }
- // Track bt headset ping count
- mBluetoothPingCount = getBluetoothPingCount();
- mBluetoothPingStart = -1;
+ public void noteEventLocked(int code, String name, int uid) {
+ uid = mapUid(uid);
+ if ((code&HistoryItem.EVENT_FLAG_START) != 0) {
+ int idx = code&~HistoryItem.EVENT_FLAG_START;
+ HashMap<String, SparseBooleanArray> active = mActiveEvents[idx];
+ if (active == null) {
+ active = new HashMap<String, SparseBooleanArray>();
+ mActiveEvents[idx] = active;
+ }
+ SparseBooleanArray uids = active.get(name);
+ if (uids == null) {
+ uids = new SparseBooleanArray();
+ active.put(name, uids);
+ }
+ if (uids.get(uid)) {
+ // Already set, nothing to do!
+ return;
+ }
+ uids.put(uid, true);
+ } else if ((code&HistoryItem.EVENT_FLAG_FINISH) != 0) {
+ int idx = code&~HistoryItem.EVENT_FLAG_FINISH;
+ HashMap<String, SparseBooleanArray> active = mActiveEvents[idx];
+ if (active == null) {
+ // not currently active, nothing to do.
+ return;
+ }
+ SparseBooleanArray uids = active.get(name);
+ if (uids == null) {
+ // not currently active, nothing to do.
+ return;
+ }
+ idx = uids.indexOfKey(uid);
+ if (idx < 0 || !uids.valueAt(idx)) {
+ // not currently active, nothing to do.
+ return;
+ }
+ uids.removeAt(idx);
+ if (uids.size() <= 0) {
+ active.remove(name);
+ }
+ }
+ addHistoryEventLocked(SystemClock.elapsedRealtime(), code, name, uid);
}
- int mWakeLockNesting;
+ private void requestWakelockCpuUpdate() {
+ if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
+ Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
+ mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS);
+ }
+ }
- public void noteStartWakeLocked(int uid, int pid, String name, int type) {
+ public void noteStartWakeLocked(int uid, int pid, String name, String historyName, int type,
+ boolean unimportantForLogging, long elapsedRealtime) {
+ uid = mapUid(uid);
if (type == WAKE_TYPE_PARTIAL) {
// Only care about partial wake locks, since full wake locks
// will be canceled when the user puts the screen to sleep.
@@ -1693,65 +2282,109 @@ public final class BatteryStatsImpl extends BatteryStats {
mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+ mHistoryCur.wakelockTag.string = historyName != null ? historyName : name;
+ mHistoryCur.wakelockTag.uid = uid;
+ mWakeLockImportant = !unimportantForLogging;
+ addHistoryRecordLocked(elapsedRealtime);
+ } else if (!mWakeLockImportant && !unimportantForLogging) {
+ if (mHistoryLastWritten.wakelockTag != null) {
+ // We'll try to update the last tag.
+ mHistoryLastWritten.wakelockTag = null;
+ mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+ mHistoryCur.wakelockTag.string = historyName != null ? historyName : name;
+ mHistoryCur.wakelockTag.uid = uid;
+ addHistoryRecordLocked(elapsedRealtime);
+ }
+ mWakeLockImportant = true;
}
mWakeLockNesting++;
}
if (uid >= 0) {
- if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
- Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
- mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS);
- }
- getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type);
+ requestWakelockCpuUpdate();
+ getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type, elapsedRealtime);
}
}
- public void noteStopWakeLocked(int uid, int pid, String name, int type) {
+ public void noteStopWakeLocked(int uid, int pid, String name, int type, long elapsedRealtime) {
+ uid = mapUid(uid);
if (type == WAKE_TYPE_PARTIAL) {
mWakeLockNesting--;
if (mWakeLockNesting == 0) {
mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
}
}
if (uid >= 0) {
- if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
- Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
- mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS);
- }
- getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type);
+ requestWakelockCpuUpdate();
+ getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type, elapsedRealtime);
}
}
- public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) {
- int N = ws.size();
+ public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name,
+ String historyName, int type, boolean unimportantForLogging) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final int N = ws.size();
for (int i=0; i<N; i++) {
- noteStartWakeLocked(ws.get(i), pid, name, type);
+ noteStartWakeLocked(ws.get(i), pid, name, historyName, type, unimportantForLogging,
+ elapsedRealtime);
+ }
+ }
+
+ public void noteChangeWakelockFromSourceLocked(WorkSource ws, int pid, String name, int type,
+ WorkSource newWs, int newPid, String newName,
+ String newHistoryName, int newType, boolean newUnimportantForLogging) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ // For correct semantics, we start the need worksources first, so that we won't
+ // make inappropriate history items as if all wake locks went away and new ones
+ // appeared. This is okay because tracking of wake locks allows nesting.
+ final int NN = ws.size();
+ for (int i=0; i<NN; i++) {
+ noteStartWakeLocked(newWs.get(i), newPid, newName, newHistoryName, newType,
+ newUnimportantForLogging, elapsedRealtime);
+ }
+ final int NO = ws.size();
+ for (int i=0; i<NO; i++) {
+ noteStopWakeLocked(ws.get(i), pid, name, type, elapsedRealtime);
}
}
public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) {
- int N = ws.size();
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final int N = ws.size();
for (int i=0; i<N; i++) {
- noteStopWakeLocked(ws.get(i), pid, name, type);
+ noteStopWakeLocked(ws.get(i), pid, name, type, elapsedRealtime);
}
}
+ public void noteWakeupReasonLocked(int irq, String reason) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ if (DEBUG_HISTORY) Slog.v(TAG, "Wakeup reason irq #" + irq + "\"" + reason +"\": "
+ + Integer.toHexString(mHistoryCur.states));
+ mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
+ mHistoryCur.wakeReasonTag.string = reason;
+ mHistoryCur.wakeReasonTag.uid = irq;
+ addHistoryRecordLocked(elapsedRealtime);
+ }
+
public int startAddingCpuLocked() {
mHandler.removeMessages(MSG_UPDATE_WAKELOCKS);
- if (mScreenOn) {
- return 0;
- }
-
final int N = mPartialTimers.size();
if (N == 0) {
mLastPartialTimers.clear();
+ mDistributeWakelockCpu = false;
+ return 0;
+ }
+
+ if (!mOnBatteryScreenOffTimeBase.isRunning() && !mDistributeWakelockCpu) {
return 0;
}
+ mDistributeWakelockCpu = false;
+
// How many timers should consume CPU? Only want to include ones
// that have already been in the list.
for (int i=0; i<N; i++) {
@@ -1838,6 +2471,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void noteProcessDiedLocked(int uid, int pid) {
+ uid = mapUid(uid);
Uid u = mUidStats.get(uid);
if (u != null) {
u.mPids.remove(pid);
@@ -1845,17 +2479,19 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public long getProcessWakeTime(int uid, int pid, long realtime) {
+ uid = mapUid(uid);
Uid u = mUidStats.get(uid);
if (u != null) {
Uid.Pid p = u.mPids.get(pid);
if (p != null) {
- return p.mWakeSum + (p.mWakeStart != 0 ? (realtime - p.mWakeStart) : 0);
+ return p.mWakeSumMs + (p.mWakeNesting > 0 ? (realtime - p.mWakeStartMs) : 0);
}
}
return 0;
}
public void reportExcessiveWakeLocked(int uid, String proc, long overTime, long usedTime) {
+ uid = mapUid(uid);
Uid u = mUidStats.get(uid);
if (u != null) {
u.reportExcessiveWakeLocked(proc, overTime, usedTime);
@@ -1863,6 +2499,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void reportExcessiveCpuLocked(int uid, String proc, long overTime, long usedTime) {
+ uid = mapUid(uid);
Uid u = mUidStats.get(uid);
if (u != null) {
u.reportExcessiveCpuLocked(proc, overTime, usedTime);
@@ -1872,67 +2509,79 @@ public final class BatteryStatsImpl extends BatteryStats {
int mSensorNesting;
public void noteStartSensorLocked(int uid, int sensor) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
if (mSensorNesting == 0) {
mHistoryCur.states |= HistoryItem.STATE_SENSOR_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Start sensor to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
}
mSensorNesting++;
- getUidStatsLocked(uid).noteStartSensor(sensor);
+ getUidStatsLocked(uid).noteStartSensor(sensor, elapsedRealtime);
}
public void noteStopSensorLocked(int uid, int sensor) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mSensorNesting--;
if (mSensorNesting == 0) {
mHistoryCur.states &= ~HistoryItem.STATE_SENSOR_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Stop sensor to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
}
- getUidStatsLocked(uid).noteStopSensor(sensor);
+ getUidStatsLocked(uid).noteStopSensor(sensor, elapsedRealtime);
}
int mGpsNesting;
public void noteStartGpsLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
if (mGpsNesting == 0) {
mHistoryCur.states |= HistoryItem.STATE_GPS_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Start GPS to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
}
mGpsNesting++;
- getUidStatsLocked(uid).noteStartGps();
+ getUidStatsLocked(uid).noteStartGps(elapsedRealtime);
}
public void noteStopGpsLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mGpsNesting--;
if (mGpsNesting == 0) {
mHistoryCur.states &= ~HistoryItem.STATE_GPS_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Stop GPS to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
}
- getUidStatsLocked(uid).noteStopGps();
+ getUidStatsLocked(uid).noteStopGps(elapsedRealtime);
}
public void noteScreenOnLocked() {
if (!mScreenOn) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
mScreenOn = true;
- mScreenOnTimer.startRunningLocked(this);
+ mScreenOnTimer.startRunningLocked(elapsedRealtime);
if (mScreenBrightnessBin >= 0) {
- mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(this);
+ mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(elapsedRealtime);
}
+ updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), false,
+ SystemClock.uptimeMillis() * 1000, elapsedRealtime * 1000);
+
// Fake a wake lock, so we consider the device waked as long
// as the screen is on.
- noteStartWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL);
-
+ noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false, elapsedRealtime);
+
// Update discharge amounts.
if (mOnBatteryInternal) {
updateDischargeScreenLevelsLocked(false, true);
@@ -1942,18 +2591,22 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteScreenOffLocked() {
if (mScreenOn) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
mScreenOn = false;
- mScreenOnTimer.stopRunningLocked(this);
+ mScreenOnTimer.stopRunningLocked(elapsedRealtime);
if (mScreenBrightnessBin >= 0) {
- mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this);
+ mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime);
}
- noteStopWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL);
-
+ noteStopWakeLocked(-1, -1, "screen", WAKE_TYPE_PARTIAL, elapsedRealtime);
+
+ updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), true,
+ SystemClock.uptimeMillis() * 1000, elapsedRealtime * 1000);
+
// Update discharge amounts.
if (mOnBatteryInternal) {
updateDischargeScreenLevelsLocked(true, false);
@@ -1967,16 +2620,17 @@ public final class BatteryStatsImpl extends BatteryStats {
if (bin < 0) bin = 0;
else if (bin >= NUM_SCREEN_BRIGHTNESS_BINS) bin = NUM_SCREEN_BRIGHTNESS_BINS-1;
if (mScreenBrightnessBin != bin) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_BRIGHTNESS_MASK)
| (bin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
if (DEBUG_HISTORY) Slog.v(TAG, "Screen brightness " + bin + " to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
if (mScreenOn) {
if (mScreenBrightnessBin >= 0) {
- mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this);
+ mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime);
}
- mScreenBrightnessTimer[bin].startRunningLocked(this);
+ mScreenBrightnessTimer[bin].startRunningLocked(elapsedRealtime);
}
mScreenBrightnessBin = bin;
}
@@ -1987,38 +2641,66 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void noteUserActivityLocked(int uid, int event) {
- getUidStatsLocked(uid).noteUserActivityLocked(event);
+ if (mOnBatteryInternal) {
+ uid = mapUid(uid);
+ getUidStatsLocked(uid).noteUserActivityLocked(event);
+ }
+ }
+
+ public void noteDataConnectionActive(int type, boolean active) {
+ if (ConnectivityManager.isNetworkTypeMobile(type)) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ if (mMobileRadioActive != active) {
+ if (active) mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
+ else mHistoryCur.states &= ~HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Mobile network active " + active + " to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(elapsedRealtime);
+ mMobileRadioActive = active;
+ if (active) {
+ mMobileRadioActiveTimer.startRunningLocked(elapsedRealtime);
+ mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtime);
+ } else {
+ updateNetworkActivityLocked(NET_UPDATE_MOBILE, elapsedRealtime);
+ mMobileRadioActiveTimer.stopRunningLocked(elapsedRealtime);
+ mMobileRadioActivePerAppTimer.stopRunningLocked(elapsedRealtime);
+ }
+ }
+ }
}
public void notePhoneOnLocked() {
if (!mPhoneOn) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mHistoryCur.states |= HistoryItem.STATE_PHONE_IN_CALL_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
mPhoneOn = true;
- mPhoneOnTimer.startRunningLocked(this);
+ mPhoneOnTimer.startRunningLocked(elapsedRealtime);
}
}
public void notePhoneOffLocked() {
if (mPhoneOn) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mHistoryCur.states &= ~HistoryItem.STATE_PHONE_IN_CALL_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
mPhoneOn = false;
- mPhoneOnTimer.stopRunningLocked(this);
+ mPhoneOnTimer.stopRunningLocked(elapsedRealtime);
}
}
void stopAllSignalStrengthTimersLocked(int except) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
if (i == except) {
continue;
}
while (mPhoneSignalStrengthsTimer[i].isRunningLocked()) {
- mPhoneSignalStrengthsTimer[i].stopRunningLocked(this);
+ mPhoneSignalStrengthsTimer[i].stopRunningLocked(elapsedRealtime);
}
}
}
@@ -2036,26 +2718,28 @@ public final class BatteryStatsImpl extends BatteryStats {
return state;
}
- private void updateAllPhoneStateLocked(int state, int simState, int bin) {
+ private void updateAllPhoneStateLocked(int state, int simState, int strengthBin) {
boolean scanning = false;
boolean newHistory = false;
mPhoneServiceStateRaw = state;
mPhoneSimStateRaw = simState;
- mPhoneSignalStrengthBinRaw = bin;
+ mPhoneSignalStrengthBinRaw = strengthBin;
+
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
if (simState == TelephonyManager.SIM_STATE_ABSENT) {
// In this case we will always be STATE_OUT_OF_SERVICE, so need
// to infer that we are scanning from other data.
if (state == ServiceState.STATE_OUT_OF_SERVICE
- && bin > SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+ && strengthBin > SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
state = ServiceState.STATE_IN_SERVICE;
}
}
// If the phone is powered off, stop all timers.
if (state == ServiceState.STATE_POWER_OFF) {
- bin = -1;
+ strengthBin = -1;
// If we are in service, make sure the correct signal string timer is running.
} else if (state == ServiceState.STATE_IN_SERVICE) {
@@ -2065,13 +2749,13 @@ public final class BatteryStatsImpl extends BatteryStats {
// bin and have the scanning bit set.
} else if (state == ServiceState.STATE_OUT_OF_SERVICE) {
scanning = true;
- bin = SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ strengthBin = SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
if (!mPhoneSignalScanningTimer.isRunningLocked()) {
mHistoryCur.states |= HistoryItem.STATE_PHONE_SCANNING_FLAG;
newHistory = true;
if (DEBUG_HISTORY) Slog.v(TAG, "Phone started scanning to: "
+ Integer.toHexString(mHistoryCur.states));
- mPhoneSignalScanningTimer.startRunningLocked(this);
+ mPhoneSignalScanningTimer.startRunningLocked(elapsedRealtime);
}
}
@@ -2082,7 +2766,7 @@ public final class BatteryStatsImpl extends BatteryStats {
if (DEBUG_HISTORY) Slog.v(TAG, "Phone stopped scanning to: "
+ Integer.toHexString(mHistoryCur.states));
newHistory = true;
- mPhoneSignalScanningTimer.stopRunningLocked(this);
+ mPhoneSignalScanningTimer.stopRunningLocked(elapsedRealtime);
}
}
@@ -2095,27 +2779,28 @@ public final class BatteryStatsImpl extends BatteryStats {
mPhoneServiceState = state;
}
- if (mPhoneSignalStrengthBin != bin) {
+ if (mPhoneSignalStrengthBin != strengthBin) {
if (mPhoneSignalStrengthBin >= 0) {
- mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].stopRunningLocked(this);
+ mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].stopRunningLocked(
+ elapsedRealtime);
}
- if (bin >= 0) {
- if (!mPhoneSignalStrengthsTimer[bin].isRunningLocked()) {
- mPhoneSignalStrengthsTimer[bin].startRunningLocked(this);
+ if (strengthBin >= 0) {
+ if (!mPhoneSignalStrengthsTimer[strengthBin].isRunningLocked()) {
+ mPhoneSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime);
}
mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_SIGNAL_STRENGTH_MASK)
- | (bin << HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + bin + " to: "
+ | (strengthBin << HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT);
+ if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: "
+ Integer.toHexString(mHistoryCur.states));
newHistory = true;
} else {
stopAllSignalStrengthTimersLocked(-1);
}
- mPhoneSignalStrengthBin = bin;
+ mPhoneSignalStrengthBin = strengthBin;
}
if (newHistory) {
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
}
}
@@ -2189,120 +2874,134 @@ public final class BatteryStatsImpl extends BatteryStats {
}
if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
if (mPhoneDataConnectionType != bin) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK)
| (bin << HistoryItem.STATE_DATA_CONNECTION_SHIFT);
if (DEBUG_HISTORY) Slog.v(TAG, "Data connection " + bin + " to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
if (mPhoneDataConnectionType >= 0) {
- mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(this);
+ mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(
+ elapsedRealtime);
}
mPhoneDataConnectionType = bin;
- mPhoneDataConnectionsTimer[bin].startRunningLocked(this);
+ mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtime);
}
}
public void noteWifiOnLocked() {
if (!mWifiOn) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mHistoryCur.states |= HistoryItem.STATE_WIFI_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
mWifiOn = true;
- mWifiOnTimer.startRunningLocked(this);
+ mWifiOnTimer.startRunningLocked(elapsedRealtime);
}
}
public void noteWifiOffLocked() {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
if (mWifiOn) {
mHistoryCur.states &= ~HistoryItem.STATE_WIFI_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
mWifiOn = false;
- mWifiOnTimer.stopRunningLocked(this);
- }
- if (mWifiOnUid >= 0) {
- getUidStatsLocked(mWifiOnUid).noteWifiStoppedLocked();
- mWifiOnUid = -1;
+ mWifiOnTimer.stopRunningLocked(elapsedRealtime);
}
}
public void noteAudioOnLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
if (!mAudioOn) {
mHistoryCur.states |= HistoryItem.STATE_AUDIO_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Audio on to: "
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(SystemClock.elapsedRealtime());
mAudioOn = true;
- mAudioOnTimer.startRunningLocked(this);
+ mAudioOnTimer.startRunningLocked(elapsedRealtime);
}
- getUidStatsLocked(uid).noteAudioTurnedOnLocked();
+ getUidStatsLocked(uid).noteAudioTurnedOnLocked(elapsedRealtime);
}
public void noteAudioOffLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
if (mAudioOn) {
mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: "
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(SystemClock.elapsedRealtime());
mAudioOn = false;
- mAudioOnTimer.stopRunningLocked(this);
+ mAudioOnTimer.stopRunningLocked(elapsedRealtime);
}
- getUidStatsLocked(uid).noteAudioTurnedOffLocked();
+ getUidStatsLocked(uid).noteAudioTurnedOffLocked(elapsedRealtime);
}
public void noteVideoOnLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
if (!mVideoOn) {
mHistoryCur.states |= HistoryItem.STATE_VIDEO_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Video on to: "
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(SystemClock.elapsedRealtime());
mVideoOn = true;
- mVideoOnTimer.startRunningLocked(this);
+ mVideoOnTimer.startRunningLocked(elapsedRealtime);
}
- getUidStatsLocked(uid).noteVideoTurnedOnLocked();
+ getUidStatsLocked(uid).noteVideoTurnedOnLocked(elapsedRealtime);
}
public void noteVideoOffLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
if (mVideoOn) {
mHistoryCur.states &= ~HistoryItem.STATE_VIDEO_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: "
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(SystemClock.elapsedRealtime());
mVideoOn = false;
- mVideoOnTimer.stopRunningLocked(this);
+ mVideoOnTimer.stopRunningLocked(elapsedRealtime);
}
- getUidStatsLocked(uid).noteVideoTurnedOffLocked();
+ getUidStatsLocked(uid).noteVideoTurnedOffLocked(elapsedRealtime);
}
public void noteActivityResumedLocked(int uid) {
- getUidStatsLocked(uid).noteActivityResumedLocked();
+ uid = mapUid(uid);
+ getUidStatsLocked(uid).noteActivityResumedLocked(SystemClock.elapsedRealtime());
}
public void noteActivityPausedLocked(int uid) {
- getUidStatsLocked(uid).noteActivityPausedLocked();
+ uid = mapUid(uid);
+ getUidStatsLocked(uid).noteActivityPausedLocked(SystemClock.elapsedRealtime());
}
public void noteVibratorOnLocked(int uid, long durationMillis) {
+ uid = mapUid(uid);
getUidStatsLocked(uid).noteVibratorOnLocked(durationMillis);
}
public void noteVibratorOffLocked(int uid) {
+ uid = mapUid(uid);
getUidStatsLocked(uid).noteVibratorOffLocked();
}
public void noteWifiRunningLocked(WorkSource ws) {
if (!mGlobalWifiRunning) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mHistoryCur.states |= HistoryItem.STATE_WIFI_RUNNING_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: "
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(SystemClock.elapsedRealtime());
mGlobalWifiRunning = true;
- mGlobalWifiRunningTimer.startRunningLocked(this);
+ mGlobalWifiRunningTimer.startRunningLocked(elapsedRealtime);
int N = ws.size();
for (int i=0; i<N; i++) {
- getUidStatsLocked(ws.get(i)).noteWifiRunningLocked();
+ int uid = mapUid(ws.get(i));
+ getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime);
}
} else {
Log.w(TAG, "noteWifiRunningLocked -- called while WIFI running");
@@ -2311,13 +3010,16 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteWifiRunningChangedLocked(WorkSource oldWs, WorkSource newWs) {
if (mGlobalWifiRunning) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
int N = oldWs.size();
for (int i=0; i<N; i++) {
- getUidStatsLocked(oldWs.get(i)).noteWifiStoppedLocked();
+ int uid = mapUid(oldWs.get(i));
+ getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime);
}
N = newWs.size();
for (int i=0; i<N; i++) {
- getUidStatsLocked(newWs.get(i)).noteWifiRunningLocked();
+ int uid = mapUid(newWs.get(i));
+ getUidStatsLocked(uid).noteWifiRunningLocked(elapsedRealtime);
}
} else {
Log.w(TAG, "noteWifiRunningChangedLocked -- called while WIFI not running");
@@ -2326,121 +3028,165 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteWifiStoppedLocked(WorkSource ws) {
if (mGlobalWifiRunning) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RUNNING_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
mGlobalWifiRunning = false;
- mGlobalWifiRunningTimer.stopRunningLocked(this);
+ mGlobalWifiRunningTimer.stopRunningLocked(elapsedRealtime);
int N = ws.size();
for (int i=0; i<N; i++) {
- getUidStatsLocked(ws.get(i)).noteWifiStoppedLocked();
+ int uid = mapUid(ws.get(i));
+ getUidStatsLocked(uid).noteWifiStoppedLocked(elapsedRealtime);
}
} else {
Log.w(TAG, "noteWifiStoppedLocked -- called while WIFI not running");
}
}
+ public void noteWifiStateLocked(int wifiState, String accessPoint) {
+ if (DEBUG) Log.i(TAG, "WiFi state -> " + wifiState);
+ if (mWifiState != wifiState) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ if (mWifiState >= 0) {
+ mWifiStateTimer[mWifiState].stopRunningLocked(elapsedRealtime);
+ }
+ mWifiState = wifiState;
+ mWifiStateTimer[wifiState].startRunningLocked(elapsedRealtime);
+ }
+ }
+
public void noteBluetoothOnLocked() {
if (!mBluetoothOn) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mHistoryCur.states |= HistoryItem.STATE_BLUETOOTH_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
mBluetoothOn = true;
- mBluetoothOnTimer.startRunningLocked(this);
+ mBluetoothOnTimer.startRunningLocked(elapsedRealtime);
}
}
public void noteBluetoothOffLocked() {
if (mBluetoothOn) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mHistoryCur.states &= ~HistoryItem.STATE_BLUETOOTH_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
mBluetoothOn = false;
- mBluetoothOnTimer.stopRunningLocked(this);
+ mBluetoothOnTimer.stopRunningLocked(elapsedRealtime);
+ }
+ }
+
+ public void noteBluetoothStateLocked(int bluetoothState) {
+ if (DEBUG) Log.i(TAG, "Bluetooth state -> " + bluetoothState);
+ if (mBluetoothState != bluetoothState) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ if (mBluetoothState >= 0) {
+ mBluetoothStateTimer[mBluetoothState].stopRunningLocked(elapsedRealtime);
+ }
+ mBluetoothState = bluetoothState;
+ mBluetoothStateTimer[bluetoothState].startRunningLocked(elapsedRealtime);
}
}
int mWifiFullLockNesting = 0;
public void noteFullWifiLockAcquiredLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
if (mWifiFullLockNesting == 0) {
mHistoryCur.states |= HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
}
mWifiFullLockNesting++;
- getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked();
+ getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked(elapsedRealtime);
}
public void noteFullWifiLockReleasedLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mWifiFullLockNesting--;
if (mWifiFullLockNesting == 0) {
mHistoryCur.states &= ~HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
}
- getUidStatsLocked(uid).noteFullWifiLockReleasedLocked();
+ getUidStatsLocked(uid).noteFullWifiLockReleasedLocked(elapsedRealtime);
}
int mWifiScanNesting = 0;
public void noteWifiScanStartedLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
if (mWifiScanNesting == 0) {
mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan started for: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
}
mWifiScanNesting++;
- getUidStatsLocked(uid).noteWifiScanStartedLocked();
+ getUidStatsLocked(uid).noteWifiScanStartedLocked(elapsedRealtime);
}
public void noteWifiScanStoppedLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mWifiScanNesting--;
if (mWifiScanNesting == 0) {
mHistoryCur.states &= ~HistoryItem.STATE_WIFI_SCAN_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan stopped for: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
}
- getUidStatsLocked(uid).noteWifiScanStoppedLocked();
+ getUidStatsLocked(uid).noteWifiScanStoppedLocked(elapsedRealtime);
}
public void noteWifiBatchedScanStartedLocked(int uid, int csph) {
- getUidStatsLocked(uid).noteWifiBatchedScanStartedLocked(csph);
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ getUidStatsLocked(uid).noteWifiBatchedScanStartedLocked(csph, elapsedRealtime);
}
public void noteWifiBatchedScanStoppedLocked(int uid) {
- getUidStatsLocked(uid).noteWifiBatchedScanStoppedLocked();
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ getUidStatsLocked(uid).noteWifiBatchedScanStoppedLocked(elapsedRealtime);
}
int mWifiMulticastNesting = 0;
public void noteWifiMulticastEnabledLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
if (mWifiMulticastNesting == 0) {
mHistoryCur.states |= HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
}
mWifiMulticastNesting++;
- getUidStatsLocked(uid).noteWifiMulticastEnabledLocked();
+ getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(elapsedRealtime);
}
public void noteWifiMulticastDisabledLocked(int uid) {
+ uid = mapUid(uid);
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
mWifiMulticastNesting--;
if (mWifiMulticastNesting == 0) {
mHistoryCur.states &= ~HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: "
+ Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ addHistoryRecordLocked(elapsedRealtime);
}
- getUidStatsLocked(uid).noteWifiMulticastDisabledLocked();
+ getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(elapsedRealtime);
}
public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) {
@@ -2499,16 +3245,45 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ private static String[] includeInStringArray(String[] array, String str) {
+ if (ArrayUtils.indexOf(array, str) >= 0) {
+ return array;
+ }
+ String[] newArray = new String[array.length+1];
+ System.arraycopy(array, 0, newArray, 0, array.length);
+ newArray[array.length] = str;
+ return newArray;
+ }
+
+ private static String[] excludeFromStringArray(String[] array, String str) {
+ int index = ArrayUtils.indexOf(array, str);
+ if (index >= 0) {
+ String[] newArray = new String[array.length-1];
+ if (index > 0) {
+ System.arraycopy(array, 0, newArray, 0, index);
+ }
+ if (index < array.length-1) {
+ System.arraycopy(array, index+1, newArray, index, array.length-index-1);
+ }
+ return newArray;
+ }
+ return array;
+ }
+
public void noteNetworkInterfaceTypeLocked(String iface, int networkType) {
if (ConnectivityManager.isNetworkTypeMobile(networkType)) {
- mMobileIfaces.add(iface);
+ mMobileIfaces = includeInStringArray(mMobileIfaces, iface);
+ if (DEBUG) Slog.d(TAG, "Note mobile iface " + iface + ": " + mMobileIfaces);
} else {
- mMobileIfaces.remove(iface);
+ mMobileIfaces = excludeFromStringArray(mMobileIfaces, iface);
+ if (DEBUG) Slog.d(TAG, "Note non-mobile iface " + iface + ": " + mMobileIfaces);
}
if (ConnectivityManager.isNetworkTypeWifi(networkType)) {
- mWifiIfaces.add(iface);
+ mWifiIfaces = includeInStringArray(mWifiIfaces, iface);
+ if (DEBUG) Slog.d(TAG, "Note wifi iface " + iface + ": " + mWifiIfaces);
} else {
- mWifiIfaces.remove(iface);
+ mWifiIfaces = excludeFromStringArray(mWifiIfaces, iface);
+ if (DEBUG) Slog.d(TAG, "Note non-wifi iface " + iface + ": " + mWifiIfaces);
}
}
@@ -2516,37 +3291,45 @@ public final class BatteryStatsImpl extends BatteryStats {
// During device boot, qtaguid isn't enabled until after the inital
// loading of battery stats. Now that they're enabled, take our initial
// snapshot for future delta calculation.
- updateNetworkActivityLocked();
+ updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime());
}
- @Override public long getScreenOnTime(long batteryRealtime, int which) {
- return mScreenOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ @Override public long getScreenOnTime(long elapsedRealtimeUs, int which) {
+ return mScreenOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+ }
+
+ @Override public int getScreenOnCount(int which) {
+ return mScreenOnTimer.getCountLocked(which);
}
@Override public long getScreenBrightnessTime(int brightnessBin,
- long batteryRealtime, int which) {
+ long elapsedRealtimeUs, int which) {
return mScreenBrightnessTimer[brightnessBin].getTotalTimeLocked(
- batteryRealtime, which);
+ elapsedRealtimeUs, which);
}
@Override public int getInputEventCount(int which) {
return mInputEventCounter.getCountLocked(which);
}
- @Override public long getPhoneOnTime(long batteryRealtime, int which) {
- return mPhoneOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ @Override public long getPhoneOnTime(long elapsedRealtimeUs, int which) {
+ return mPhoneOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+ }
+
+ @Override public int getPhoneOnCount(int which) {
+ return mPhoneOnTimer.getCountLocked(which);
}
@Override public long getPhoneSignalStrengthTime(int strengthBin,
- long batteryRealtime, int which) {
+ long elapsedRealtimeUs, int which) {
return mPhoneSignalStrengthsTimer[strengthBin].getTotalTimeLocked(
- batteryRealtime, which);
+ elapsedRealtimeUs, which);
}
@Override public long getPhoneSignalScanningTime(
- long batteryRealtime, int which) {
+ long elapsedRealtimeUs, int which) {
return mPhoneSignalScanningTimer.getTotalTimeLocked(
- batteryRealtime, which);
+ elapsedRealtimeUs, which);
}
@Override public int getPhoneSignalStrengthCount(int strengthBin, int which) {
@@ -2554,36 +3337,85 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override public long getPhoneDataConnectionTime(int dataType,
- long batteryRealtime, int which) {
+ long elapsedRealtimeUs, int which) {
return mPhoneDataConnectionsTimer[dataType].getTotalTimeLocked(
- batteryRealtime, which);
+ elapsedRealtimeUs, which);
}
@Override public int getPhoneDataConnectionCount(int dataType, int which) {
return mPhoneDataConnectionsTimer[dataType].getCountLocked(which);
}
- @Override public long getWifiOnTime(long batteryRealtime, int which) {
- return mWifiOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ @Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) {
+ return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+ }
+
+ @Override public int getMobileRadioActiveCount(int which) {
+ return mMobileRadioActiveTimer.getCountLocked(which);
+ }
+
+ @Override public long getMobileRadioActiveUnknownTime(int which) {
+ return mMobileRadioActiveUnknownTime.getCountLocked(which);
+ }
+
+ @Override public int getMobileRadioActiveUnknownCount(int which) {
+ return (int)mMobileRadioActiveUnknownCount.getCountLocked(which);
+ }
+
+ @Override public long getWifiOnTime(long elapsedRealtimeUs, int which) {
+ return mWifiOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+ }
+
+ @Override public long getGlobalWifiRunningTime(long elapsedRealtimeUs, int which) {
+ return mGlobalWifiRunningTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
- @Override public long getGlobalWifiRunningTime(long batteryRealtime, int which) {
- return mGlobalWifiRunningTimer.getTotalTimeLocked(batteryRealtime, which);
+ @Override public long getWifiStateTime(int wifiState,
+ long elapsedRealtimeUs, int which) {
+ return mWifiStateTimer[wifiState].getTotalTimeLocked(
+ elapsedRealtimeUs, which);
}
- @Override public long getBluetoothOnTime(long batteryRealtime, int which) {
- return mBluetoothOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ @Override public int getWifiStateCount(int wifiState, int which) {
+ return mWifiStateTimer[wifiState].getCountLocked(which);
+ }
+
+ @Override public long getBluetoothOnTime(long elapsedRealtimeUs, int which) {
+ return mBluetoothOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+ }
+
+ @Override public long getBluetoothStateTime(int bluetoothState,
+ long elapsedRealtimeUs, int which) {
+ return mBluetoothStateTimer[bluetoothState].getTotalTimeLocked(
+ elapsedRealtimeUs, which);
+ }
+
+ @Override public int getBluetoothStateCount(int bluetoothState, int which) {
+ return mBluetoothStateTimer[bluetoothState].getCountLocked(which);
+ }
+
+ @Override
+ public long getNetworkActivityBytes(int type, int which) {
+ if (type >= 0 && type < mNetworkByteActivityCounters.length) {
+ return mNetworkByteActivityCounters[type].getCountLocked(which);
+ } else {
+ return 0;
+ }
}
@Override
- public long getNetworkActivityCount(int type, int which) {
- if (type >= 0 && type < mNetworkActivityCounters.length) {
- return mNetworkActivityCounters[type].getCountLocked(which);
+ public long getNetworkActivityPackets(int type, int which) {
+ if (type >= 0 && type < mNetworkPacketActivityCounters.length) {
+ return mNetworkPacketActivityCounters[type].getCountLocked(which);
} else {
return 0;
}
}
+ @Override public long getStartClockTime() {
+ return mStartClockTime;
+ }
+
@Override public boolean getIsOnBattery() {
return mOnBattery;
}
@@ -2627,7 +3459,10 @@ public final class BatteryStatsImpl extends BatteryStats {
Counter[] mUserActivityCounters;
- LongSamplingCounter[] mNetworkActivityCounters;
+ LongSamplingCounter[] mNetworkByteActivityCounters;
+ LongSamplingCounter[] mNetworkPacketActivityCounters;
+ LongSamplingCounter mMobileRadioActiveTime;
+ LongSamplingCounter mMobileRadioActiveCount;
/**
* The statistics we have collected for this uid's wake locks.
@@ -2657,14 +3492,14 @@ public final class BatteryStatsImpl extends BatteryStats {
public Uid(int uid) {
mUid = uid;
mWifiRunningTimer = new StopwatchTimer(Uid.this, WIFI_RUNNING,
- mWifiRunningTimers, mUnpluggables);
+ mWifiRunningTimers, mOnBatteryTimeBase);
mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
- mFullWifiLockTimers, mUnpluggables);
+ mFullWifiLockTimers, mOnBatteryTimeBase);
mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN,
- mWifiScanTimers, mUnpluggables);
+ mWifiScanTimers, mOnBatteryTimeBase);
mWifiBatchedScanTimer = new StopwatchTimer[NUM_WIFI_BATCHED_SCAN_BINS];
mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
- mWifiMulticastTimers, mUnpluggables);
+ mWifiMulticastTimers, mOnBatteryTimeBase);
}
@Override
@@ -2693,67 +3528,67 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
- public void noteWifiRunningLocked() {
+ public void noteWifiRunningLocked(long elapsedRealtimeMs) {
if (!mWifiRunning) {
mWifiRunning = true;
if (mWifiRunningTimer == null) {
mWifiRunningTimer = new StopwatchTimer(Uid.this, WIFI_RUNNING,
- mWifiRunningTimers, mUnpluggables);
+ mWifiRunningTimers, mOnBatteryTimeBase);
}
- mWifiRunningTimer.startRunningLocked(BatteryStatsImpl.this);
+ mWifiRunningTimer.startRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteWifiStoppedLocked() {
+ public void noteWifiStoppedLocked(long elapsedRealtimeMs) {
if (mWifiRunning) {
mWifiRunning = false;
- mWifiRunningTimer.stopRunningLocked(BatteryStatsImpl.this);
+ mWifiRunningTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteFullWifiLockAcquiredLocked() {
+ public void noteFullWifiLockAcquiredLocked(long elapsedRealtimeMs) {
if (!mFullWifiLockOut) {
mFullWifiLockOut = true;
if (mFullWifiLockTimer == null) {
mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
- mFullWifiLockTimers, mUnpluggables);
+ mFullWifiLockTimers, mOnBatteryTimeBase);
}
- mFullWifiLockTimer.startRunningLocked(BatteryStatsImpl.this);
+ mFullWifiLockTimer.startRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteFullWifiLockReleasedLocked() {
+ public void noteFullWifiLockReleasedLocked(long elapsedRealtimeMs) {
if (mFullWifiLockOut) {
mFullWifiLockOut = false;
- mFullWifiLockTimer.stopRunningLocked(BatteryStatsImpl.this);
+ mFullWifiLockTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteWifiScanStartedLocked() {
+ public void noteWifiScanStartedLocked(long elapsedRealtimeMs) {
if (!mWifiScanStarted) {
mWifiScanStarted = true;
if (mWifiScanTimer == null) {
mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN,
- mWifiScanTimers, mUnpluggables);
+ mWifiScanTimers, mOnBatteryTimeBase);
}
- mWifiScanTimer.startRunningLocked(BatteryStatsImpl.this);
+ mWifiScanTimer.startRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteWifiScanStoppedLocked() {
+ public void noteWifiScanStoppedLocked(long elapsedRealtimeMs) {
if (mWifiScanStarted) {
mWifiScanStarted = false;
- mWifiScanTimer.stopRunningLocked(BatteryStatsImpl.this);
+ mWifiScanTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteWifiBatchedScanStartedLocked(int csph) {
+ public void noteWifiBatchedScanStartedLocked(int csph, long elapsedRealtimeMs) {
int bin = 0;
while (csph > 8 && bin < NUM_WIFI_BATCHED_SCAN_BINS) {
csph = csph >> 3;
@@ -2764,66 +3599,66 @@ public final class BatteryStatsImpl extends BatteryStats {
if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) {
mWifiBatchedScanTimer[mWifiBatchedScanBinStarted].
- stopRunningLocked(BatteryStatsImpl.this);
+ stopRunningLocked(elapsedRealtimeMs);
}
mWifiBatchedScanBinStarted = bin;
if (mWifiBatchedScanTimer[bin] == null) {
makeWifiBatchedScanBin(bin, null);
}
- mWifiBatchedScanTimer[bin].startRunningLocked(BatteryStatsImpl.this);
+ mWifiBatchedScanTimer[bin].startRunningLocked(elapsedRealtimeMs);
}
@Override
- public void noteWifiBatchedScanStoppedLocked() {
+ public void noteWifiBatchedScanStoppedLocked(long elapsedRealtimeMs) {
if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) {
mWifiBatchedScanTimer[mWifiBatchedScanBinStarted].
- stopRunningLocked(BatteryStatsImpl.this);
+ stopRunningLocked(elapsedRealtimeMs);
mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED;
}
}
@Override
- public void noteWifiMulticastEnabledLocked() {
+ public void noteWifiMulticastEnabledLocked(long elapsedRealtimeMs) {
if (!mWifiMulticastEnabled) {
mWifiMulticastEnabled = true;
if (mWifiMulticastTimer == null) {
mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
- mWifiMulticastTimers, mUnpluggables);
+ mWifiMulticastTimers, mOnBatteryTimeBase);
}
- mWifiMulticastTimer.startRunningLocked(BatteryStatsImpl.this);
+ mWifiMulticastTimer.startRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteWifiMulticastDisabledLocked() {
+ public void noteWifiMulticastDisabledLocked(long elapsedRealtimeMs) {
if (mWifiMulticastEnabled) {
mWifiMulticastEnabled = false;
- mWifiMulticastTimer.stopRunningLocked(BatteryStatsImpl.this);
+ mWifiMulticastTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
public StopwatchTimer createAudioTurnedOnTimerLocked() {
if (mAudioTurnedOnTimer == null) {
mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
- null, mUnpluggables);
+ null, mOnBatteryTimeBase);
}
return mAudioTurnedOnTimer;
}
@Override
- public void noteAudioTurnedOnLocked() {
+ public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) {
if (!mAudioTurnedOn) {
mAudioTurnedOn = true;
- createAudioTurnedOnTimerLocked().startRunningLocked(BatteryStatsImpl.this);
+ createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteAudioTurnedOffLocked() {
+ public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) {
if (mAudioTurnedOn) {
mAudioTurnedOn = false;
if (mAudioTurnedOnTimer != null) {
- mAudioTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+ mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
}
@@ -2831,25 +3666,25 @@ public final class BatteryStatsImpl extends BatteryStats {
public StopwatchTimer createVideoTurnedOnTimerLocked() {
if (mVideoTurnedOnTimer == null) {
mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
- null, mUnpluggables);
+ null, mOnBatteryTimeBase);
}
return mVideoTurnedOnTimer;
}
@Override
- public void noteVideoTurnedOnLocked() {
+ public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) {
if (!mVideoTurnedOn) {
mVideoTurnedOn = true;
- createVideoTurnedOnTimerLocked().startRunningLocked(BatteryStatsImpl.this);
+ createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
}
}
@Override
- public void noteVideoTurnedOffLocked() {
+ public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) {
if (mVideoTurnedOn) {
mVideoTurnedOn = false;
if (mVideoTurnedOnTimer != null) {
- mVideoTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+ mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
}
@@ -2857,28 +3692,27 @@ public final class BatteryStatsImpl extends BatteryStats {
public StopwatchTimer createForegroundActivityTimerLocked() {
if (mForegroundActivityTimer == null) {
mForegroundActivityTimer = new StopwatchTimer(
- Uid.this, FOREGROUND_ACTIVITY, null, mUnpluggables);
+ Uid.this, FOREGROUND_ACTIVITY, null, mOnBatteryTimeBase);
}
return mForegroundActivityTimer;
}
@Override
- public void noteActivityResumedLocked() {
+ public void noteActivityResumedLocked(long elapsedRealtimeMs) {
// We always start, since we want multiple foreground PIDs to nest
- createForegroundActivityTimerLocked().startRunningLocked(BatteryStatsImpl.this);
+ createForegroundActivityTimerLocked().startRunningLocked(elapsedRealtimeMs);
}
@Override
- public void noteActivityPausedLocked() {
+ public void noteActivityPausedLocked(long elapsedRealtimeMs) {
if (mForegroundActivityTimer != null) {
- mForegroundActivityTimer.stopRunningLocked(BatteryStatsImpl.this);
+ mForegroundActivityTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
public BatchTimer createVibratorOnTimerLocked() {
if (mVibratorOnTimer == null) {
- mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON,
- mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal);
+ mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON, mOnBatteryTimeBase);
}
return mVibratorOnTimer;
}
@@ -2894,61 +3728,60 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
- public long getWifiRunningTime(long batteryRealtime, int which) {
+ public long getWifiRunningTime(long elapsedRealtimeUs, int which) {
if (mWifiRunningTimer == null) {
return 0;
}
- return mWifiRunningTimer.getTotalTimeLocked(batteryRealtime, which);
+ return mWifiRunningTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
- public long getFullWifiLockTime(long batteryRealtime, int which) {
+ public long getFullWifiLockTime(long elapsedRealtimeUs, int which) {
if (mFullWifiLockTimer == null) {
return 0;
}
- return mFullWifiLockTimer.getTotalTimeLocked(batteryRealtime, which);
+ return mFullWifiLockTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
- public long getWifiScanTime(long batteryRealtime, int which) {
+ public long getWifiScanTime(long elapsedRealtimeUs, int which) {
if (mWifiScanTimer == null) {
return 0;
}
- return mWifiScanTimer.getTotalTimeLocked(batteryRealtime, which);
+ return mWifiScanTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
- public long getWifiBatchedScanTime(int csphBin, long batteryRealtime, int which) {
+ public long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which) {
if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0;
if (mWifiBatchedScanTimer[csphBin] == null) {
return 0;
}
- return mWifiBatchedScanTimer[csphBin].getTotalTimeLocked(batteryRealtime, which);
+ return mWifiBatchedScanTimer[csphBin].getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
- public long getWifiMulticastTime(long batteryRealtime, int which) {
+ public long getWifiMulticastTime(long elapsedRealtimeUs, int which) {
if (mWifiMulticastTimer == null) {
return 0;
}
- return mWifiMulticastTimer.getTotalTimeLocked(batteryRealtime,
- which);
+ return mWifiMulticastTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
- public long getAudioTurnedOnTime(long batteryRealtime, int which) {
+ public long getAudioTurnedOnTime(long elapsedRealtimeUs, int which) {
if (mAudioTurnedOnTimer == null) {
return 0;
}
- return mAudioTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ return mAudioTurnedOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
- public long getVideoTurnedOnTime(long batteryRealtime, int which) {
+ public long getVideoTurnedOnTime(long elapsedRealtimeUs, int which) {
if (mVideoTurnedOnTimer == null) {
return 0;
}
- return mVideoTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ return mVideoTurnedOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
}
@Override
@@ -2997,10 +3830,10 @@ public final class BatteryStatsImpl extends BatteryStats {
}
if (in == null) {
mWifiBatchedScanTimer[i] = new StopwatchTimer(this, WIFI_BATCHED_SCAN, collected,
- mUnpluggables);
+ mOnBatteryTimeBase);
} else {
mWifiBatchedScanTimer[i] = new StopwatchTimer(this, WIFI_BATCHED_SCAN, collected,
- mUnpluggables, in);
+ mOnBatteryTimeBase, in);
}
}
@@ -3008,42 +3841,77 @@ public final class BatteryStatsImpl extends BatteryStats {
void initUserActivityLocked() {
mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES];
for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
- mUserActivityCounters[i] = new Counter(mUnpluggables);
+ mUserActivityCounters[i] = new Counter(mOnBatteryTimeBase);
}
}
- void noteNetworkActivityLocked(int type, long delta) {
- if (mNetworkActivityCounters == null) {
+ void noteNetworkActivityLocked(int type, long deltaBytes, long deltaPackets) {
+ if (mNetworkByteActivityCounters == null) {
initNetworkActivityLocked();
}
if (type >= 0 && type < NUM_NETWORK_ACTIVITY_TYPES) {
- mNetworkActivityCounters[type].addCountLocked(delta);
+ mNetworkByteActivityCounters[type].addCountLocked(deltaBytes);
+ mNetworkPacketActivityCounters[type].addCountLocked(deltaPackets);
} else {
Slog.w(TAG, "Unknown network activity type " + type + " was specified.",
new Throwable());
}
}
+ void noteMobileRadioActiveTimeLocked(long batteryUptime) {
+ if (mNetworkByteActivityCounters == null) {
+ initNetworkActivityLocked();
+ }
+ mMobileRadioActiveTime.addCountLocked(batteryUptime);
+ mMobileRadioActiveCount.addCountLocked(1);
+ }
+
@Override
public boolean hasNetworkActivity() {
- return mNetworkActivityCounters != null;
+ return mNetworkByteActivityCounters != null;
+ }
+
+ @Override
+ public long getNetworkActivityBytes(int type, int which) {
+ if (mNetworkByteActivityCounters != null && type >= 0
+ && type < mNetworkByteActivityCounters.length) {
+ return mNetworkByteActivityCounters[type].getCountLocked(which);
+ } else {
+ return 0;
+ }
}
@Override
- public long getNetworkActivityCount(int type, int which) {
- if (mNetworkActivityCounters != null && type >= 0
- && type < mNetworkActivityCounters.length) {
- return mNetworkActivityCounters[type].getCountLocked(which);
+ public long getNetworkActivityPackets(int type, int which) {
+ if (mNetworkPacketActivityCounters != null && type >= 0
+ && type < mNetworkPacketActivityCounters.length) {
+ return mNetworkPacketActivityCounters[type].getCountLocked(which);
} else {
return 0;
}
}
+ @Override
+ public long getMobileRadioActiveTime(int which) {
+ return mMobileRadioActiveTime != null
+ ? mMobileRadioActiveTime.getCountLocked(which) : 0;
+ }
+
+ @Override
+ public int getMobileRadioActiveCount(int which) {
+ return mMobileRadioActiveCount != null
+ ? (int)mMobileRadioActiveCount.getCountLocked(which) : 0;
+ }
+
void initNetworkActivityLocked() {
- mNetworkActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+ mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+ mNetworkPacketActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables);
+ mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+ mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
}
+ mMobileRadioActiveTime = new LongSamplingCounter(mOnBatteryTimeBase);
+ mMobileRadioActiveCount = new LongSamplingCounter(mOnBatteryTimeBase);
}
/**
@@ -3054,42 +3922,42 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean active = false;
if (mWifiRunningTimer != null) {
- active |= !mWifiRunningTimer.reset(BatteryStatsImpl.this, false);
+ active |= !mWifiRunningTimer.reset(false);
active |= mWifiRunning;
}
if (mFullWifiLockTimer != null) {
- active |= !mFullWifiLockTimer.reset(BatteryStatsImpl.this, false);
+ active |= !mFullWifiLockTimer.reset(false);
active |= mFullWifiLockOut;
}
if (mWifiScanTimer != null) {
- active |= !mWifiScanTimer.reset(BatteryStatsImpl.this, false);
+ active |= !mWifiScanTimer.reset(false);
active |= mWifiScanStarted;
}
if (mWifiBatchedScanTimer != null) {
for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) {
if (mWifiBatchedScanTimer[i] != null) {
- active |= !mWifiBatchedScanTimer[i].reset(BatteryStatsImpl.this, false);
+ active |= !mWifiBatchedScanTimer[i].reset(false);
}
}
active |= (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED);
}
if (mWifiMulticastTimer != null) {
- active |= !mWifiMulticastTimer.reset(BatteryStatsImpl.this, false);
+ active |= !mWifiMulticastTimer.reset(false);
active |= mWifiMulticastEnabled;
}
if (mAudioTurnedOnTimer != null) {
- active |= !mAudioTurnedOnTimer.reset(BatteryStatsImpl.this, false);
+ active |= !mAudioTurnedOnTimer.reset(false);
active |= mAudioTurnedOn;
}
if (mVideoTurnedOnTimer != null) {
- active |= !mVideoTurnedOnTimer.reset(BatteryStatsImpl.this, false);
+ active |= !mVideoTurnedOnTimer.reset(false);
active |= mVideoTurnedOn;
}
if (mForegroundActivityTimer != null) {
- active |= !mForegroundActivityTimer.reset(BatteryStatsImpl.this, false);
+ active |= !mForegroundActivityTimer.reset(false);
}
if (mVibratorOnTimer != null) {
- if (mVibratorOnTimer.reset(BatteryStatsImpl.this, false)) {
+ if (mVibratorOnTimer.reset(false)) {
mVibratorOnTimer.detach();
mVibratorOnTimer = null;
} else {
@@ -3103,10 +3971,13 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- if (mNetworkActivityCounters != null) {
+ if (mNetworkByteActivityCounters != null) {
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i].reset(false);
+ mNetworkByteActivityCounters[i].reset(false);
+ mNetworkPacketActivityCounters[i].reset(false);
}
+ mMobileRadioActiveTime.reset(false);
+ mMobileRadioActiveCount.reset(false);
}
if (mWakelockStats.size() > 0) {
@@ -3142,10 +4013,12 @@ public final class BatteryStatsImpl extends BatteryStats {
mProcessStats.clear();
}
if (mPids.size() > 0) {
- for (int i=0; !active && i<mPids.size(); i++) {
+ for (int i=mPids.size()-1; i>=0; i--) {
Pid pid = mPids.valueAt(i);
- if (pid.mWakeStart != 0) {
+ if (pid.mWakeNesting > 0) {
active = true;
+ } else {
+ mPids.removeAt(i);
}
}
}
@@ -3167,8 +4040,6 @@ public final class BatteryStatsImpl extends BatteryStats {
mPackageStats.clear();
}
- mPids.clear();
-
if (!active) {
if (mWifiRunningTimer != null) {
mWifiRunningTimer.detach();
@@ -3204,29 +4075,31 @@ public final class BatteryStatsImpl extends BatteryStats {
mUserActivityCounters[i].detach();
}
}
- if (mNetworkActivityCounters != null) {
+ if (mNetworkByteActivityCounters != null) {
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i].detach();
+ mNetworkByteActivityCounters[i].detach();
+ mNetworkPacketActivityCounters[i].detach();
}
}
+ mPids.clear();
}
return !active;
}
- void writeToParcelLocked(Parcel out, long batteryRealtime) {
+ void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
out.writeInt(mWakelockStats.size());
for (Map.Entry<String, Uid.Wakelock> wakelockEntry : mWakelockStats.entrySet()) {
out.writeString(wakelockEntry.getKey());
Uid.Wakelock wakelock = wakelockEntry.getValue();
- wakelock.writeToParcelLocked(out, batteryRealtime);
+ wakelock.writeToParcelLocked(out, elapsedRealtimeUs);
}
out.writeInt(mSensorStats.size());
for (Map.Entry<Integer, Uid.Sensor> sensorEntry : mSensorStats.entrySet()) {
out.writeInt(sensorEntry.getKey());
Uid.Sensor sensor = sensorEntry.getValue();
- sensor.writeToParcelLocked(out, batteryRealtime);
+ sensor.writeToParcelLocked(out, elapsedRealtimeUs);
}
out.writeInt(mProcessStats.size());
@@ -3245,57 +4118,57 @@ public final class BatteryStatsImpl extends BatteryStats {
if (mWifiRunningTimer != null) {
out.writeInt(1);
- mWifiRunningTimer.writeToParcel(out, batteryRealtime);
+ mWifiRunningTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
if (mFullWifiLockTimer != null) {
out.writeInt(1);
- mFullWifiLockTimer.writeToParcel(out, batteryRealtime);
+ mFullWifiLockTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
if (mWifiScanTimer != null) {
out.writeInt(1);
- mWifiScanTimer.writeToParcel(out, batteryRealtime);
+ mWifiScanTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) {
if (mWifiBatchedScanTimer[i] != null) {
out.writeInt(1);
- mWifiBatchedScanTimer[i].writeToParcel(out, batteryRealtime);
+ mWifiBatchedScanTimer[i].writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
}
if (mWifiMulticastTimer != null) {
out.writeInt(1);
- mWifiMulticastTimer.writeToParcel(out, batteryRealtime);
+ mWifiMulticastTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
if (mAudioTurnedOnTimer != null) {
out.writeInt(1);
- mAudioTurnedOnTimer.writeToParcel(out, batteryRealtime);
+ mAudioTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
if (mVideoTurnedOnTimer != null) {
out.writeInt(1);
- mVideoTurnedOnTimer.writeToParcel(out, batteryRealtime);
+ mVideoTurnedOnTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
if (mForegroundActivityTimer != null) {
out.writeInt(1);
- mForegroundActivityTimer.writeToParcel(out, batteryRealtime);
+ mForegroundActivityTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
if (mVibratorOnTimer != null) {
out.writeInt(1);
- mVibratorOnTimer.writeToParcel(out, batteryRealtime);
+ mVibratorOnTimer.writeToParcel(out, elapsedRealtimeUs);
} else {
out.writeInt(0);
}
@@ -3307,23 +4180,26 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
- if (mNetworkActivityCounters != null) {
+ if (mNetworkByteActivityCounters != null) {
out.writeInt(1);
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i].writeToParcel(out);
+ mNetworkByteActivityCounters[i].writeToParcel(out);
+ mNetworkPacketActivityCounters[i].writeToParcel(out);
}
+ mMobileRadioActiveTime.writeToParcel(out);
+ mMobileRadioActiveCount.writeToParcel(out);
} else {
out.writeInt(0);
}
}
- void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
+ void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
int numWakelocks = in.readInt();
mWakelockStats.clear();
for (int j = 0; j < numWakelocks; j++) {
String wakelockName = in.readString();
Uid.Wakelock wakelock = new Wakelock();
- wakelock.readFromParcelLocked(unpluggables, in);
+ wakelock.readFromParcelLocked(timeBase, screenOffTimeBase, in);
// We will just drop some random set of wakelocks if
// the previous run of the system was an older version
// that didn't impose a limit.
@@ -3335,7 +4211,7 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int k = 0; k < numSensors; k++) {
int sensorNumber = in.readInt();
Uid.Sensor sensor = new Sensor(sensorNumber);
- sensor.readFromParcelLocked(mUnpluggables, in);
+ sensor.readFromParcelLocked(mOnBatteryTimeBase, in);
mSensorStats.put(sensorNumber, sensor);
}
@@ -3360,21 +4236,21 @@ public final class BatteryStatsImpl extends BatteryStats {
mWifiRunning = false;
if (in.readInt() != 0) {
mWifiRunningTimer = new StopwatchTimer(Uid.this, WIFI_RUNNING,
- mWifiRunningTimers, mUnpluggables, in);
+ mWifiRunningTimers, mOnBatteryTimeBase, in);
} else {
mWifiRunningTimer = null;
}
mFullWifiLockOut = false;
if (in.readInt() != 0) {
mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
- mFullWifiLockTimers, mUnpluggables, in);
+ mFullWifiLockTimers, mOnBatteryTimeBase, in);
} else {
mFullWifiLockTimer = null;
}
mWifiScanStarted = false;
if (in.readInt() != 0) {
mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN,
- mWifiScanTimers, mUnpluggables, in);
+ mWifiScanTimers, mOnBatteryTimeBase, in);
} else {
mWifiScanTimer = null;
}
@@ -3389,51 +4265,58 @@ public final class BatteryStatsImpl extends BatteryStats {
mWifiMulticastEnabled = false;
if (in.readInt() != 0) {
mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
- mWifiMulticastTimers, mUnpluggables, in);
+ mWifiMulticastTimers, mOnBatteryTimeBase, in);
} else {
mWifiMulticastTimer = null;
}
mAudioTurnedOn = false;
if (in.readInt() != 0) {
mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
- null, mUnpluggables, in);
+ null, mOnBatteryTimeBase, in);
} else {
mAudioTurnedOnTimer = null;
}
mVideoTurnedOn = false;
if (in.readInt() != 0) {
mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
- null, mUnpluggables, in);
+ null, mOnBatteryTimeBase, in);
} else {
mVideoTurnedOnTimer = null;
}
if (in.readInt() != 0) {
mForegroundActivityTimer = new StopwatchTimer(
- Uid.this, FOREGROUND_ACTIVITY, null, mUnpluggables, in);
+ Uid.this, FOREGROUND_ACTIVITY, null, mOnBatteryTimeBase, in);
} else {
mForegroundActivityTimer = null;
}
if (in.readInt() != 0) {
- mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON,
- mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal, in);
+ mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON, mOnBatteryTimeBase, in);
} else {
mVibratorOnTimer = null;
}
if (in.readInt() != 0) {
mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES];
for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
- mUserActivityCounters[i] = new Counter(mUnpluggables, in);
+ mUserActivityCounters[i] = new Counter(mOnBatteryTimeBase, in);
}
} else {
mUserActivityCounters = null;
}
if (in.readInt() != 0) {
- mNetworkActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+ mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
+ mNetworkPacketActivityCounters
+ = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables, in);
+ mNetworkByteActivityCounters[i]
+ = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mNetworkPacketActivityCounters[i]
+ = new LongSamplingCounter(mOnBatteryTimeBase, in);
}
+ mMobileRadioActiveTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mMobileRadioActiveCount = new LongSamplingCounter(mOnBatteryTimeBase, in);
} else {
- mNetworkActivityCounters = null;
+ mNetworkByteActivityCounters = null;
+ mNetworkPacketActivityCounters = null;
}
}
@@ -3464,24 +4347,24 @@ public final class BatteryStatsImpl extends BatteryStats {
* return a new Timer, or null.
*/
private StopwatchTimer readTimerFromParcel(int type, ArrayList<StopwatchTimer> pool,
- ArrayList<Unpluggable> unpluggables, Parcel in) {
+ TimeBase timeBase, Parcel in) {
if (in.readInt() == 0) {
return null;
}
- return new StopwatchTimer(Uid.this, type, pool, unpluggables, in);
+ return new StopwatchTimer(Uid.this, type, pool, timeBase, in);
}
boolean reset() {
boolean wlactive = false;
if (mTimerFull != null) {
- wlactive |= !mTimerFull.reset(BatteryStatsImpl.this, false);
+ wlactive |= !mTimerFull.reset(false);
}
if (mTimerPartial != null) {
- wlactive |= !mTimerPartial.reset(BatteryStatsImpl.this, false);
+ wlactive |= !mTimerPartial.reset(false);
}
if (mTimerWindow != null) {
- wlactive |= !mTimerWindow.reset(BatteryStatsImpl.this, false);
+ wlactive |= !mTimerWindow.reset(false);
}
if (!wlactive) {
if (mTimerFull != null) {
@@ -3500,19 +4383,19 @@ public final class BatteryStatsImpl extends BatteryStats {
return !wlactive;
}
- void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
+ void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
mTimerPartial = readTimerFromParcel(WAKE_TYPE_PARTIAL,
- mPartialTimers, unpluggables, in);
+ mPartialTimers, screenOffTimeBase, in);
mTimerFull = readTimerFromParcel(WAKE_TYPE_FULL,
- mFullTimers, unpluggables, in);
+ mFullTimers, timeBase, in);
mTimerWindow = readTimerFromParcel(WAKE_TYPE_WINDOW,
- mWindowTimers, unpluggables, in);
+ mWindowTimers, timeBase, in);
}
- void writeToParcelLocked(Parcel out, long batteryRealtime) {
- Timer.writeTimerToParcel(out, mTimerPartial, batteryRealtime);
- Timer.writeTimerToParcel(out, mTimerFull, batteryRealtime);
- Timer.writeTimerToParcel(out, mTimerWindow, batteryRealtime);
+ void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
+ Timer.writeTimerToParcel(out, mTimerPartial, elapsedRealtimeUs);
+ Timer.writeTimerToParcel(out, mTimerFull, elapsedRealtimeUs);
+ Timer.writeTimerToParcel(out, mTimerWindow, elapsedRealtimeUs);
}
@Override
@@ -3534,8 +4417,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mHandle = handle;
}
- private StopwatchTimer readTimerFromParcel(ArrayList<Unpluggable> unpluggables,
- Parcel in) {
+ private StopwatchTimer readTimerFromParcel(TimeBase timeBase, Parcel in) {
if (in.readInt() == 0) {
return null;
}
@@ -3545,23 +4427,23 @@ public final class BatteryStatsImpl extends BatteryStats {
pool = new ArrayList<StopwatchTimer>();
mSensorTimers.put(mHandle, pool);
}
- return new StopwatchTimer(Uid.this, 0, pool, unpluggables, in);
+ return new StopwatchTimer(Uid.this, 0, pool, timeBase, in);
}
boolean reset() {
- if (mTimer.reset(BatteryStatsImpl.this, true)) {
+ if (mTimer.reset(true)) {
mTimer = null;
return true;
}
return false;
}
- void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
- mTimer = readTimerFromParcel(unpluggables, in);
+ void readFromParcelLocked(TimeBase timeBase, Parcel in) {
+ mTimer = readTimerFromParcel(timeBase, in);
}
- void writeToParcelLocked(Parcel out, long batteryRealtime) {
- Timer.writeTimerToParcel(out, mTimer, batteryRealtime);
+ void writeToParcelLocked(Parcel out, long elapsedRealtimeUs) {
+ Timer.writeTimerToParcel(out, mTimer, elapsedRealtimeUs);
}
@Override
@@ -3578,7 +4460,12 @@ public final class BatteryStatsImpl extends BatteryStats {
/**
* The statistics associated with a particular process.
*/
- public final class Proc extends BatteryStats.Uid.Proc implements Unpluggable {
+ public final class Proc extends BatteryStats.Uid.Proc implements TimeBaseObs {
+ /**
+ * Remains true until removed from the stats.
+ */
+ boolean mActive = true;
+
/**
* Total time (in 1/100 sec) spent executing in user code.
*/
@@ -3664,26 +4551,27 @@ public final class BatteryStatsImpl extends BatteryStats {
ArrayList<ExcessivePower> mExcessivePower;
Proc() {
- mUnpluggables.add(this);
+ mOnBatteryTimeBase.add(this);
mSpeedBins = new SamplingCounter[getCpuSpeedSteps()];
}
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
mUnpluggedUserTime = mUserTime;
mUnpluggedSystemTime = mSystemTime;
mUnpluggedForegroundTime = mForegroundTime;
mUnpluggedStarts = mStarts;
}
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
}
void detach() {
- mUnpluggables.remove(this);
+ mActive = false;
+ mOnBatteryTimeBase.remove(this);
for (int i = 0; i < mSpeedBins.length; i++) {
SamplingCounter c = mSpeedBins[i];
if (c != null) {
- mUnpluggables.remove(c);
+ mOnBatteryTimeBase.remove(c);
mSpeedBins[i] = null;
}
}
@@ -3812,7 +4700,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mSpeedBins = new SamplingCounter[bins >= steps ? bins : steps];
for (int i = 0; i < bins; i++) {
if (in.readInt() != 0) {
- mSpeedBins[i] = new SamplingCounter(mUnpluggables, in);
+ mSpeedBins[i] = new SamplingCounter(mOnBatteryTimeBase, in);
}
}
@@ -3837,6 +4725,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
+ public boolean isActive() {
+ return mActive;
+ }
+
+ @Override
public long getUserTime(int which) {
long val;
if (which == STATS_LAST) {
@@ -3907,7 +4800,7 @@ public final class BatteryStatsImpl extends BatteryStats {
if (amt != 0) {
SamplingCounter c = mSpeedBins[i];
if (c == null) {
- mSpeedBins[i] = c = new SamplingCounter(mUnpluggables);
+ mSpeedBins[i] = c = new SamplingCounter(mOnBatteryTimeBase);
}
c.addCountAtomic(values[i]);
}
@@ -3928,7 +4821,7 @@ public final class BatteryStatsImpl extends BatteryStats {
/**
* The statistics associated with a particular package.
*/
- public final class Pkg extends BatteryStats.Uid.Pkg implements Unpluggable {
+ public final class Pkg extends BatteryStats.Uid.Pkg implements TimeBaseObs {
/**
* Number of times this package has done something that could wake up the
* device from sleep.
@@ -3959,18 +4852,18 @@ public final class BatteryStatsImpl extends BatteryStats {
final HashMap<String, Serv> mServiceStats = new HashMap<String, Serv>();
Pkg() {
- mUnpluggables.add(this);
+ mOnBatteryScreenOffTimeBase.add(this);
}
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStarted(long elapsedRealtime, long baseUptime, long baseRealtime) {
mUnpluggedWakeups = mWakeups;
}
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
}
void detach() {
- mUnpluggables.remove(this);
+ mOnBatteryScreenOffTimeBase.remove(this);
}
void readFromParcelLocked(Parcel in) {
@@ -4029,7 +4922,7 @@ public final class BatteryStatsImpl extends BatteryStats {
/**
* The statistics associated with a particular service.
*/
- public final class Serv extends BatteryStats.Uid.Pkg.Serv implements Unpluggable {
+ public final class Serv extends BatteryStats.Uid.Pkg.Serv implements TimeBaseObs {
/**
* Total time (ms in battery uptime) the service has been left started.
*/
@@ -4121,20 +5014,22 @@ public final class BatteryStatsImpl extends BatteryStats {
int mUnpluggedLaunches;
Serv() {
- mUnpluggables.add(this);
+ mOnBatteryTimeBase.add(this);
}
- public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
- mUnpluggedStartTime = getStartTimeToNowLocked(batteryUptime);
+ public void onTimeStarted(long elapsedRealtime, long baseUptime,
+ long baseRealtime) {
+ mUnpluggedStartTime = getStartTimeToNowLocked(baseUptime);
mUnpluggedStarts = mStarts;
mUnpluggedLaunches = mLaunches;
}
- public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ public void onTimeStopped(long elapsedRealtime, long baseUptime,
+ long baseRealtime) {
}
void detach() {
- mUnpluggables.remove(this);
+ mOnBatteryTimeBase.remove(this);
}
void readFromParcelLocked(Parcel in) {
@@ -4369,7 +5264,7 @@ public final class BatteryStatsImpl extends BatteryStats {
t = wl.mTimerPartial;
if (t == null) {
t = new StopwatchTimer(Uid.this, WAKE_TYPE_PARTIAL,
- mPartialTimers, mUnpluggables);
+ mPartialTimers, mOnBatteryScreenOffTimeBase);
wl.mTimerPartial = t;
}
return t;
@@ -4377,7 +5272,7 @@ public final class BatteryStatsImpl extends BatteryStats {
t = wl.mTimerFull;
if (t == null) {
t = new StopwatchTimer(Uid.this, WAKE_TYPE_FULL,
- mFullTimers, mUnpluggables);
+ mFullTimers, mOnBatteryTimeBase);
wl.mTimerFull = t;
}
return t;
@@ -4385,7 +5280,7 @@ public final class BatteryStatsImpl extends BatteryStats {
t = wl.mTimerWindow;
if (t == null) {
t = new StopwatchTimer(Uid.this, WAKE_TYPE_WINDOW,
- mWindowTimers, mUnpluggables);
+ mWindowTimers, mOnBatteryTimeBase);
wl.mTimerWindow = t;
}
return t;
@@ -4412,34 +5307,36 @@ public final class BatteryStatsImpl extends BatteryStats {
timers = new ArrayList<StopwatchTimer>();
mSensorTimers.put(sensor, timers);
}
- t = new StopwatchTimer(Uid.this, BatteryStats.SENSOR, timers, mUnpluggables);
+ t = new StopwatchTimer(Uid.this, BatteryStats.SENSOR, timers, mOnBatteryTimeBase);
se.mTimer = t;
return t;
}
- public void noteStartWakeLocked(int pid, String name, int type) {
+ public void noteStartWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) {
StopwatchTimer t = getWakeTimerLocked(name, type);
if (t != null) {
- t.startRunningLocked(BatteryStatsImpl.this);
+ t.startRunningLocked(elapsedRealtimeMs);
}
if (pid >= 0 && type == WAKE_TYPE_PARTIAL) {
Pid p = getPidStatsLocked(pid);
- if (p.mWakeStart == 0) {
- p.mWakeStart = SystemClock.elapsedRealtime();
+ if (p.mWakeNesting++ == 0) {
+ p.mWakeStartMs = elapsedRealtimeMs;
}
}
}
- public void noteStopWakeLocked(int pid, String name, int type) {
+ public void noteStopWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) {
StopwatchTimer t = getWakeTimerLocked(name, type);
if (t != null) {
- t.stopRunningLocked(BatteryStatsImpl.this);
+ t.stopRunningLocked(elapsedRealtimeMs);
}
if (pid >= 0 && type == WAKE_TYPE_PARTIAL) {
Pid p = mPids.get(pid);
- if (p != null && p.mWakeStart != 0) {
- p.mWakeSum += SystemClock.elapsedRealtime() - p.mWakeStart;
- p.mWakeStart = 0;
+ if (p != null && p.mWakeNesting > 0) {
+ if (p.mWakeNesting-- == 1) {
+ p.mWakeSumMs += elapsedRealtimeMs - p.mWakeStartMs;
+ p.mWakeStartMs = 0;
+ }
}
}
}
@@ -4458,32 +5355,32 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- public void noteStartSensor(int sensor) {
+ public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
StopwatchTimer t = getSensorTimerLocked(sensor, true);
if (t != null) {
- t.startRunningLocked(BatteryStatsImpl.this);
+ t.startRunningLocked(elapsedRealtimeMs);
}
}
- public void noteStopSensor(int sensor) {
+ public void noteStopSensor(int sensor, long elapsedRealtimeMs) {
// Don't create a timer if one doesn't already exist
StopwatchTimer t = getSensorTimerLocked(sensor, false);
if (t != null) {
- t.stopRunningLocked(BatteryStatsImpl.this);
+ t.stopRunningLocked(elapsedRealtimeMs);
}
}
- public void noteStartGps() {
+ public void noteStartGps(long elapsedRealtimeMs) {
StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, true);
if (t != null) {
- t.startRunningLocked(BatteryStatsImpl.this);
+ t.startRunningLocked(elapsedRealtimeMs);
}
}
- public void noteStopGps() {
+ public void noteStopGps(long elapsedRealtimeMs) {
StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, false);
if (t != null) {
- t.stopRunningLocked(BatteryStatsImpl.this);
+ t.stopRunningLocked(elapsedRealtimeMs);
}
}
@@ -4496,35 +5393,46 @@ public final class BatteryStatsImpl extends BatteryStats {
mFile = new JournaledFile(new File(filename), new File(filename + ".tmp"));
mHandler = new MyHandler(handler.getLooper());
mStartCount++;
- mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables);
+ mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mUnpluggables);
+ mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mOnBatteryTimeBase);
}
- mInputEventCounter = new Counter(mUnpluggables);
- mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables);
+ mInputEventCounter = new Counter(mOnBatteryTimeBase);
+ mPhoneOnTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
- mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null, mUnpluggables);
+ mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null,
+ mOnBatteryTimeBase);
}
- mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables);
+ mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mOnBatteryTimeBase);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, null, mUnpluggables);
+ mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, null,
+ mOnBatteryTimeBase);
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables);
- }
- mWifiOnTimer = new StopwatchTimer(null, -3, null, mUnpluggables);
- mGlobalWifiRunningTimer = new StopwatchTimer(null, -4, null, mUnpluggables);
- mBluetoothOnTimer = new StopwatchTimer(null, -5, null, mUnpluggables);
- mAudioOnTimer = new StopwatchTimer(null, -6, null, mUnpluggables);
- mVideoOnTimer = new StopwatchTimer(null, -7, null, mUnpluggables);
+ mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+ mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase);
+ }
+ mMobileRadioActiveTimer = new StopwatchTimer(null, -400, null, mOnBatteryTimeBase);
+ mMobileRadioActivePerAppTimer = new StopwatchTimer(null, -401, null, mOnBatteryTimeBase);
+ mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase);
+ mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase);
+ mWifiOnTimer = new StopwatchTimer(null, -3, null, mOnBatteryTimeBase);
+ mGlobalWifiRunningTimer = new StopwatchTimer(null, -4, null, mOnBatteryTimeBase);
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ mWifiStateTimer[i] = new StopwatchTimer(null, -600-i, null, mOnBatteryTimeBase);
+ }
+ mBluetoothOnTimer = new StopwatchTimer(null, -5, null, mOnBatteryTimeBase);
+ for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
+ mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i, null, mOnBatteryTimeBase);
+ }
+ mAudioOnTimer = new StopwatchTimer(null, -6, null, mOnBatteryTimeBase);
+ mVideoOnTimer = new StopwatchTimer(null, -7, null, mOnBatteryTimeBase);
mOnBattery = mOnBatteryInternal = false;
- initTimes();
- mTrackBatteryPastUptime = 0;
- mTrackBatteryPastRealtime = 0;
- mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000;
- mRealtimeStart = mTrackBatteryRealtimeStart = SystemClock.elapsedRealtime() * 1000;
- mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart);
- mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart);
+ long uptime = SystemClock.uptimeMillis() * 1000;
+ long realtime = SystemClock.elapsedRealtime() * 1000;
+ initTimes(uptime, realtime);
+ mUptimeStart = uptime;
+ mRealtimeStart = realtime;
mDischargeStartLevel = 0;
mDischargeUnplugLevel = 0;
mDischargeCurrentLevel = 0;
@@ -4557,18 +5465,21 @@ public final class BatteryStatsImpl extends BatteryStats {
public boolean startIteratingOldHistoryLocked() {
if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize()
+ " pos=" + mHistoryBuffer.dataPosition());
+ if ((mHistoryIterator = mHistory) == null) {
+ return false;
+ }
mHistoryBuffer.setDataPosition(0);
mHistoryReadTmp.clear();
mReadOverflow = false;
mIteratingHistory = true;
- return (mHistoryIterator = mHistory) != null;
+ return true;
}
@Override
public boolean getNextOldHistoryLocked(HistoryItem out) {
boolean end = mHistoryBuffer.dataPosition() >= mHistoryBuffer.dataSize();
if (!end) {
- mHistoryReadTmp.readDelta(mHistoryBuffer);
+ readHistoryDelta(mHistoryBuffer, mHistoryReadTmp);
mReadOverflow |= mHistoryReadTmp.cmd == HistoryItem.CMD_OVERFLOW;
}
HistoryItem cur = mHistoryIterator;
@@ -4588,9 +5499,9 @@ public final class BatteryStatsImpl extends BatteryStats {
PrintWriter pw = new FastPrintWriter(new LogWriter(android.util.Log.WARN, TAG));
pw.println("Histories differ!");
pw.println("Old history:");
- (new HistoryPrinter()).printNextItem(pw, out, now);
+ (new HistoryPrinter()).printNextItem(pw, out, now, false);
pw.println("New history:");
- (new HistoryPrinter()).printNextItem(pw, mHistoryReadTmp, now);
+ (new HistoryPrinter()).printNextItem(pw, mHistoryReadTmp, now, false);
pw.flush();
}
}
@@ -4601,16 +5512,60 @@ public final class BatteryStatsImpl extends BatteryStats {
public void finishIteratingOldHistoryLocked() {
mIteratingHistory = false;
mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+ mHistoryIterator = null;
+ }
+
+ public int getHistoryTotalSize() {
+ return MAX_HISTORY_BUFFER;
+ }
+
+ public int getHistoryUsedSize() {
+ return mHistoryBuffer.dataSize();
}
@Override
public boolean startIteratingHistoryLocked() {
if (DEBUG_HISTORY) Slog.i(TAG, "ITERATING: buff size=" + mHistoryBuffer.dataSize()
+ " pos=" + mHistoryBuffer.dataPosition());
+ if (mHistoryBuffer.dataSize() <= 0) {
+ return false;
+ }
mHistoryBuffer.setDataPosition(0);
mReadOverflow = false;
mIteratingHistory = true;
- return mHistoryBuffer.dataSize() > 0;
+ mReadHistoryStrings = new String[mHistoryTagPool.size()];
+ mReadHistoryUids = new int[mHistoryTagPool.size()];
+ mReadHistoryChars = 0;
+ for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
+ final HistoryTag tag = ent.getKey();
+ final int idx = ent.getValue();
+ mReadHistoryStrings[idx] = tag.string;
+ mReadHistoryUids[idx] = tag.uid;
+ mReadHistoryChars += tag.string.length() + 1;
+ }
+ return true;
+ }
+
+ @Override
+ public int getHistoryStringPoolSize() {
+ return mReadHistoryStrings.length;
+ }
+
+ @Override
+ public int getHistoryStringPoolBytes() {
+ // Each entry is a fixed 12 bytes: 4 for index, 4 for uid, 4 for string size
+ // Each string character is 2 bytes.
+ return (mReadHistoryStrings.length * 12) + (mReadHistoryChars * 2);
+ }
+
+ @Override
+ public String getHistoryTagPoolString(int index) {
+ return mReadHistoryStrings[index];
+ }
+
+ @Override
+ public int getHistoryTagPoolUid(int index) {
+ return mReadHistoryUids[index];
}
@Override
@@ -4624,7 +5579,7 @@ public final class BatteryStatsImpl extends BatteryStats {
return false;
}
- out.readDelta(mHistoryBuffer);
+ readHistoryDelta(mHistoryBuffer, out);
return true;
}
@@ -4632,6 +5587,7 @@ public final class BatteryStatsImpl extends BatteryStats {
public void finishIteratingHistoryLocked() {
mIteratingHistory = false;
mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+ mReadHistoryStrings = null;
}
@Override
@@ -4652,13 +5608,12 @@ public final class BatteryStatsImpl extends BatteryStats {
return mScreenOn;
}
- void initTimes() {
- mBatteryRealtime = mTrackBatteryPastUptime = 0;
- mBatteryUptime = mTrackBatteryPastRealtime = 0;
- mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000;
- mRealtimeStart = mTrackBatteryRealtimeStart = SystemClock.elapsedRealtime() * 1000;
- mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart);
- mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart);
+ void initTimes(long uptime, long realtime) {
+ mStartClockTime = System.currentTimeMillis();
+ mOnBatteryTimeBase.init(uptime, realtime);
+ mOnBatteryScreenOffTimeBase.init(uptime, realtime);
+ mUptimeStart = uptime;
+ mRealtimeStart = realtime;
}
void initDischarge() {
@@ -4669,31 +5624,67 @@ public final class BatteryStatsImpl extends BatteryStats {
mDischargeAmountScreenOff = 0;
mDischargeAmountScreenOffSinceCharge = 0;
}
-
- public void resetAllStatsLocked() {
+
+ public void resetAllStatsCmdLocked() {
+ resetAllStatsLocked();
+ long uptime = SystemClock.uptimeMillis() * 1000;
+ long mSecRealtime = SystemClock.elapsedRealtime();
+ long realtime = mSecRealtime * 1000;
+ mDischargeStartLevel = mHistoryCur.batteryLevel;
+ pullPendingStateUpdatesLocked();
+ addHistoryRecordLocked(mSecRealtime);
+ mDischargeCurrentLevel = mDischargeUnplugLevel = mHistoryCur.batteryLevel;
+ mOnBatteryTimeBase.reset(uptime, realtime);
+ mOnBatteryScreenOffTimeBase.reset(uptime, realtime);
+ if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) {
+ if (mScreenOn) {
+ mDischargeScreenOnUnplugLevel = mHistoryCur.batteryLevel;
+ mDischargeScreenOffUnplugLevel = 0;
+ } else {
+ mDischargeScreenOnUnplugLevel = 0;
+ mDischargeScreenOffUnplugLevel = mHistoryCur.batteryLevel;
+ }
+ mDischargeAmountScreenOn = 0;
+ mDischargeAmountScreenOff = 0;
+ }
+ initActiveHistoryEventsLocked(mSecRealtime);
+ }
+
+ private void resetAllStatsLocked() {
mStartCount = 0;
- initTimes();
- mScreenOnTimer.reset(this, false);
+ initTimes(SystemClock.uptimeMillis() * 1000, SystemClock.elapsedRealtime() * 1000);
+ mScreenOnTimer.reset(false);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- mScreenBrightnessTimer[i].reset(this, false);
+ mScreenBrightnessTimer[i].reset(false);
}
mInputEventCounter.reset(false);
- mPhoneOnTimer.reset(this, false);
- mAudioOnTimer.reset(this, false);
- mVideoOnTimer.reset(this, false);
+ mPhoneOnTimer.reset(false);
+ mAudioOnTimer.reset(false);
+ mVideoOnTimer.reset(false);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
- mPhoneSignalStrengthsTimer[i].reset(this, false);
+ mPhoneSignalStrengthsTimer[i].reset(false);
}
- mPhoneSignalScanningTimer.reset(this, false);
+ mPhoneSignalScanningTimer.reset(false);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- mPhoneDataConnectionsTimer[i].reset(this, false);
+ mPhoneDataConnectionsTimer[i].reset(false);
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i].reset(false);
+ mNetworkByteActivityCounters[i].reset(false);
+ mNetworkPacketActivityCounters[i].reset(false);
+ }
+ mMobileRadioActiveTimer.reset(false);
+ mMobileRadioActivePerAppTimer.reset(false);
+ mMobileRadioActiveUnknownTime.reset(false);
+ mMobileRadioActiveUnknownCount.reset(false);
+ mWifiOnTimer.reset(false);
+ mGlobalWifiRunningTimer.reset(false);
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ mWifiStateTimer[i].reset(false);
+ }
+ mBluetoothOnTimer.reset(false);
+ for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
+ mBluetoothStateTimer[i].reset(false);
}
- mWifiOnTimer.reset(this, false);
- mGlobalWifiRunningTimer.reset(this, false);
- mBluetoothOnTimer.reset(this, false);
for (int i=0; i<mUidStats.size(); i++) {
if (mUidStats.valueAt(i).reset()) {
@@ -4704,7 +5695,7 @@ public final class BatteryStatsImpl extends BatteryStats {
if (mKernelWakelockStats.size() > 0) {
for (SamplingTimer timer : mKernelWakelockStats.values()) {
- mUnpluggables.remove(timer);
+ mOnBatteryScreenOffTimeBase.remove(timer);
}
mKernelWakelockStats.clear();
}
@@ -4714,6 +5705,23 @@ public final class BatteryStatsImpl extends BatteryStats {
clearHistoryLocked();
}
+ private void initActiveHistoryEventsLocked(long nowRealtime) {
+ for (int i=0; i<HistoryItem.EVENT_COUNT; i++) {
+ HashMap<String, SparseBooleanArray> active = mActiveEvents[i];
+ if (active == null) {
+ continue;
+ }
+ for (HashMap.Entry<String, SparseBooleanArray> ent : active.entrySet()) {
+ SparseBooleanArray uids = ent.getValue();
+ for (int j=0; j<uids.size(); j++) {
+ if (uids.valueAt(j)) {
+ addHistoryEventLocked(nowRealtime, i, ent.getKey(), uids.keyAt(j));
+ }
+ }
+ }
+ }
+ }
+
void updateDischargeScreenLevelsLocked(boolean oldScreenOn, boolean newScreenOn) {
if (oldScreenOn) {
int diff = mDischargeScreenOnUnplugLevel - mDischargeCurrentLevel;
@@ -4737,9 +5745,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- void setOnBattery(boolean onBattery, int oldStatus, int level) {
- synchronized(this) {
- setOnBatteryLocked(onBattery, oldStatus, level);
+ public void pullPendingStateUpdatesLocked() {
+ updateKernelWakelocksLocked();
+ updateNetworkActivityLocked(NET_UPDATE_ALL, SystemClock.elapsedRealtime());
+ if (mOnBatteryInternal) {
+ updateDischargeScreenLevelsLocked(mScreenOn, mScreenOn);
}
}
@@ -4758,24 +5768,24 @@ public final class BatteryStatsImpl extends BatteryStats {
// battery was last full, or the level is at 100, or
// we have gone through a significant charge (from a very low
// level to a now very high level).
+ boolean reset = false;
if (oldStatus == BatteryManager.BATTERY_STATUS_FULL
|| level >= 90
|| (mDischargeCurrentLevel < 20 && level >= 80)) {
doWrite = true;
resetAllStatsLocked();
mDischargeStartLevel = level;
+ reset = true;
}
- updateKernelWakelocksLocked();
- updateNetworkActivityLocked();
+ pullPendingStateUpdatesLocked();
mHistoryCur.batteryLevel = (byte)level;
mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: "
+ Integer.toHexString(mHistoryCur.states));
+ mHistoryCur.currentTime = System.currentTimeMillis();
+ addHistoryBufferLocked(mSecRealtime, HistoryItem.CMD_CURRENT_TIME);
+ mHistoryCur.currentTime = 0;
addHistoryRecordLocked(mSecRealtime);
- mTrackBatteryUptimeStart = uptime;
- mTrackBatteryRealtimeStart = realtime;
- mUnpluggedBatteryUptime = getBatteryUptimeLocked(uptime);
- mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(realtime);
mDischargeCurrentLevel = mDischargeUnplugLevel = level;
if (mScreenOn) {
mDischargeScreenOnUnplugLevel = level;
@@ -4786,24 +5796,24 @@ public final class BatteryStatsImpl extends BatteryStats {
}
mDischargeAmountScreenOn = 0;
mDischargeAmountScreenOff = 0;
- doUnplugLocked(realtime, mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
+ updateTimeBasesLocked(true, !mScreenOn, uptime, realtime);
+ if (reset) {
+ initActiveHistoryEventsLocked(mSecRealtime);
+ }
} else {
- updateKernelWakelocksLocked();
- updateNetworkActivityLocked();
+ pullPendingStateUpdatesLocked();
mHistoryCur.batteryLevel = (byte)level;
mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: "
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(mSecRealtime);
- mTrackBatteryPastUptime += uptime - mTrackBatteryUptimeStart;
- mTrackBatteryPastRealtime += realtime - mTrackBatteryRealtimeStart;
mDischargeCurrentLevel = level;
if (level < mDischargeUnplugLevel) {
mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1;
mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level;
}
updateDischargeScreenLevelsLocked(mScreenOn, mScreenOn);
- doPlugLocked(realtime, getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime));
+ updateTimeBasesLocked(false, !mScreenOn, uptime, realtime);
}
if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) {
if (mFile != null) {
@@ -4882,7 +5892,7 @@ public final class BatteryStatsImpl extends BatteryStats {
if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) {
// We don't record history while we are plugged in and fully charged.
// The next time we are unplugged, history will be cleared.
- mRecordingHistory = false;
+ mRecordingHistory = DEBUG;
}
}
}
@@ -4902,8 +5912,8 @@ public final class BatteryStatsImpl extends BatteryStats {
SamplingTimer kwlt = mKernelWakelockStats.get(name);
if (kwlt == null) {
- kwlt = new SamplingTimer(mUnpluggables, mOnBatteryInternal,
- true /* track reported values */);
+ kwlt = new SamplingTimer(mOnBatteryScreenOffTimeBase,
+ true /* track reported val */);
mKernelWakelockStats.put(name, kwlt);
}
kwlt.updateCurrentReportedCount(kws.mCount);
@@ -4922,48 +5932,124 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- private void updateNetworkActivityLocked() {
+ static final int NET_UPDATE_MOBILE = 1<<0;
+ static final int NET_UPDATE_WIFI = 1<<1;
+ static final int NET_UPDATE_ALL = 0xffff;
+
+ private void updateNetworkActivityLocked(int which, long elapsedRealtimeMs) {
if (!SystemProperties.getBoolean(PROP_QTAGUID_ENABLED, false)) return;
- final NetworkStats snapshot;
- try {
- snapshot = mNetworkStatsFactory.readNetworkStatsDetail();
- } catch (IOException e) {
- Log.wtf(TAG, "Failed to read network stats", e);
- return;
- }
+ if ((which&NET_UPDATE_MOBILE) != 0 && mMobileIfaces.length > 0) {
+ final NetworkStats snapshot;
+ final NetworkStats last = mCurMobileSnapshot;
+ try {
+ snapshot = mNetworkStatsFactory.readNetworkStatsDetail(UID_ALL,
+ mMobileIfaces, NetworkStats.TAG_NONE, mLastMobileSnapshot);
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed to read mobile network stats", e);
+ return;
+ }
- if (mLastSnapshot == null) {
- mLastSnapshot = snapshot;
- return;
- }
+ mCurMobileSnapshot = snapshot;
+ mLastMobileSnapshot = last;
- final NetworkStats delta = snapshot.subtract(mLastSnapshot);
- mLastSnapshot = snapshot;
+ if (mOnBatteryInternal) {
+ final NetworkStats delta = NetworkStats.subtract(snapshot, last,
+ null, null, mTmpNetworkStats);
+ mTmpNetworkStats = delta;
+
+ long radioTime = mMobileRadioActivePerAppTimer.checkpointRunningLocked(
+ elapsedRealtimeMs);
+ long totalPackets = delta.getTotalPackets();
+
+ final int size = delta.size();
+ for (int i = 0; i < size; i++) {
+ final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
+
+ if (entry.rxBytes == 0 || entry.txBytes == 0) continue;
+
+ final Uid u = getUidStatsLocked(mapUid(entry.uid));
+ u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_DATA, entry.rxBytes,
+ entry.rxPackets);
+ u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_DATA, entry.txBytes,
+ entry.txPackets);
+
+ if (radioTime > 0) {
+ // Distribute total radio active time in to this app.
+ long appPackets = entry.rxPackets + entry.txPackets;
+ long appRadioTime = (radioTime*appPackets)/totalPackets;
+ u.noteMobileRadioActiveTimeLocked(appRadioTime);
+ // Remove this app from the totals, so that we don't lose any time
+ // due to rounding.
+ radioTime -= appRadioTime;
+ totalPackets -= appPackets;
+ }
- NetworkStats.Entry entry = null;
- final int size = delta.size();
- for (int i = 0; i < size; i++) {
- entry = delta.getValues(i, entry);
+ mNetworkByteActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
+ entry.rxBytes);
+ mNetworkByteActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
+ entry.txBytes);
+ mNetworkPacketActivityCounters[NETWORK_MOBILE_RX_DATA].addCountLocked(
+ entry.rxPackets);
+ mNetworkPacketActivityCounters[NETWORK_MOBILE_TX_DATA].addCountLocked(
+ entry.txPackets);
+ }
+
+ if (radioTime > 0) {
+ // Whoops, there is some radio time we can't blame on an app!
+ mMobileRadioActiveUnknownTime.addCountLocked(radioTime);
+ mMobileRadioActiveUnknownCount.addCountLocked(1);
+ }
+ }
+ }
- if (entry.rxBytes == 0 || entry.txBytes == 0) continue;
- if (entry.tag != NetworkStats.TAG_NONE) continue;
+ if ((which&NET_UPDATE_WIFI) != 0 && mWifiIfaces.length > 0) {
+ final NetworkStats snapshot;
+ final NetworkStats last = mCurWifiSnapshot;
+ try {
+ snapshot = mNetworkStatsFactory.readNetworkStatsDetail(UID_ALL,
+ mWifiIfaces, NetworkStats.TAG_NONE, mLastWifiSnapshot);
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed to read wifi network stats", e);
+ return;
+ }
- final Uid u = getUidStatsLocked(entry.uid);
+ mCurWifiSnapshot = snapshot;
+ mLastWifiSnapshot = last;
- if (mMobileIfaces.contains(entry.iface)) {
- u.noteNetworkActivityLocked(NETWORK_MOBILE_RX_BYTES, entry.rxBytes);
- u.noteNetworkActivityLocked(NETWORK_MOBILE_TX_BYTES, entry.txBytes);
+ if (mOnBatteryInternal) {
+ final NetworkStats delta = NetworkStats.subtract(snapshot, last,
+ null, null, mTmpNetworkStats);
+ mTmpNetworkStats = delta;
+
+ final int size = delta.size();
+ for (int i = 0; i < size; i++) {
+ final NetworkStats.Entry entry = delta.getValues(i, mTmpNetworkStatsEntry);
+
+ if (DEBUG) {
+ final NetworkStats.Entry cur = snapshot.getValues(i, null);
+ Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes
+ + " tx=" + entry.txBytes + ", cur rx=" + cur.rxBytes
+ + " tx=" + cur.txBytes);
+ }
- mNetworkActivityCounters[NETWORK_MOBILE_RX_BYTES].addCountLocked(entry.rxBytes);
- mNetworkActivityCounters[NETWORK_MOBILE_TX_BYTES].addCountLocked(entry.txBytes);
+ if (entry.rxBytes == 0 || entry.txBytes == 0) continue;
- } else if (mWifiIfaces.contains(entry.iface)) {
- u.noteNetworkActivityLocked(NETWORK_WIFI_RX_BYTES, entry.rxBytes);
- u.noteNetworkActivityLocked(NETWORK_WIFI_TX_BYTES, entry.txBytes);
+ final Uid u = getUidStatsLocked(mapUid(entry.uid));
+ u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes,
+ entry.rxPackets);
+ u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes,
+ entry.txPackets);
- mNetworkActivityCounters[NETWORK_WIFI_RX_BYTES].addCountLocked(entry.rxBytes);
- mNetworkActivityCounters[NETWORK_WIFI_TX_BYTES].addCountLocked(entry.txBytes);
+ mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
+ entry.rxBytes);
+ mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
+ entry.txBytes);
+ mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
+ entry.rxPackets);
+ mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
+ entry.txPackets);
+ }
}
}
}
@@ -4982,7 +6068,7 @@ public final class BatteryStatsImpl extends BatteryStats {
case STATS_SINCE_CHARGED: return mUptime + (curTime-mUptimeStart);
case STATS_LAST: return mLastUptime;
case STATS_CURRENT: return (curTime-mUptimeStart);
- case STATS_SINCE_UNPLUGGED: return (curTime-mTrackBatteryUptimeStart);
+ case STATS_SINCE_UNPLUGGED: return (curTime- mOnBatteryTimeBase.getUptimeStart());
}
return 0;
}
@@ -4993,69 +6079,43 @@ public final class BatteryStatsImpl extends BatteryStats {
case STATS_SINCE_CHARGED: return mRealtime + (curTime-mRealtimeStart);
case STATS_LAST: return mLastRealtime;
case STATS_CURRENT: return (curTime-mRealtimeStart);
- case STATS_SINCE_UNPLUGGED: return (curTime-mTrackBatteryRealtimeStart);
+ case STATS_SINCE_UNPLUGGED: return (curTime- mOnBatteryTimeBase.getRealtimeStart());
}
return 0;
}
@Override
public long computeBatteryUptime(long curTime, int which) {
- switch (which) {
- case STATS_SINCE_CHARGED:
- return mBatteryUptime + getBatteryUptime(curTime);
- case STATS_LAST:
- return mBatteryLastUptime;
- case STATS_CURRENT:
- return getBatteryUptime(curTime);
- case STATS_SINCE_UNPLUGGED:
- return getBatteryUptimeLocked(curTime) - mUnpluggedBatteryUptime;
- }
- return 0;
+ return mOnBatteryTimeBase.computeUptime(curTime, which);
}
@Override
public long computeBatteryRealtime(long curTime, int which) {
- switch (which) {
- case STATS_SINCE_CHARGED:
- return mBatteryRealtime + getBatteryRealtimeLocked(curTime);
- case STATS_LAST:
- return mBatteryLastRealtime;
- case STATS_CURRENT:
- return getBatteryRealtimeLocked(curTime);
- case STATS_SINCE_UNPLUGGED:
- return getBatteryRealtimeLocked(curTime) - mUnpluggedBatteryRealtime;
- }
- return 0;
+ return mOnBatteryTimeBase.computeRealtime(curTime, which);
}
- long getBatteryUptimeLocked(long curTime) {
- long time = mTrackBatteryPastUptime;
- if (mOnBatteryInternal) {
- time += curTime - mTrackBatteryUptimeStart;
- }
- return time;
+ @Override
+ public long computeBatteryScreenOffUptime(long curTime, int which) {
+ return mOnBatteryScreenOffTimeBase.computeUptime(curTime, which);
+ }
+
+ @Override
+ public long computeBatteryScreenOffRealtime(long curTime, int which) {
+ return mOnBatteryScreenOffTimeBase.computeRealtime(curTime, which);
}
long getBatteryUptimeLocked() {
- return getBatteryUptime(SystemClock.uptimeMillis() * 1000);
+ return mOnBatteryTimeBase.getUptime(SystemClock.uptimeMillis() * 1000);
}
@Override
public long getBatteryUptime(long curTime) {
- return getBatteryUptimeLocked(curTime);
- }
-
- long getBatteryRealtimeLocked(long curTime) {
- long time = mTrackBatteryPastRealtime;
- if (mOnBatteryInternal) {
- time += curTime - mTrackBatteryRealtimeStart;
- }
- return time;
+ return mOnBatteryTimeBase.getUptime(curTime);
}
@Override
public long getBatteryRealtime(long curTime) {
- return getBatteryRealtimeLocked(curTime);
+ return mOnBatteryTimeBase.getRealtime(curTime);
}
@Override
@@ -5175,24 +6235,7 @@ public final class BatteryStatsImpl extends BatteryStats {
* if needed.
*/
public Uid.Proc getProcessStatsLocked(int uid, String name) {
- Uid u = getUidStatsLocked(uid);
- return u.getProcessStatsLocked(name);
- }
-
- /**
- * Retrieve the statistics object for a particular process, given
- * the name of the process.
- * @param name process name
- * @return the statistics object for the process
- */
- public Uid.Proc getProcessStatsLocked(String name, int pid) {
- int uid;
- if (mUidCache.containsKey(name)) {
- uid = mUidCache.get(name);
- } else {
- uid = Process.getUidForPid(pid);
- mUidCache.put(name, uid);
- }
+ uid = mapUid(uid);
Uid u = getUidStatsLocked(uid);
return u.getProcessStatsLocked(name);
}
@@ -5202,6 +6245,7 @@ public final class BatteryStatsImpl extends BatteryStats {
* if needed.
*/
public Uid.Pkg getPackageStatsLocked(int uid, String pkg) {
+ uid = mapUid(uid);
Uid u = getUidStatsLocked(uid);
return u.getPackageStatsLocked(pkg);
}
@@ -5211,6 +6255,7 @@ public final class BatteryStatsImpl extends BatteryStats {
* if needed.
*/
public Uid.Pkg.Serv getServiceStatsLocked(int uid, String pkg, String name) {
+ uid = mapUid(uid);
Uid u = getUidStatsLocked(uid);
return u.getServiceStatsLocked(pkg, name);
}
@@ -5251,7 +6296,7 @@ public final class BatteryStatsImpl extends BatteryStats {
time = (time*uidRunningTime)/totalRunningTime;
SamplingCounter uidSc = uidProc.mSpeedBins[sb];
if (uidSc == null) {
- uidSc = new SamplingCounter(mUnpluggables);
+ uidSc = new SamplingCounter(mOnBatteryTimeBase);
uidProc.mSpeedBins[sb] = uidSc;
}
uidSc.mCount.addAndGet((int)time);
@@ -5388,15 +6433,20 @@ public final class BatteryStatsImpl extends BatteryStats {
stream.close();
readSummaryFromParcel(in);
- } catch(java.io.IOException e) {
+ } catch(Exception e) {
Slog.e("BatteryStats", "Error reading battery statistics", e);
}
- long now = SystemClock.elapsedRealtime();
- if (USE_OLD_HISTORY) {
- addHistoryRecordLocked(now, HistoryItem.CMD_START);
+ if (mHistoryBuffer.dataPosition() > 0) {
+ long now = SystemClock.elapsedRealtime();
+ if (USE_OLD_HISTORY) {
+ addHistoryRecordLocked(now, HistoryItem.CMD_START);
+ }
+ addHistoryBufferLocked(now, HistoryItem.CMD_START);
+ mHistoryCur.currentTime = System.currentTimeMillis();
+ addHistoryBufferLocked(now, HistoryItem.CMD_CURRENT_TIME);
+ mHistoryCur.currentTime = 0;
}
- addHistoryBufferLocked(now, HistoryItem.CMD_START);
}
public int describeContents() {
@@ -5408,6 +6458,25 @@ public final class BatteryStatsImpl extends BatteryStats {
mHistoryBuffer.setDataSize(0);
mHistoryBuffer.setDataPosition(0);
+ mHistoryTagPool.clear();
+ mNextHistoryTagIdx = 0;
+ mNumHistoryTagChars = 0;
+
+ int numTags = in.readInt();
+ for (int i=0; i<numTags; i++) {
+ int idx = in.readInt();
+ String str = in.readString();
+ int uid = in.readInt();
+ HistoryTag tag = new HistoryTag();
+ tag.string = str;
+ tag.uid = uid;
+ tag.poolIdx = idx;
+ mHistoryTagPool.put(tag, idx);
+ if (idx >= mNextHistoryTagIdx) {
+ mNextHistoryTagIdx = idx+1;
+ }
+ mNumHistoryTagChars += tag.string.length() + 1;
+ }
int bufSize = in.readInt();
int curPos = in.dataPosition();
@@ -5476,6 +6545,13 @@ public final class BatteryStatsImpl extends BatteryStats {
Slog.i(TAG, sb.toString());
}
out.writeLong(mHistoryBaseTime + mLastHistoryTime);
+ out.writeInt(mHistoryTagPool.size());
+ for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
+ HistoryTag tag = ent.getKey();
+ out.writeInt(ent.getValue());
+ out.writeString(tag.string);
+ out.writeInt(tag.uid);
+ }
out.writeInt(mHistoryBuffer.dataSize());
if (DEBUG_HISTORY) Slog.i(TAG, "***************** WRITING HISTORY: "
+ mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
@@ -5509,10 +6585,11 @@ public final class BatteryStatsImpl extends BatteryStats {
readHistory(in, true);
mStartCount = in.readInt();
- mBatteryUptime = in.readLong();
- mBatteryRealtime = in.readLong();
mUptime = in.readLong();
mRealtime = in.readLong();
+ mStartClockTime = in.readLong();
+ mOnBatteryTimeBase.readSummaryFromParcel(in);
+ mOnBatteryScreenOffTimeBase.readSummaryFromParcel(in);
mDischargeUnplugLevel = in.readInt();
mDischargeCurrentLevel = in.readInt();
mLowDischargeAmountSinceCharge = in.readInt();
@@ -5538,14 +6615,26 @@ public final class BatteryStatsImpl extends BatteryStats {
mPhoneDataConnectionsTimer[i].readSummaryFromParcelLocked(in);
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i].readSummaryFromParcelLocked(in);
- }
+ mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in);
+ mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in);
+ }
+ mMobileRadioActive = false;
+ mMobileRadioActiveTimer.readSummaryFromParcelLocked(in);
+ mMobileRadioActivePerAppTimer.readSummaryFromParcelLocked(in);
+ mMobileRadioActiveUnknownTime.readSummaryFromParcelLocked(in);
+ mMobileRadioActiveUnknownCount.readSummaryFromParcelLocked(in);
mWifiOn = false;
mWifiOnTimer.readSummaryFromParcelLocked(in);
mGlobalWifiRunning = false;
mGlobalWifiRunningTimer.readSummaryFromParcelLocked(in);
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ mWifiStateTimer[i].readSummaryFromParcelLocked(in);
+ }
mBluetoothOn = false;
mBluetoothOnTimer.readSummaryFromParcelLocked(in);
+ for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
+ mBluetoothStateTimer[i].readSummaryFromParcelLocked(in);
+ }
int NKW = in.readInt();
if (NKW > 10000) {
@@ -5560,6 +6649,9 @@ public final class BatteryStatsImpl extends BatteryStats {
}
sNumSpeedSteps = in.readInt();
+ if (sNumSpeedSteps < 0 || sNumSpeedSteps > 100) {
+ throw new BadParcelableException("Bad speed steps in data: " + sNumSpeedSteps);
+ }
final int NU = in.readInt();
if (NU > 10000) {
@@ -5619,12 +6711,15 @@ public final class BatteryStatsImpl extends BatteryStats {
}
if (in.readInt() != 0) {
- if (u.mNetworkActivityCounters == null) {
+ if (u.mNetworkByteActivityCounters == null) {
u.initNetworkActivityLocked();
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- u.mNetworkActivityCounters[i].readSummaryFromParcelLocked(in);
+ u.mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in);
+ u.mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in);
}
+ u.mMobileRadioActiveTime.readSummaryFromParcelLocked(in);
+ u.mMobileRadioActiveCount.readSummaryFromParcelLocked(in);
}
int NW = in.readInt();
@@ -5678,7 +6773,7 @@ public final class BatteryStatsImpl extends BatteryStats {
p.mSpeedBins = new SamplingCounter[NSB];
for (int i=0; i<NSB; i++) {
if (in.readInt() != 0) {
- p.mSpeedBins[i] = new SamplingCounter(mUnpluggables);
+ p.mSpeedBins[i] = new SamplingCounter(mOnBatteryTimeBase);
p.mSpeedBins[i].readSummaryFromParcelLocked(in);
}
}
@@ -5719,50 +6814,58 @@ public final class BatteryStatsImpl extends BatteryStats {
* @param out the Parcel to be written to.
*/
public void writeSummaryToParcel(Parcel out) {
- // Need to update with current kernel wake lock counts.
- updateKernelWakelocksLocked();
- updateNetworkActivityLocked();
+ pullPendingStateUpdatesLocked();
final long NOW_SYS = SystemClock.uptimeMillis() * 1000;
final long NOWREAL_SYS = SystemClock.elapsedRealtime() * 1000;
- final long NOW = getBatteryUptimeLocked(NOW_SYS);
- final long NOWREAL = getBatteryRealtimeLocked(NOWREAL_SYS);
out.writeInt(VERSION);
writeHistory(out, true);
out.writeInt(mStartCount);
- out.writeLong(computeBatteryUptime(NOW_SYS, STATS_SINCE_CHARGED));
- out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED));
out.writeLong(computeUptime(NOW_SYS, STATS_SINCE_CHARGED));
out.writeLong(computeRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED));
+ out.writeLong(mStartClockTime);
+ mOnBatteryTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS);
+ mOnBatteryScreenOffTimeBase.writeSummaryToParcel(out, NOW_SYS, NOWREAL_SYS);
out.writeInt(mDischargeUnplugLevel);
out.writeInt(mDischargeCurrentLevel);
out.writeInt(getLowDischargeAmountSinceCharge());
out.writeInt(getHighDischargeAmountSinceCharge());
out.writeInt(getDischargeAmountScreenOnSinceCharge());
out.writeInt(getDischargeAmountScreenOffSinceCharge());
-
- mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+
+ mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL);
+ mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
}
mInputEventCounter.writeSummaryFromParcelLocked(out);
- mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ mPhoneOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
- mPhoneSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL);
+ mPhoneSignalStrengthsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
}
- mPhoneSignalScanningTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ mPhoneSignalScanningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- mPhoneDataConnectionsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL);
+ mPhoneDataConnectionsTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i].writeSummaryFromParcelLocked(out);
+ mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out);
+ mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out);
+ }
+ mMobileRadioActiveTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ mMobileRadioActivePerAppTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ mMobileRadioActiveUnknownTime.writeSummaryFromParcelLocked(out);
+ mMobileRadioActiveUnknownCount.writeSummaryFromParcelLocked(out);
+ mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ mWifiStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ }
+ mBluetoothOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+ for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
+ mBluetoothStateTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
}
- mWifiOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
- mGlobalWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL);
- mBluetoothOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
out.writeInt(mKernelWakelockStats.size());
for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
@@ -5770,7 +6873,7 @@ public final class BatteryStatsImpl extends BatteryStats {
if (kwlt != null) {
out.writeInt(1);
out.writeString(ent.getKey());
- ent.getValue().writeSummaryFromParcelLocked(out, NOWREAL);
+ ent.getValue().writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
@@ -5785,57 +6888,57 @@ public final class BatteryStatsImpl extends BatteryStats {
if (u.mWifiRunningTimer != null) {
out.writeInt(1);
- u.mWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mWifiRunningTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (u.mFullWifiLockTimer != null) {
out.writeInt(1);
- u.mFullWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mFullWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (u.mWifiScanTimer != null) {
out.writeInt(1);
- u.mWifiScanTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mWifiScanTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
for (int i = 0; i < Uid.NUM_WIFI_BATCHED_SCAN_BINS; i++) {
if (u.mWifiBatchedScanTimer[i] != null) {
out.writeInt(1);
- u.mWifiBatchedScanTimer[i].writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mWifiBatchedScanTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
}
if (u.mWifiMulticastTimer != null) {
out.writeInt(1);
- u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (u.mAudioTurnedOnTimer != null) {
out.writeInt(1);
- u.mAudioTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mAudioTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (u.mVideoTurnedOnTimer != null) {
out.writeInt(1);
- u.mVideoTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mVideoTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (u.mForegroundActivityTimer != null) {
out.writeInt(1);
- u.mForegroundActivityTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mForegroundActivityTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (u.mVibratorOnTimer != null) {
out.writeInt(1);
- u.mVibratorOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mVibratorOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
@@ -5849,13 +6952,16 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- if (u.mNetworkActivityCounters == null) {
+ if (u.mNetworkByteActivityCounters == null) {
out.writeInt(0);
} else {
out.writeInt(1);
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- u.mNetworkActivityCounters[i].writeSummaryFromParcelLocked(out);
+ u.mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out);
+ u.mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out);
}
+ u.mMobileRadioActiveTime.writeSummaryFromParcelLocked(out);
+ u.mMobileRadioActiveCount.writeSummaryFromParcelLocked(out);
}
int NW = u.mWakelockStats.size();
@@ -5867,19 +6973,19 @@ public final class BatteryStatsImpl extends BatteryStats {
Uid.Wakelock wl = ent.getValue();
if (wl.mTimerFull != null) {
out.writeInt(1);
- wl.mTimerFull.writeSummaryFromParcelLocked(out, NOWREAL);
+ wl.mTimerFull.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (wl.mTimerPartial != null) {
out.writeInt(1);
- wl.mTimerPartial.writeSummaryFromParcelLocked(out, NOWREAL);
+ wl.mTimerPartial.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
if (wl.mTimerWindow != null) {
out.writeInt(1);
- wl.mTimerWindow.writeSummaryFromParcelLocked(out, NOWREAL);
+ wl.mTimerWindow.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
@@ -5895,7 +7001,7 @@ public final class BatteryStatsImpl extends BatteryStats {
Uid.Sensor se = ent.getValue();
if (se.mTimer != null) {
out.writeInt(1);
- se.mTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ se.mTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
} else {
out.writeInt(0);
}
@@ -5942,7 +7048,8 @@ public final class BatteryStatsImpl extends BatteryStats {
: ps.mServiceStats.entrySet()) {
out.writeString(sent.getKey());
BatteryStatsImpl.Uid.Pkg.Serv ss = sent.getValue();
- long time = ss.getStartTimeToNowLocked(NOW);
+ long time = ss.getStartTimeToNowLocked(
+ mOnBatteryTimeBase.getUptime(NOW_SYS));
out.writeLong(time);
out.writeInt(ss.mStarts);
out.writeInt(ss.mLaunches);
@@ -5966,51 +7073,60 @@ public final class BatteryStatsImpl extends BatteryStats {
readHistory(in, false);
mStartCount = in.readInt();
- mBatteryUptime = in.readLong();
- mBatteryLastUptime = 0;
- mBatteryRealtime = in.readLong();
- mBatteryLastRealtime = 0;
+ mStartClockTime = in.readLong();
+ mUptime = in.readLong();
+ mUptimeStart = in.readLong();
+ mLastUptime = 0;
+ mRealtime = in.readLong();
+ mRealtimeStart = in.readLong();
+ mLastRealtime = 0;
+ mOnBattery = in.readInt() != 0;
+ mOnBatteryInternal = false; // we are no longer really running.
+ mOnBatteryTimeBase.readFromParcel(in);
+ mOnBatteryScreenOffTimeBase.readFromParcel(in);
+
mScreenOn = false;
- mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables, in);
+ mScreenOnTimer = new StopwatchTimer(null, -1, null, mOnBatteryTimeBase, in);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i,
- null, mUnpluggables, in);
+ mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mOnBatteryTimeBase,
+ in);
}
- mInputEventCounter = new Counter(mUnpluggables, in);
+ mInputEventCounter = new Counter(mOnBatteryTimeBase, in);
mPhoneOn = false;
- mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
+ mPhoneOnTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i,
- null, mUnpluggables, in);
+ null, mOnBatteryTimeBase, in);
}
- mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables, in);
+ mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mOnBatteryTimeBase, in);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i,
- null, mUnpluggables, in);
+ null, mOnBatteryTimeBase, in);
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i] = new LongSamplingCounter(mUnpluggables, in);
- }
+ mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ }
+ mMobileRadioActive = false;
+ mMobileRadioActiveTimer = new StopwatchTimer(null, -400, null, mOnBatteryTimeBase, in);
+ mMobileRadioActivePerAppTimer = new StopwatchTimer(null, -401, null, mOnBatteryTimeBase,
+ in);
+ mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
+ mMobileRadioActiveUnknownCount = new LongSamplingCounter(mOnBatteryTimeBase, in);
mWifiOn = false;
- mWifiOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
+ mWifiOnTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in);
mGlobalWifiRunning = false;
- mGlobalWifiRunningTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
+ mGlobalWifiRunningTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in);
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ mWifiStateTimer[i] = new StopwatchTimer(null, -600-i,
+ null, mOnBatteryTimeBase, in);
+ }
mBluetoothOn = false;
- mBluetoothOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
- mUptime = in.readLong();
- mUptimeStart = in.readLong();
- mLastUptime = 0;
- mRealtime = in.readLong();
- mRealtimeStart = in.readLong();
- mLastRealtime = 0;
- mOnBattery = in.readInt() != 0;
- mOnBatteryInternal = false; // we are no longer really running.
- mTrackBatteryPastUptime = in.readLong();
- mTrackBatteryUptimeStart = in.readLong();
- mTrackBatteryPastRealtime = in.readLong();
- mTrackBatteryRealtimeStart = in.readLong();
- mUnpluggedBatteryUptime = in.readLong();
- mUnpluggedBatteryRealtime = in.readLong();
+ mBluetoothOnTimer = new StopwatchTimer(null, -2, null, mOnBatteryTimeBase, in);
+ for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
+ mBluetoothStateTimer[i] = new StopwatchTimer(null, -500-i,
+ null, mOnBatteryTimeBase, in);
+ }
mDischargeUnplugLevel = in.readInt();
mDischargeCurrentLevel = in.readInt();
mLowDischargeAmountSinceCharge = in.readInt();
@@ -6021,9 +7137,6 @@ public final class BatteryStatsImpl extends BatteryStats {
mDischargeAmountScreenOffSinceCharge = in.readInt();
mLastWriteTime = in.readLong();
- mRadioDataUptime = in.readLong();
- mRadioDataStart = -1;
-
mBluetoothPingCount = in.readInt();
mBluetoothPingStart = -1;
@@ -6033,7 +7146,7 @@ public final class BatteryStatsImpl extends BatteryStats {
if (in.readInt() != 0) {
String wakelockName = in.readString();
in.readInt(); // Extra 0/1 written by Timer.writeTimerToParcel
- SamplingTimer kwlt = new SamplingTimer(mUnpluggables, mOnBattery, in);
+ SamplingTimer kwlt = new SamplingTimer(mOnBatteryTimeBase, in);
mKernelWakelockStats.put(wakelockName, kwlt);
}
}
@@ -6054,7 +7167,7 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int i = 0; i < numUids; i++) {
int uid = in.readInt();
Uid u = new Uid(uid);
- u.readFromParcelLocked(mUnpluggables, in);
+ u.readFromParcelLocked(mOnBatteryTimeBase, mOnBatteryScreenOffTimeBase, in);
mUidStats.append(uid, u);
}
}
@@ -6070,51 +7183,57 @@ public final class BatteryStatsImpl extends BatteryStats {
@SuppressWarnings("unused")
void writeToParcelLocked(Parcel out, boolean inclUids, int flags) {
// Need to update with current kernel wake lock counts.
- updateKernelWakelocksLocked();
- updateNetworkActivityLocked();
+ pullPendingStateUpdatesLocked();
final long uSecUptime = SystemClock.uptimeMillis() * 1000;
final long uSecRealtime = SystemClock.elapsedRealtime() * 1000;
- final long batteryUptime = getBatteryUptimeLocked(uSecUptime);
- final long batteryRealtime = getBatteryRealtimeLocked(uSecRealtime);
+ final long batteryRealtime = mOnBatteryTimeBase.getRealtime(uSecRealtime);
+ final long batteryScreenOffRealtime = mOnBatteryScreenOffTimeBase.getRealtime(uSecRealtime);
out.writeInt(MAGIC);
writeHistory(out, false);
out.writeInt(mStartCount);
- out.writeLong(mBatteryUptime);
- out.writeLong(mBatteryRealtime);
- mScreenOnTimer.writeToParcel(out, batteryRealtime);
+ out.writeLong(mStartClockTime);
+ out.writeLong(mUptime);
+ out.writeLong(mUptimeStart);
+ out.writeLong(mRealtime);
+ out.writeLong(mRealtimeStart);
+ out.writeInt(mOnBattery ? 1 : 0);
+ mOnBatteryTimeBase.writeToParcel(out, uSecUptime, uSecRealtime);
+ mOnBatteryScreenOffTimeBase.writeToParcel(out, uSecUptime, uSecRealtime);
+
+ mScreenOnTimer.writeToParcel(out, uSecRealtime);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- mScreenBrightnessTimer[i].writeToParcel(out, batteryRealtime);
+ mScreenBrightnessTimer[i].writeToParcel(out, uSecRealtime);
}
mInputEventCounter.writeToParcel(out);
- mPhoneOnTimer.writeToParcel(out, batteryRealtime);
+ mPhoneOnTimer.writeToParcel(out, uSecRealtime);
for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
- mPhoneSignalStrengthsTimer[i].writeToParcel(out, batteryRealtime);
+ mPhoneSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime);
}
- mPhoneSignalScanningTimer.writeToParcel(out, batteryRealtime);
+ mPhoneSignalScanningTimer.writeToParcel(out, uSecRealtime);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- mPhoneDataConnectionsTimer[i].writeToParcel(out, batteryRealtime);
+ mPhoneDataConnectionsTimer[i].writeToParcel(out, uSecRealtime);
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
- mNetworkActivityCounters[i].writeToParcel(out);
+ mNetworkByteActivityCounters[i].writeToParcel(out);
+ mNetworkPacketActivityCounters[i].writeToParcel(out);
+ }
+ mMobileRadioActiveTimer.writeToParcel(out, uSecRealtime);
+ mMobileRadioActivePerAppTimer.writeToParcel(out, uSecRealtime);
+ mMobileRadioActiveUnknownTime.writeToParcel(out);
+ mMobileRadioActiveUnknownCount.writeToParcel(out);
+ mWifiOnTimer.writeToParcel(out, uSecRealtime);
+ mGlobalWifiRunningTimer.writeToParcel(out, uSecRealtime);
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ mWifiStateTimer[i].writeToParcel(out, uSecRealtime);
+ }
+ mBluetoothOnTimer.writeToParcel(out, uSecRealtime);
+ for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
+ mBluetoothStateTimer[i].writeToParcel(out, uSecRealtime);
}
- mWifiOnTimer.writeToParcel(out, batteryRealtime);
- mGlobalWifiRunningTimer.writeToParcel(out, batteryRealtime);
- mBluetoothOnTimer.writeToParcel(out, batteryRealtime);
- out.writeLong(mUptime);
- out.writeLong(mUptimeStart);
- out.writeLong(mRealtime);
- out.writeLong(mRealtimeStart);
- out.writeInt(mOnBattery ? 1 : 0);
- out.writeLong(batteryUptime);
- out.writeLong(mTrackBatteryUptimeStart);
- out.writeLong(batteryRealtime);
- out.writeLong(mTrackBatteryRealtimeStart);
- out.writeLong(mUnpluggedBatteryUptime);
- out.writeLong(mUnpluggedBatteryRealtime);
out.writeInt(mDischargeUnplugLevel);
out.writeInt(mDischargeCurrentLevel);
out.writeInt(mLowDischargeAmountSinceCharge);
@@ -6125,9 +7244,6 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mDischargeAmountScreenOffSinceCharge);
out.writeLong(mLastWriteTime);
- // Write radio uptime for data
- out.writeLong(getRadioDataUptime());
-
out.writeInt(getBluetoothPingCount());
if (inclUids) {
@@ -6137,7 +7253,7 @@ public final class BatteryStatsImpl extends BatteryStats {
if (kwlt != null) {
out.writeInt(1);
out.writeString(ent.getKey());
- Timer.writeTimerToParcel(out, kwlt, batteryRealtime);
+ Timer.writeTimerToParcel(out, kwlt, uSecRealtime);
} else {
out.writeInt(0);
}
@@ -6155,7 +7271,7 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mUidStats.keyAt(i));
Uid uid = mUidStats.valueAt(i);
- uid.writeToParcelLocked(out, batteryRealtime);
+ uid.writeToParcelLocked(out, uSecRealtime);
}
} else {
out.writeInt(0);
@@ -6175,12 +7291,15 @@ public final class BatteryStatsImpl extends BatteryStats {
public void prepareForDumpLocked() {
// Need to retrieve current kernel wake lock stats before printing.
- updateKernelWakelocksLocked();
- updateNetworkActivityLocked();
+ pullPendingStateUpdatesLocked();
}
- public void dumpLocked(PrintWriter pw, boolean isUnpluggedOnly, int reqUid) {
+ public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) {
if (DEBUG) {
+ pw.println("mOnBatteryTimeBase:");
+ mOnBatteryTimeBase.dump(pw, " ");
+ pw.println("mOnBatteryScreenOffTimeBase:");
+ mOnBatteryScreenOffTimeBase.dump(pw, " ");
Printer pr = new PrintWriterPrinter(pw);
pr.println("*** Screen timer:");
mScreenOnTimer.logState(pr, " ");
@@ -6202,13 +7321,23 @@ public final class BatteryStatsImpl extends BatteryStats {
pr.println("*** Data connection type #" + i + ":");
mPhoneDataConnectionsTimer[i].logState(pr, " ");
}
+ pr.println("*** Mobile network active timer:");
+ mMobileRadioActiveTimer.logState(pr, " ");
pr.println("*** Wifi timer:");
mWifiOnTimer.logState(pr, " ");
pr.println("*** WifiRunning timer:");
mGlobalWifiRunningTimer.logState(pr, " ");
+ for (int i=0; i<NUM_WIFI_STATES; i++) {
+ pr.println("*** Wifi state #" + i + ":");
+ mWifiStateTimer[i].logState(pr, " ");
+ }
pr.println("*** Bluetooth timer:");
mBluetoothOnTimer.logState(pr, " ");
+ for (int i=0; i< NUM_BLUETOOTH_STATES; i++) {
+ pr.println("*** Bluetooth active type #" + i + ":");
+ mBluetoothStateTimer[i].logState(pr, " ");
+ }
}
- super.dumpLocked(pw, isUnpluggedOnly, reqUid);
+ super.dumpLocked(context, pw, flags, reqUid, histStart);
}
}
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index c6b3e7c..1766f7b 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -25,7 +25,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import libcore.io.IoUtils;
-import libcore.io.Libcore;
import dalvik.system.Zygote;
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index bf62745..05c57e8 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -244,9 +244,11 @@ public class ZygoteInit {
}
static void preload() {
+ Log.d(TAG, "begin preload");
preloadClasses();
preloadResources();
preloadOpenGL();
+ Log.d(TAG, "end preload");
}
private static void preloadOpenGL() {
diff --git a/core/java/com/android/internal/preference/YesNoPreference.java b/core/java/com/android/internal/preference/YesNoPreference.java
index cf68a58..7abf416 100644
--- a/core/java/com/android/internal/preference/YesNoPreference.java
+++ b/core/java/com/android/internal/preference/YesNoPreference.java
@@ -31,15 +31,19 @@ import android.util.AttributeSet;
*/
public class YesNoPreference extends DialogPreference {
private boolean mWasPositiveResult;
-
- public YesNoPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+
+ public YesNoPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public YesNoPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
public YesNoPreference(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.yesNoPreferenceStyle);
}
-
+
public YesNoPreference(Context context) {
this(context, null);
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 97ea7d8..4734712 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -41,11 +41,11 @@ interface IStatusBarService
out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications,
out int[] switches, out List<IBinder> binders);
void onPanelRevealed();
- void onNotificationClick(String pkg, String tag, int id);
+ void onNotificationClick(String pkg, String tag, int id, int userId);
void onNotificationError(String pkg, String tag, int id,
- int uid, int initialPid, String message);
- void onClearAllNotifications();
- void onNotificationClear(String pkg, String tag, int id);
+ int uid, int initialPid, String message, int userId);
+ void onClearAllNotifications(int userId);
+ void onNotificationClear(String pkg, String tag, int id, int userId);
void setSystemUiVisibility(int vis, int mask);
void setHardKeyboardEnabled(boolean enabled);
void toggleRecentApps();
diff --git a/core/java/com/android/internal/view/ActionBarPolicy.java b/core/java/com/android/internal/view/ActionBarPolicy.java
index 25086c5..bee59dc 100644
--- a/core/java/com/android/internal/view/ActionBarPolicy.java
+++ b/core/java/com/android/internal/view/ActionBarPolicy.java
@@ -19,11 +19,9 @@ package com.android.internal.view;
import com.android.internal.R;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
-import android.view.ViewConfiguration;
/**
* Allows components to query for various configuration policy decisions
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 12ced68..45ca7fc 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -32,7 +32,9 @@ import com.android.internal.view.IInputMethodClient;
* this file.
*/
interface IInputMethodManager {
+ // TODO: Use ParceledListSlice instead
List<InputMethodInfo> getInputMethodList();
+ // TODO: Use ParceledListSlice instead
List<InputMethodInfo> getEnabledInputMethodList();
List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId,
boolean allowsImplicitlySelectedSubtypes);
@@ -73,5 +75,7 @@ interface IInputMethodManager {
boolean switchToNextInputMethod(in IBinder token, boolean onlyCurrentIme);
boolean shouldOfferSwitchingToNextInputMethod(in IBinder token);
boolean setInputMethodEnabled(String id, boolean enabled);
- oneway void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
+ void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
+ int getInputMethodWindowVisibleHeight();
+ oneway void notifyTextCommitted();
}
diff --git a/core/java/com/android/internal/view/RotationPolicy.java b/core/java/com/android/internal/view/RotationPolicy.java
index 6295314..b479cb1 100644
--- a/core/java/com/android/internal/view/RotationPolicy.java
+++ b/core/java/com/android/internal/view/RotationPolicy.java
@@ -18,7 +18,9 @@ package com.android.internal.view;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.database.ContentObserver;
+import android.graphics.Point;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
@@ -26,15 +28,20 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
+import android.view.Display;
import android.view.IWindowManager;
import android.view.Surface;
import android.view.WindowManagerGlobal;
+import com.android.internal.R;
+
/**
* Provides helper functions for configuring the display rotation policy.
*/
public final class RotationPolicy {
private static final String TAG = "RotationPolicy";
+ private static final int CURRENT_ROTATION = -1;
+ private static final int NATURAL_ROTATION = Surface.ROTATION_0;
private RotationPolicy() {
}
@@ -57,23 +64,33 @@ public final class RotationPolicy {
}
/**
- * Returns true if the device supports the rotation-lock toggle feature
- * in the system UI or system bar.
+ * Returns the orientation that will be used when locking the orientation from system UI
+ * with {@link #setRotationLock}.
*
- * When the rotation-lock toggle is supported, the "auto-rotate screen" option in
- * Display settings should be hidden, but it should remain available in Accessibility
- * settings.
+ * If the device only supports locking to its natural orientation, this will be either
+ * Configuration.ORIENTATION_PORTRAIT or Configuration.ORIENTATION_LANDSCAPE,
+ * otherwise Configuration.ORIENTATION_UNDEFINED if any orientation is lockable.
*/
- public static boolean isRotationLockToggleSupported(Context context) {
- return isRotationSupported(context)
- && context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
+ public static int getRotationLockOrientation(Context context) {
+ if (!areAllRotationsAllowed(context)) {
+ final Point size = new Point();
+ final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+ try {
+ wm.getInitialDisplaySize(Display.DEFAULT_DISPLAY, size);
+ return size.x < size.y ?
+ Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to get the display size");
+ }
+ }
+ return Configuration.ORIENTATION_UNDEFINED;
}
/**
- * Returns true if the rotation-lock toggle should be shown in the UI.
+ * Returns true if the rotation-lock toggle should be shown in system UI.
*/
public static boolean isRotationLockToggleVisible(Context context) {
- return isRotationLockToggleSupported(context) &&
+ return isRotationSupported(context) &&
Settings.System.getIntForUser(context.getContentResolver(),
Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0,
UserHandle.USER_CURRENT) == 0;
@@ -88,50 +105,42 @@ public final class RotationPolicy {
}
/**
- * Enables or disables rotation lock.
- *
- * Should be used by the rotation lock toggle.
+ * Enables or disables rotation lock from the system UI toggle.
*/
public static void setRotationLock(Context context, final boolean enabled) {
Settings.System.putIntForUser(context.getContentResolver(),
Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0,
UserHandle.USER_CURRENT);
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- try {
- IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- if (enabled) {
- wm.freezeRotation(-1);
- } else {
- wm.thawRotation();
- }
- } catch (RemoteException exc) {
- Log.w(TAG, "Unable to save auto-rotate setting");
- }
- }
- });
+ final int rotation = areAllRotationsAllowed(context) ? CURRENT_ROTATION : NATURAL_ROTATION;
+ setRotationLock(enabled, rotation);
}
/**
- * Enables or disables rotation lock and adjusts whether the rotation lock toggle
- * should be hidden for accessibility purposes.
+ * Enables or disables natural rotation lock from Accessibility settings.
*
- * Should be used by Display settings and Accessibility settings.
+ * If rotation is locked for accessibility, the system UI toggle is hidden to avoid confusion.
*/
public static void setRotationLockForAccessibility(Context context, final boolean enabled) {
Settings.System.putIntForUser(context.getContentResolver(),
Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, enabled ? 1 : 0,
UserHandle.USER_CURRENT);
+ setRotationLock(enabled, NATURAL_ROTATION);
+ }
+
+ private static boolean areAllRotationsAllowed(Context context) {
+ return context.getResources().getBoolean(R.bool.config_allowAllRotations);
+ }
+
+ private static void setRotationLock(final boolean enabled, final int rotation) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
try {
IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
if (enabled) {
- wm.freezeRotation(Surface.ROTATION_0);
+ wm.freezeRotation(rotation);
} else {
wm.thawRotation();
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java
index 7ca6c1b..ed676bb 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItem.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -163,7 +163,7 @@ public class ActionMenuItem implements MenuItem {
public MenuItem setIcon(int iconRes) {
mIconResId = iconRes;
- mIconDrawable = mContext.getResources().getDrawable(iconRes);
+ mIconDrawable = mContext.getDrawable(iconRes);
return this;
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 238a9c0..3cceebe 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -28,8 +28,11 @@ import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.ActionMenuView;
+import android.widget.ListPopupWindow;
import android.widget.TextView;
import android.widget.Toast;
+import android.widget.ListPopupWindow.ForwardingListener;
/**
* @hide
@@ -43,6 +46,8 @@ public class ActionMenuItemView extends TextView
private CharSequence mTitle;
private Drawable mIcon;
private MenuBuilder.ItemInvoker mItemInvoker;
+ private ForwardingListener mForwardingListener;
+ private PopupCallback mPopupCallback;
private boolean mAllowTextWithIcon;
private boolean mExpandedFormat;
@@ -60,13 +65,17 @@ public class ActionMenuItemView extends TextView
this(context, attrs, 0);
}
- public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public ActionMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ActionMenuItemView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
final Resources res = context.getResources();
mAllowTextWithIcon = res.getBoolean(
com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
- TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.ActionMenuItemView, 0, 0);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.ActionMenuItemView, defStyleAttr, defStyleRes);
mMinWidth = a.getDimensionPixelSize(
com.android.internal.R.styleable.ActionMenuItemView_minWidth, 0);
a.recycle();
@@ -99,6 +108,7 @@ public class ActionMenuItemView extends TextView
return mItemData;
}
+ @Override
public void initialize(MenuItemImpl itemData, int menuType) {
mItemData = itemData;
@@ -108,8 +118,24 @@ public class ActionMenuItemView extends TextView
setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
setEnabled(itemData.isEnabled());
+
+ if (itemData.hasSubMenu()) {
+ if (mForwardingListener == null) {
+ mForwardingListener = new ActionMenuItemForwardingListener();
+ }
+ }
}
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+ if (mItemData.hasSubMenu() && mForwardingListener != null
+ && mForwardingListener.onTouch(this, e)) {
+ return true;
+ }
+ return super.onTouchEvent(e);
+ }
+
+ @Override
public void onClick(View v) {
if (mItemInvoker != null) {
mItemInvoker.invokeItem(mItemData);
@@ -120,6 +146,10 @@ public class ActionMenuItemView extends TextView
mItemInvoker = invoker;
}
+ public void setPopupCallback(PopupCallback popupCallback) {
+ mPopupCallback = popupCallback;
+ }
+
public boolean prefersCondensedTitle() {
return true;
}
@@ -285,4 +315,42 @@ public class ActionMenuItemView extends TextView
super.setPadding((w - dw) / 2, getPaddingTop(), getPaddingRight(), getPaddingBottom());
}
}
+
+ private class ActionMenuItemForwardingListener extends ForwardingListener {
+ public ActionMenuItemForwardingListener() {
+ super(ActionMenuItemView.this);
+ }
+
+ @Override
+ public ListPopupWindow getPopup() {
+ if (mPopupCallback != null) {
+ return mPopupCallback.getPopup();
+ }
+ return null;
+ }
+
+ @Override
+ protected boolean onForwardingStarted() {
+ // Call the invoker, then check if the expected popup is showing.
+ if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) {
+ final ListPopupWindow popup = getPopup();
+ return popup != null && popup.isShowing();
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean onForwardingStopped() {
+ final ListPopupWindow popup = getPopup();
+ if (popup != null) {
+ popup.dismiss();
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public static abstract class PopupCallback {
+ public abstract ListPopupWindow getPopup();
+ }
}
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index 5d0b25f..de5e279 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -57,8 +57,8 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
private static String sPrependShortcutLabel;
- public IconMenuItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs);
+ public IconMenuItemView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
if (sPrependShortcutLabel == null) {
/*
@@ -68,10 +68,9 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
sPrependShortcutLabel = getResources().getString(
com.android.internal.R.string.prepend_shortcut_label);
}
-
- TypedArray a =
- context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.MenuView, defStyle, 0);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.MenuView, defStyleAttr, defStyleRes);
mDisabledAlpha = a.getFloat(
com.android.internal.R.styleable.MenuView_itemIconDisabledAlpha, 0.8f);
@@ -81,7 +80,11 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
a.recycle();
}
-
+
+ public IconMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
public IconMenuItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index a2a4acc..692bdac 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -55,13 +55,13 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
private boolean mForceShowIcon;
- public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs);
-
- TypedArray a =
- context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.MenuView, defStyle, 0);
-
+ public ListMenuItemView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.MenuView, defStyleAttr, defStyleRes);
+
mBackground = a.getDrawable(com.android.internal.R.styleable.MenuView_itemBackground);
mTextAppearance = a.getResourceId(com.android.internal.R.styleable.
MenuView_itemTextAppearance, -1);
@@ -72,6 +72,10 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
a.recycle();
}
+ public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
public ListMenuItemView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
index e1bb3621..c476354 100644
--- a/core/java/com/android/internal/view/menu/ListMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
@@ -17,7 +17,6 @@
package com.android.internal.view.menu;
import android.content.Context;
-import android.database.DataSetObserver;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.SparseArray;
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 195a00d..b776226 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -919,7 +919,7 @@ public class MenuBuilder implements Menu {
* sub menu is about to be shown, <var>allMenusAreClosing</var>
* is false.
*/
- final void close(boolean allMenusAreClosing) {
+ public final void close(boolean allMenusAreClosing) {
if (mIsClosing) return;
mIsClosing = true;
@@ -946,7 +946,7 @@ public class MenuBuilder implements Menu {
* false if only item properties changed.
* (Visibility is a structural property since it affects layout.)
*/
- void onItemsChanged(boolean structureChanged) {
+ public void onItemsChanged(boolean structureChanged) {
if (!mPreventDispatchingItemsChanged) {
if (structureChanged) {
mIsVisibleItemsStale = true;
@@ -1000,7 +1000,7 @@ public class MenuBuilder implements Menu {
onItemsChanged(true);
}
- ArrayList<MenuItemImpl> getVisibleItems() {
+ public ArrayList<MenuItemImpl> getVisibleItems() {
if (!mIsVisibleItemsStale) return mVisibleItems;
// Refresh the visible items
@@ -1085,12 +1085,12 @@ public class MenuBuilder implements Menu {
mIsActionItemsStale = false;
}
- ArrayList<MenuItemImpl> getActionItems() {
+ public ArrayList<MenuItemImpl> getActionItems() {
flagActionItems();
return mActionItems;
}
- ArrayList<MenuItemImpl> getNonActionItems() {
+ public ArrayList<MenuItemImpl> getNonActionItems() {
flagActionItems();
return mNonActionItems;
}
@@ -1121,7 +1121,7 @@ public class MenuBuilder implements Menu {
}
if (iconRes > 0) {
- mHeaderIcon = r.getDrawable(iconRes);
+ mHeaderIcon = getContext().getDrawable(iconRes);
} else if (icon != null) {
mHeaderIcon = icon;
}
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 4d0a326..61dcaca 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -385,7 +385,7 @@ public final class MenuItemImpl implements MenuItem {
}
if (mIconResId != NO_ICON) {
- Drawable icon = mMenu.getResources().getDrawable(mIconResId);
+ Drawable icon = mMenu.getContext().getDrawable(mIconResId);
mIconResId = NO_ICON;
mIconDrawable = icon;
return icon;
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 05e9a66..d664058 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -23,7 +23,6 @@ import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuItem;
-import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java
index f3891c7..183478f 100644
--- a/core/java/com/android/internal/widget/AbsActionBarView.java
+++ b/core/java/com/android/internal/widget/AbsActionBarView.java
@@ -16,8 +16,8 @@
package com.android.internal.widget;
import com.android.internal.R;
-import com.android.internal.view.menu.ActionMenuPresenter;
-import com.android.internal.view.menu.ActionMenuView;
+import android.widget.ActionMenuPresenter;
+import android.widget.ActionMenuView;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -47,15 +47,20 @@ public abstract class AbsActionBarView extends ViewGroup {
private static final int FADE_DURATION = 200;
public AbsActionBarView(Context context) {
- super(context);
+ this(context, null);
}
public AbsActionBarView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
}
- public AbsActionBarView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public AbsActionBarView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AbsActionBarView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
@@ -95,9 +100,6 @@ public abstract class AbsActionBarView extends ViewGroup {
public void setContentHeight(int height) {
mContentHeight = height;
- if (mMenuView != null) {
- mMenuView.setMaxItemHeight(mContentHeight);
- }
requestLayout();
}
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index 8a49899..c2d22dd 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -16,10 +16,10 @@
package com.android.internal.widget;
-import android.app.ActionBar;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.ColorFilter;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ActionMode;
@@ -51,7 +51,8 @@ public class ActionBarContainer extends FrameLayout {
public ActionBarContainer(Context context, AttributeSet attrs) {
super(context, attrs);
- setBackgroundDrawable(null);
+ // Set a transparent background so that we project appropriately.
+ setBackground(new ActionBarBackgroundDrawable());
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.ActionBar);
@@ -243,24 +244,6 @@ public class ActionBarContainer extends FrameLayout {
}
@Override
- public void onDraw(Canvas canvas) {
- if (getWidth() == 0 || getHeight() == 0) {
- return;
- }
-
- if (mIsSplit) {
- if (mSplitBackground != null) mSplitBackground.draw(canvas);
- } else {
- if (mBackground != null) {
- mBackground.draw(canvas);
- }
- if (mStackedBackground != null && mIsStacked) {
- mStackedBackground.draw(canvas);
- }
- }
- }
-
- @Override
public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
// No starting an action mode for an action bar child! (Where would it go?)
return null;
@@ -291,12 +274,13 @@ public class ActionBarContainer extends FrameLayout {
public void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- final boolean hasTabs = mTabContainer != null && mTabContainer.getVisibility() != GONE;
+ final View tabContainer = mTabContainer;
+ final boolean hasTabs = tabContainer != null && tabContainer.getVisibility() != GONE;
- if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
+ if (tabContainer != null && tabContainer.getVisibility() != GONE) {
final int containerHeight = getMeasuredHeight();
- final int tabHeight = mTabContainer.getMeasuredHeight();
- mTabContainer.layout(l, containerHeight - tabHeight, r, containerHeight);
+ final int tabHeight = tabContainer.getMeasuredHeight();
+ tabContainer.layout(l, containerHeight - tabHeight, r, containerHeight);
}
boolean needsInvalidate = false;
@@ -307,13 +291,15 @@ public class ActionBarContainer extends FrameLayout {
}
} else {
if (mBackground != null) {
- mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
- mActionBarView.getRight(), mActionBarView.getBottom());
+ final ActionBarView actionBarView = mActionBarView;
+ mBackground.setBounds(actionBarView.getLeft(), actionBarView.getTop(),
+ actionBarView.getRight(), actionBarView.getBottom());
needsInvalidate = true;
}
- if ((mIsStacked = hasTabs && mStackedBackground != null)) {
- mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(),
- mTabContainer.getRight(), mTabContainer.getBottom());
+ mIsStacked = hasTabs;
+ if (hasTabs && mStackedBackground != null) {
+ mStackedBackground.setBounds(tabContainer.getLeft(), tabContainer.getTop(),
+ tabContainer.getRight(), tabContainer.getBottom());
needsInvalidate = true;
}
}
@@ -322,4 +308,37 @@ public class ActionBarContainer extends FrameLayout {
invalidate();
}
}
+
+ /**
+ * Dummy drawable so that we don't break background display lists and
+ * projection surfaces.
+ */
+ private class ActionBarBackgroundDrawable extends Drawable {
+ @Override
+ public void draw(Canvas canvas) {
+ if (mIsSplit) {
+ if (mSplitBackground != null) mSplitBackground.draw(canvas);
+ } else {
+ if (mBackground != null) {
+ mBackground.draw(canvas);
+ }
+ if (mStackedBackground != null && mIsStacked) {
+ mStackedBackground.draw(canvas);
+ }
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 8bc1081..e10070f 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -16,8 +16,8 @@
package com.android.internal.widget;
import com.android.internal.R;
-import com.android.internal.view.menu.ActionMenuPresenter;
-import com.android.internal.view.menu.ActionMenuView;
+import android.widget.ActionMenuPresenter;
+import android.widget.ActionMenuView;
import com.android.internal.view.menu.MenuBuilder;
import android.animation.Animator;
@@ -25,7 +25,6 @@ import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
@@ -74,10 +73,16 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi
this(context, attrs, com.android.internal.R.attr.actionModeStyle);
}
- public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionMode, defStyle, 0);
+ public ActionBarContextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ActionBarContextView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.ActionMode, defStyleAttr, defStyleRes);
setBackgroundDrawable(a.getDrawable(
com.android.internal.R.styleable.ActionMode_background));
mTitleStyleRes = a.getResourceId(
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index 5469b63..c957b67 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -20,6 +20,7 @@ import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import com.android.internal.app.ActionBarImpl;
import android.content.Context;
@@ -96,7 +97,7 @@ public class ActionBarOverlayLayout extends ViewGroup {
if (mLastSystemUiVisibility != 0) {
int newVis = mLastSystemUiVisibility;
onWindowSystemUiVisibilityChanged(newVis);
- requestFitSystemWindows();
+ requestApplyInsets();
}
}
}
@@ -152,7 +153,7 @@ public class ActionBarOverlayLayout extends ViewGroup {
}
if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
if (mActionBar != null) {
- requestFitSystemWindows();
+ requestApplyInsets();
}
}
}
@@ -190,19 +191,20 @@ public class ActionBarOverlayLayout extends ViewGroup {
}
@Override
- protected boolean fitSystemWindows(Rect insets) {
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
pullChildren();
final int vis = getWindowSystemUiVisibility();
final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
+ final Rect systemInsets = insets.getSystemWindowInsets();
// The top and bottom action bars are always within the content area.
- boolean changed = applyInsets(mActionBarTop, insets, true, true, false, true);
+ boolean changed = applyInsets(mActionBarTop, systemInsets, true, true, false, true);
if (mActionBarBottom != null) {
- changed |= applyInsets(mActionBarBottom, insets, true, false, true, true);
+ changed |= applyInsets(mActionBarBottom, systemInsets, true, false, true, true);
}
- mBaseInnerInsets.set(insets);
+ mBaseInnerInsets.set(systemInsets);
computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets);
if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
changed = true;
@@ -215,9 +217,9 @@ public class ActionBarOverlayLayout extends ViewGroup {
// We don't do any more at this point. To correctly compute the content/inner
// insets in all cases, we need to know the measured size of the various action
- // bar elements. fitSystemWindows() happens before the measure pass, so we can't
+ // bar elements. onApplyWindowInsets() happens before the measure pass, so we can't
// do that here. Instead we will take this up in onMeasure().
- return true;
+ return WindowInsets.EMPTY;
}
@Override
@@ -321,7 +323,7 @@ public class ActionBarOverlayLayout extends ViewGroup {
// the app's fitSystemWindows(). We do this before measuring the content
// view to keep the same semantics as the normal fitSystemWindows() call.
mLastInnerInsets.set(mInnerInsets);
- super.fitSystemWindows(mInnerInsets);
+ mContent.dispatchApplyWindowInsets(new WindowInsets(mInnerInsets));
}
measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 786f5cf..1273c4d 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -41,6 +41,8 @@ import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.ActionMenuPresenter;
+import android.widget.ActionMenuView;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -52,8 +54,6 @@ import android.widget.TextView;
import com.android.internal.R;
import com.android.internal.transition.ActionBarTransition;
import com.android.internal.view.menu.ActionMenuItem;
-import com.android.internal.view.menu.ActionMenuPresenter;
-import com.android.internal.view.menu.ActionMenuView;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuItemImpl;
import com.android.internal.view.menu.MenuPresenter;
@@ -430,6 +430,7 @@ public class ActionBarView extends AbsActionBarView {
mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
// Span the whole width
layoutParams.width = LayoutParams.MATCH_PARENT;
+ layoutParams.height = mContentHeight;
configPresenters(builder);
menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
if (mSplitView != null) {
@@ -696,7 +697,7 @@ public class ActionBarView extends AbsActionBarView {
}
public void setIcon(int resId) {
- setIcon(resId != 0 ? mContext.getResources().getDrawable(resId) : null);
+ setIcon(resId != 0 ? mContext.getDrawable(resId) : null);
}
public boolean hasIcon() {
@@ -711,7 +712,7 @@ public class ActionBarView extends AbsActionBarView {
}
public void setLogo(int resId) {
- setLogo(resId != 0 ? mContext.getResources().getDrawable(resId) : null);
+ setLogo(resId != 0 ? mContext.getDrawable(resId) : null);
}
public boolean hasLogo() {
@@ -1416,7 +1417,7 @@ public class ActionBarView extends AbsActionBarView {
public void setUpIndicator(int resId) {
mUpIndicatorRes = resId;
- mUpView.setImageDrawable(resId != 0 ? getResources().getDrawable(resId) : null);
+ mUpView.setImageDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
}
@Override
diff --git a/core/java/com/android/internal/widget/DialogTitle.java b/core/java/com/android/internal/widget/DialogTitle.java
index b86c438..7ea3d6b 100644
--- a/core/java/com/android/internal/widget/DialogTitle.java
+++ b/core/java/com/android/internal/widget/DialogTitle.java
@@ -28,10 +28,13 @@ import android.widget.TextView;
* the text to the available space.
*/
public class DialogTitle extends TextView {
-
- public DialogTitle(Context context, AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);
+
+ public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
}
public DialogTitle(Context context, AttributeSet attrs) {
diff --git a/core/java/com/android/internal/widget/FaceUnlockView.java b/core/java/com/android/internal/widget/FaceUnlockView.java
index e3c1247..121e601 100644
--- a/core/java/com/android/internal/widget/FaceUnlockView.java
+++ b/core/java/com/android/internal/widget/FaceUnlockView.java
@@ -18,8 +18,6 @@ package com.android.internal.widget;
import android.content.Context;
import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
import android.widget.RelativeLayout;
public class FaceUnlockView extends RelativeLayout {
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 8602260..2d79491 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -24,13 +24,13 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.storage.IMountService;
+import android.os.storage.StorageManager;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -499,6 +499,13 @@ public class LockPatternUtils {
getLockSettings().setLockPattern(patternToString(pattern), getCurrentOrCallingUserId());
DevicePolicyManager dpm = getDevicePolicyManager();
if (pattern != null) {
+
+ int userHandle = getCurrentOrCallingUserId();
+ if (userHandle == UserHandle.USER_OWNER) {
+ String stringPattern = patternToString(pattern);
+ updateEncryptionPassword(StorageManager.CRYPT_TYPE_PATTERN, stringPattern);
+ }
+
setBoolean(PATTERN_EVER_CHOSEN_KEY, true);
if (!isFallback) {
deleteGallery();
@@ -566,7 +573,7 @@ public class LockPatternUtils {
}
/** Update the encryption password if it is enabled **/
- private void updateEncryptionPassword(String password) {
+ private void updateEncryptionPassword(int type, String password) {
DevicePolicyManager dpm = getDevicePolicyManager();
if (dpm.getStorageEncryptionStatus(getCurrentOrCallingUserId())
!= DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE) {
@@ -581,7 +588,7 @@ public class LockPatternUtils {
IMountService mountService = IMountService.Stub.asInterface(service);
try {
- mountService.changeEncryptionPassword(password);
+ mountService.changeEncryptionPassword(type, password);
} catch (RemoteException e) {
Log.e(TAG, "Error changing encryption password", e);
}
@@ -624,12 +631,15 @@ public class LockPatternUtils {
getLockSettings().setLockPassword(password, userHandle);
DevicePolicyManager dpm = getDevicePolicyManager();
if (password != null) {
+ int computedQuality = computePasswordQuality(password);
+
if (userHandle == UserHandle.USER_OWNER) {
// Update the encryption password.
- updateEncryptionPassword(password);
+ int type = computedQuality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ ? StorageManager.CRYPT_TYPE_PIN : StorageManager.CRYPT_TYPE_PASSWORD;
+ updateEncryptionPassword(type, password);
}
- int computedQuality = computePasswordQuality(password);
if (!isFallback) {
deleteGallery();
setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
@@ -676,8 +686,7 @@ public class LockPatternUtils {
0, 0, 0, 0, 0, 0, 0, userHandle);
}
// Add the password to the password history. We assume all
- // password
- // hashes have the same length for simplicity of implementation.
+ // password hashes have the same length for simplicity of implementation.
String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle);
if (passwordHistory == null) {
passwordHistory = new String();
@@ -696,6 +705,11 @@ public class LockPatternUtils {
}
setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
} else {
+ if (userHandle == UserHandle.USER_OWNER) {
+ // Update the encryption password.
+ updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, password);
+ }
+
dpm.setActivePasswordState(
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0,
userHandle);
@@ -1201,7 +1215,7 @@ public class LockPatternUtils {
private void setLong(String secureSettingKey, long value, int userHandle) {
try {
- getLockSettings().setLong(secureSettingKey, value, getCurrentOrCallingUserId());
+ getLockSettings().setLong(secureSettingKey, value, userHandle);
} catch (RemoteException re) {
// What can we do?
Log.e(TAG, "Couldn't write long " + secureSettingKey + re);
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index b066d70..26cb4e5 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -868,12 +868,10 @@ public class LockPatternView extends View {
// TODO: the path should be created and cached every time we hit-detect a cell
// only the last segment of the path should be computed here
- // draw the path of the pattern (unless the user is in progress, and
- // we are in stealth mode)
- final boolean drawPath = (!mInStealthMode || mPatternDisplayMode == DisplayMode.Wrong);
+ // draw the path of the pattern (unless we are in stealth mode)
+ final boolean drawPath = !mInStealthMode;
- // draw the arrows associated with the path (unless the user is in progress, and
- // we are in stealth mode)
+ // draw the arrows associated with the path (unless we are in stealth mode)
boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0;
mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms
if (drawPath) {
@@ -974,7 +972,7 @@ public class LockPatternView extends View {
Bitmap outerCircle;
Bitmap innerCircle;
- if (!partOfPattern || (mInStealthMode && mPatternDisplayMode != DisplayMode.Wrong)) {
+ if (!partOfPattern || mInStealthMode) {
// unselected circle
outerCircle = mBitmapCircleDefault;
innerCircle = mBitmapBtnDefault;
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboard.java b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java
index 3c01c69..7483e75 100644
--- a/core/java/com/android/internal/widget/PasswordEntryKeyboard.java
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java
@@ -16,7 +16,6 @@
package com.android.internal.widget;
-import java.util.Locale;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -73,8 +72,8 @@ public class PasswordEntryKeyboard extends Keyboard {
private void init(Context context) {
final Resources res = context.getResources();
- mShiftIcon = res.getDrawable(R.drawable.sym_keyboard_shift);
- mShiftLockIcon = res.getDrawable(R.drawable.sym_keyboard_shift_locked);
+ mShiftIcon = context.getDrawable(R.drawable.sym_keyboard_shift);
+ mShiftLockIcon = context.getDrawable(R.drawable.sym_keyboard_shift_locked);
sSpacebarVerticalCorrection = res.getDimensionPixelOffset(
R.dimen.password_keyboard_spacebar_vertical_correction);
}
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
index a3df291..b2c9dc5 100644
--- a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
@@ -21,9 +21,7 @@ import android.content.res.Resources;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.inputmethodservice.KeyboardView.OnKeyboardActionListener;
-import android.os.Handler;
import android.os.SystemClock;
-import android.os.Vibrator;
import android.provider.Settings;
import android.util.Log;
import android.view.HapticFeedbackConstants;
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java
index b37adff..d27346b 100644
--- a/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java
@@ -29,11 +29,16 @@ public class PasswordEntryKeyboardView extends KeyboardView {
static final int KEYCODE_NEXT_LANGUAGE = -104;
public PasswordEntryKeyboardView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
}
- public PasswordEntryKeyboardView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public PasswordEntryKeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public PasswordEntryKeyboardView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index d82831f..e339c44 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -31,11 +31,13 @@ import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.WindowManagerPolicy.PointerEventListener;
import android.view.MotionEvent.PointerCoords;
import java.util.ArrayList;
-public class PointerLocationView extends View implements InputDeviceListener {
+public class PointerLocationView extends View implements InputDeviceListener,
+ PointerEventListener {
private static final String TAG = "Pointer";
// The system property key used to specify an alternate velocity tracker strategy
@@ -520,7 +522,8 @@ public class PointerLocationView extends View implements InputDeviceListener {
.toString());
}
- public void addPointerEvent(MotionEvent event) {
+ @Override
+ public void onPointerEvent(MotionEvent event) {
final int action = event.getAction();
int NP = mPointers.size();
@@ -648,7 +651,7 @@ public class PointerLocationView extends View implements InputDeviceListener {
@Override
public boolean onTouchEvent(MotionEvent event) {
- addPointerEvent(event);
+ onPointerEvent(event);
if (event.getAction() == MotionEvent.ACTION_DOWN && !isFocused()) {
requestFocus();
@@ -660,7 +663,7 @@ public class PointerLocationView extends View implements InputDeviceListener {
public boolean onGenericMotionEvent(MotionEvent event) {
final int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
- addPointerEvent(event);
+ onPointerEvent(event);
} else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
logMotionEvent("Joystick", event);
} else if ((source & InputDevice.SOURCE_CLASS_POSITION) != 0) {
diff --git a/core/java/com/android/internal/widget/RotarySelector.java b/core/java/com/android/internal/widget/RotarySelector.java
index 4e405f4..f856027 100644
--- a/core/java/com/android/internal/widget/RotarySelector.java
+++ b/core/java/com/android/internal/widget/RotarySelector.java
@@ -24,7 +24,6 @@ import android.graphics.Paint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
-import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index fa29e6e..d6bd1d6 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -23,7 +23,6 @@ import android.animation.TimeInterpolator;
import android.app.ActionBar;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
diff --git a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
index ba113a3..961e471 100644
--- a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
+++ b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
@@ -79,17 +79,20 @@ public class SizeAdaptiveLayout extends ViewGroup {
private int mModestyPanelTop;
public SizeAdaptiveLayout(Context context) {
- super(context);
- initialize();
+ this(context, null);
}
public SizeAdaptiveLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize();
+ this(context, attrs, 0);
+ }
+
+ public SizeAdaptiveLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
}
- public SizeAdaptiveLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public SizeAdaptiveLayout(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
initialize();
}
@@ -151,6 +154,10 @@ public class SizeAdaptiveLayout extends ViewGroup {
if (DEBUG) Log.d(TAG, this + " measure spec: " +
MeasureSpec.toString(heightMeasureSpec));
View model = selectActiveChild(heightMeasureSpec);
+ if (model == null) {
+ setMeasuredDimension(0, 0);
+ return;
+ }
SizeAdaptiveLayout.LayoutParams lp =
(SizeAdaptiveLayout.LayoutParams) model.getLayoutParams();
if (DEBUG) Log.d(TAG, "active min: " + lp.minHeight + " max: " + lp.maxHeight);
@@ -239,6 +246,8 @@ public class SizeAdaptiveLayout extends ViewGroup {
int measureSpec = View.MeasureSpec.makeMeasureSpec(bottom - top,
View.MeasureSpec.EXACTLY);
mActiveChild = selectActiveChild(measureSpec);
+ if (mActiveChild == null) return;
+
mActiveChild.setVisibility(View.VISIBLE);
if (mLastActive != mActiveChild && mLastActive != null) {
diff --git a/core/java/com/android/internal/widget/SubtitleView.java b/core/java/com/android/internal/widget/SubtitleView.java
index 071193c..117463a 100644
--- a/core/java/com/android/internal/widget/SubtitleView.java
+++ b/core/java/com/android/internal/widget/SubtitleView.java
@@ -18,7 +18,6 @@ package com.android.internal.widget;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.res.Resources.Theme;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -33,7 +32,6 @@ import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
-import android.util.TypedValue;
import android.view.View;
import android.view.accessibility.CaptioningManager.CaptionStyle;
@@ -41,6 +39,12 @@ public class SubtitleView extends View {
// Ratio of inner padding to font size.
private static final float INNER_PADDING_RATIO = 0.125f;
+ /** Color used for the shadowed edge of a bevel. */
+ private static final int COLOR_BEVEL_DARK = 0x80000000;
+
+ /** Color used for the illuminated edge of a bevel. */
+ private static final int COLOR_BEVEL_LIGHT = 0x80FFFFFF;
+
// Styled dimensions.
private final float mCornerRadius;
private final float mOutlineWidth;
@@ -79,12 +83,15 @@ public class SubtitleView extends View {
this(context, attrs, 0);
}
- public SubtitleView(Context context, AttributeSet attrs, int defStyle) {
+ public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs);
- final Theme theme = context.getTheme();
- final TypedArray a = theme.obtainStyledAttributes(
- attrs, android.R.styleable.TextView, defStyle, 0);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes);
CharSequence text = "";
int textSize = 15;
@@ -112,7 +119,6 @@ public class SubtitleView extends View {
// Set up density-dependent properties.
// TODO: Move these to a default style.
final Resources res = getContext().getResources();
- final DisplayMetrics m = res.getDisplayMetrics();
mCornerRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_corner_radius);
mOutlineWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_outline_width);
mShadowRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_shadow_radius);
@@ -311,7 +317,8 @@ public class SubtitleView extends View {
}
}
- if (mEdgeType == CaptionStyle.EDGE_TYPE_OUTLINE) {
+ final int edgeType = mEdgeType;
+ if (edgeType == CaptionStyle.EDGE_TYPE_OUTLINE) {
textPaint.setStrokeJoin(Join.ROUND);
textPaint.setStrokeWidth(mOutlineWidth);
textPaint.setColor(mEdgeColor);
@@ -320,8 +327,24 @@ public class SubtitleView extends View {
for (int i = 0; i < lineCount; i++) {
layout.drawText(c, i, i);
}
- } else if (mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
+ } else if (edgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
textPaint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mEdgeColor);
+ } else if (edgeType == CaptionStyle.EDGE_TYPE_RAISED
+ || edgeType == CaptionStyle.EDGE_TYPE_DEPRESSED) {
+ final boolean raised = edgeType == CaptionStyle.EDGE_TYPE_RAISED;
+ final int colorUp = raised ? Color.WHITE : mEdgeColor;
+ final int colorDown = raised ? mEdgeColor : Color.WHITE;
+ final float offset = mShadowRadius / 2f;
+
+ textPaint.setColor(mForegroundColor);
+ textPaint.setStyle(Style.FILL);
+ textPaint.setShadowLayer(mShadowRadius, -offset, -offset, colorUp);
+
+ for (int i = 0; i < lineCount; i++) {
+ layout.drawText(c, i, i);
+ }
+
+ textPaint.setShadowLayer(mShadowRadius, offset, offset, colorDown);
}
textPaint.setColor(mForegroundColor);
diff --git a/core/java/com/android/internal/widget/TextProgressBar.java b/core/java/com/android/internal/widget/TextProgressBar.java
index e898aa4..7ca07d4 100644
--- a/core/java/com/android/internal/widget/TextProgressBar.java
+++ b/core/java/com/android/internal/widget/TextProgressBar.java
@@ -19,7 +19,6 @@ package com.android.internal.widget;
import android.content.Context;
import android.os.SystemClock;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -59,9 +58,13 @@ public class TextProgressBar extends RelativeLayout implements OnChronometerTick
boolean mChronometerFollow = false;
int mChronometerGravity = Gravity.NO_GRAVITY;
+
+ public TextProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
- public TextProgressBar(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public TextProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
}
public TextProgressBar(Context context, AttributeSet attrs) {
diff --git a/core/java/com/android/internal/widget/WaveView.java b/core/java/com/android/internal/widget/WaveView.java
index d33d50c..0c5993b 100644
--- a/core/java/com/android/internal/widget/WaveView.java
+++ b/core/java/com/android/internal/widget/WaveView.java
@@ -28,7 +28,6 @@ import android.graphics.drawable.BitmapDrawable;
import android.os.UserHandle;
import android.os.Vibrator;
import android.provider.Settings;
-import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
diff --git a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
index cd1ccd3..93ea5b3 100644
--- a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
+++ b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
@@ -234,7 +234,7 @@ public class GlowPadView extends View {
mMagneticTargets = a.getBoolean(R.styleable.GlowPadView_magneticTargets, mMagneticTargets);
int pointId = getResourceId(a, R.styleable.GlowPadView_pointDrawable);
- Drawable pointDrawable = pointId != 0 ? res.getDrawable(pointId) : null;
+ Drawable pointDrawable = pointId != 0 ? context.getDrawable(pointId) : null;
mGlowRadius = a.getDimension(R.styleable.GlowPadView_glowRadius, 0.0f);
TypedValue outValue = new TypedValue();
diff --git a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
index 16bec16..5a4c441 100644
--- a/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
+++ b/core/java/com/android/internal/widget/multiwaveview/TargetDrawable.java
@@ -18,7 +18,6 @@ package com.android.internal.widget.multiwaveview;
import android.content.res.Resources;
import android.graphics.Canvas;
-import android.graphics.ColorFilter;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.util.Log;