summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java185
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java35
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl5
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl5
-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/alsa/AlsaCardsParser.java116
-rw-r--r--core/java/android/alsa/AlsaDevicesParser.java275
-rw-r--r--core/java/android/alsa/LineTokenizer.java57
-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/RevealAnimator.java141
-rw-r--r--core/java/android/animation/TypeConverter.java68
-rw-r--r--core/java/android/animation/ValueAnimator.java55
-rw-r--r--core/java/android/annotation/AnimRes.java37
-rw-r--r--core/java/android/annotation/AnimatorRes.java37
-rw-r--r--core/java/android/annotation/AnyRes.java39
-rw-r--r--core/java/android/annotation/ArrayRes.java37
-rw-r--r--core/java/android/annotation/AttrRes.java37
-rw-r--r--core/java/android/annotation/BoolRes.java37
-rw-r--r--core/java/android/annotation/ColorRes.java37
-rw-r--r--core/java/android/annotation/DimenRes.java37
-rw-r--r--core/java/android/annotation/DrawableRes.java37
-rw-r--r--core/java/android/annotation/FractionRes.java37
-rw-r--r--core/java/android/annotation/IdRes.java37
-rw-r--r--core/java/android/annotation/IntDef.java60
-rw-r--r--core/java/android/annotation/IntegerRes.java37
-rw-r--r--core/java/android/annotation/InterpolatorRes.java37
-rw-r--r--core/java/android/annotation/LayoutRes.java37
-rw-r--r--core/java/android/annotation/MenuRes.java37
-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/PluralsRes.java37
-rw-r--r--core/java/android/annotation/RawRes.java37
-rw-r--r--core/java/android/annotation/StringDef.java51
-rw-r--r--core/java/android/annotation/StringRes.java37
-rw-r--r--core/java/android/annotation/StyleRes.java37
-rw-r--r--core/java/android/annotation/StyleableRes.java37
-rw-r--r--core/java/android/annotation/XmlRes.java37
-rw-r--r--core/java/android/app/ActionBar.java254
-rw-r--r--core/java/android/app/Activity.java378
-rw-r--r--core/java/android/app/ActivityManager.java99
-rw-r--r--core/java/android/app/ActivityManagerNative.java196
-rw-r--r--core/java/android/app/ActivityOptions.java379
-rw-r--r--core/java/android/app/ActivityThread.java58
-rw-r--r--core/java/android/app/AlertDialog.java28
-rw-r--r--core/java/android/app/AppOpsManager.java48
-rw-r--r--core/java/android/app/ApplicationErrorReport.java1
-rw-r--r--core/java/android/app/ApplicationPackageManager.java55
-rw-r--r--core/java/android/app/ApplicationThreadNative.java23
-rw-r--r--core/java/android/app/ContextImpl.java74
-rw-r--r--core/java/android/app/Dialog.java28
-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.java34
-rw-r--r--core/java/android/app/IApplicationThread.java9
-rw-r--r--core/java/android/app/IBackupAgent.aidl20
-rw-r--r--core/java/android/app/INotificationManager.aidl2
-rw-r--r--core/java/android/app/IUiAutomationConnection.aidl8
-rw-r--r--core/java/android/app/LoadedApk.java98
-rw-r--r--core/java/android/app/MediaRouteButton.java11
-rw-r--r--core/java/android/app/Notification.java323
-rw-r--r--core/java/android/app/OnActivityPausedListener.java2
-rw-r--r--core/java/android/app/PackageInstallObserver.java49
-rw-r--r--core/java/android/app/PendingIntent.java65
-rw-r--r--core/java/android/app/ResourcesManager.java19
-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/UiAutomation.java205
-rw-r--r--core/java/android/app/UiAutomationConnection.java76
-rw-r--r--core/java/android/app/WallpaperManager.java22
-rw-r--r--core/java/android/app/admin/DeviceAdminInfo.java16
-rw-r--r--core/java/android/app/admin/DeviceAdminReceiver.java83
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java205
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl9
-rw-r--r--core/java/android/app/backup/BackupAgent.java32
-rw-r--r--core/java/android/app/backup/BackupDataOutput.java4
-rw-r--r--core/java/android/app/backup/FullBackup.java3
-rw-r--r--core/java/android/app/backup/IBackupManager.aidl2
-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/app/trust/ITrustListener.aidl (renamed from core/java/android/view/IMagnificationCallbacks.aidl)19
-rw-r--r--core/java/android/app/trust/ITrustManager.aidl31
-rw-r--r--core/java/android/app/trust/TrustManager.java134
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java2
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java87
-rw-r--r--core/java/android/appwidget/AppWidgetProvider.java51
-rw-r--r--core/java/android/appwidget/AppWidgetProviderInfo.java2
-rw-r--r--core/java/android/bluetooth/BluetoothA2dp.java3
-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.java11
-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/BluetoothHeadset.java3
-rw-r--r--core/java/android/bluetooth/BluetoothHealth.java4
-rw-r--r--core/java/android/bluetooth/BluetoothInputDevice.java4
-rw-r--r--core/java/android/bluetooth/BluetoothMap.java7
-rw-r--r--core/java/android/bluetooth/BluetoothPan.java4
-rw-r--r--core/java/android/bluetooth/BluetoothPbap.java4
-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.java308
-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.java131
-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.java34
-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/ILauncherApps.aidl38
-rw-r--r--core/java/android/content/pm/IOnAppsChangedListener.aidl30
-rw-r--r--core/java/android/content/pm/IPackageInstallObserver2.aidl45
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl20
-rw-r--r--core/java/android/content/pm/LauncherActivityInfo.java146
-rw-r--r--core/java/android/content/pm/LauncherApps.java286
-rw-r--r--core/java/android/content/pm/PackageInfo.java21
-rw-r--r--core/java/android/content/pm/PackageManager.java162
-rw-r--r--core/java/android/content/pm/PackageParser.java36
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java25
-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.java21
-rw-r--r--core/java/android/content/res/ColorStateList.java90
-rw-r--r--core/java/android/content/res/Resources.java656
-rw-r--r--core/java/android/content/res/TypedArray.java421
-rw-r--r--core/java/android/content/res/XmlBlock.java10
-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.java1155
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java27
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java11
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java1250
-rw-r--r--core/java/android/hardware/camera2/CaptureFailure.java2
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java1246
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java2186
-rw-r--r--core/java/android/hardware/camera2/CaptureResultExtras.aidl20
-rw-r--r--core/java/android/hardware/camera2/CaptureResultExtras.java90
-rw-r--r--core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl8
-rw-r--r--core/java/android/hardware/camera2/ICameraDeviceUser.aidl12
-rw-r--r--core/java/android/hardware/camera2/LongParcelable.aidl20
-rw-r--r--core/java/android/hardware/camera2/LongParcelable.java74
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDevice.java296
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java151
-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.java67
-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.java33
-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.java127
-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.java53
-rw-r--r--core/java/android/net/IConnectivityManager.aidl2
-rw-r--r--core/java/android/net/INetworkManagementEventObserver.aidl3
-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/NetworkKey.aidl19
-rw-r--r--core/java/android/net/NetworkKey.java105
-rw-r--r--core/java/android/net/NetworkScoreManager.java118
-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/RssiCurve.java129
-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/ScoredNetwork.aidl19
-rw-r--r--core/java/android/net/ScoredNetwork.java99
-rw-r--r--core/java/android/net/SntpClient.java1
-rw-r--r--core/java/android/net/WifiKey.java106
-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.java84
-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/BatteryManager.java34
-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.java116
-rw-r--r--core/java/android/os/BatteryStats.java2080
-rw-r--r--core/java/android/os/Broadcaster.java8
-rw-r--r--core/java/android/os/Build.java13
-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.java195
-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.aidl11
-rw-r--r--core/java/android/os/IUserManager.aidl2
-rw-r--r--core/java/android/os/IVibratorService.aidl4
-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.java22
-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.java45
-rw-r--r--core/java/android/os/Registrant.java1
-rw-r--r--core/java/android/os/RegistrantList.java2
-rw-r--r--core/java/android/os/SystemProperties.java2
-rw-r--r--core/java/android/os/SystemService.java2
-rw-r--r--core/java/android/os/SystemVibrator.java25
-rw-r--r--core/java/android/os/Trace.java2
-rw-r--r--core/java/android/os/UserManager.java146
-rw-r--r--core/java/android/os/Vibrator.java74
-rw-r--r--core/java/android/os/storage/IMountService.java171
-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.java77
-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/PreferenceGroupAdapter.java16
-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/print/PrintManager.java11
-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.java24
-rw-r--r--core/java/android/provider/SearchIndexableData.java171
-rw-r--r--core/java/android/provider/SearchIndexableResource.java79
-rw-r--r--core/java/android/provider/SearchIndexablesContract.java265
-rw-r--r--core/java/android/provider/SearchIndexablesProvider.java189
-rw-r--r--core/java/android/provider/Settings.java189
-rw-r--r--core/java/android/provider/TvContract.java449
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java53
-rw-r--r--core/java/android/service/notification/StatusBarNotification.java40
-rw-r--r--core/java/android/service/textservice/SpellCheckerService.java1
-rw-r--r--core/java/android/service/trust/ITrustAgentService.aidl28
-rw-r--r--core/java/android/service/trust/ITrustAgentServiceCallback.aidl28
-rw-r--r--core/java/android/service/trust/TrustAgentService.java148
-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.java33
-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/DynamicLayout.java17
-rw-r--r--core/java/android/text/Html.java5
-rw-r--r--core/java/android/text/Layout.java12
-rw-r--r--core/java/android/text/MeasuredText.java6
-rw-r--r--core/java/android/text/PackedIntVector.java7
-rw-r--r--core/java/android/text/PackedObjectVector.java15
-rw-r--r--core/java/android/text/SpannableStringBuilder.java77
-rw-r--r--core/java/android/text/SpannableStringInternal.java14
-rw-r--r--core/java/android/text/StaticLayout.java29
-rw-r--r--core/java/android/text/TextLine.java2
-rw-r--r--core/java/android/text/TextUtils.java2
-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/CircularPropagation.java103
-rw-r--r--core/java/android/transition/Explode.java228
-rw-r--r--core/java/android/transition/Fade.java258
-rw-r--r--core/java/android/transition/MatrixClippedDrawable.java300
-rw-r--r--core/java/android/transition/MoveImage.java326
-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/SidePropagation.java165
-rw-r--r--core/java/android/transition/Slide.java243
-rw-r--r--core/java/android/transition/Transition.java244
-rw-r--r--core/java/android/transition/TransitionInflater.java37
-rw-r--r--core/java/android/transition/TransitionManager.java56
-rw-r--r--core/java/android/transition/TransitionPropagation.java88
-rw-r--r--core/java/android/transition/TransitionSet.java41
-rw-r--r--core/java/android/transition/Visibility.java200
-rw-r--r--core/java/android/transition/VisibilityPropagation.java112
-rw-r--r--core/java/android/tv/ITvInputClient.aidl30
-rw-r--r--core/java/android/tv/ITvInputManager.aidl43
-rw-r--r--core/java/android/tv/ITvInputService.aidl31
-rw-r--r--core/java/android/tv/ITvInputServiceCallback.aidl28
-rw-r--r--core/java/android/tv/ITvInputSession.aidl34
-rw-r--r--core/java/android/tv/ITvInputSessionCallback.aidl28
-rw-r--r--core/java/android/tv/ITvInputSessionWrapper.java100
-rw-r--r--core/java/android/tv/TvInputInfo.aidl19
-rw-r--r--core/java/android/tv/TvInputInfo.java152
-rw-r--r--core/java/android/tv/TvInputManager.java392
-rw-r--r--core/java/android/tv/TvInputService.java237
-rw-r--r--core/java/android/util/ArrayMap.java22
-rw-r--r--core/java/android/util/ArraySet.java18
-rw-r--r--core/java/android/util/ContainerHelpers.java4
-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/LongSparseArray.java57
-rw-r--r--core/java/android/util/LongSparseLongArray.java49
-rw-r--r--core/java/android/util/LruCache.java3
-rw-r--r--core/java/android/util/Patterns.java51
-rw-r--r--core/java/android/util/Slog.java5
-rw-r--r--core/java/android/util/SparseArray.java57
-rw-r--r--core/java/android/util/SparseBooleanArray.java69
-rw-r--r--core/java/android/util/SparseIntArray.java57
-rw-r--r--core/java/android/util/SparseLongArray.java49
-rw-r--r--core/java/android/view/AccessibilityInteractionController.java164
-rw-r--r--core/java/android/view/AccessibilityIterators.java2
-rw-r--r--core/java/android/view/AnimationRenderStats.aidl19
-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.java686
-rw-r--r--core/java/android/view/FrameStats.java97
-rw-r--r--core/java/android/view/GLES20Canvas.java180
-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.java1566
-rw-r--r--core/java/android/view/HapticFeedbackConstants.java5
-rw-r--r--core/java/android/view/HardwareCanvas.java82
-rw-r--r--core/java/android/view/HardwareLayer.java347
-rw-r--r--core/java/android/view/HardwareRenderer.java1771
-rw-r--r--core/java/android/view/IWindowManager.aidl51
-rw-r--r--core/java/android/view/InputQueue.java1
-rw-r--r--core/java/android/view/KeyEvent.java180
-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/RenderNode.java900
-rw-r--r--core/java/android/view/Surface.java10
-rw-r--r--core/java/android/view/SurfaceControl.java87
-rw-r--r--core/java/android/view/SurfaceView.java14
-rw-r--r--core/java/android/view/TextureView.java68
-rw-r--r--core/java/android/view/ThreadedRenderer.java313
-rw-r--r--core/java/android/view/View.java1847
-rw-r--r--core/java/android/view/ViewConfiguration.java2
-rw-r--r--core/java/android/view/ViewGroup.java159
-rw-r--r--core/java/android/view/ViewOverlay.java15
-rw-r--r--core/java/android/view/ViewPropertyAnimator.java113
-rw-r--r--core/java/android/view/ViewRootImpl.java379
-rw-r--r--core/java/android/view/ViewStub.java13
-rw-r--r--core/java/android/view/VolumePanel.java197
-rw-r--r--core/java/android/view/Window.java145
-rw-r--r--core/java/android/view/WindowAnimationFrameStats.aidl19
-rw-r--r--core/java/android/view/WindowAnimationFrameStats.java94
-rw-r--r--core/java/android/view/WindowContentFrameStats.aidl19
-rw-r--r--core/java/android/view/WindowContentFrameStats.java152
-rw-r--r--core/java/android/view/WindowInfo.aidl19
-rw-r--r--core/java/android/view/WindowInfo.java164
-rw-r--r--core/java/android/view/WindowManager.java17
-rw-r--r--core/java/android/view/WindowManagerInternal.java133
-rw-r--r--core/java/android/view/WindowManagerPolicy.java42
-rw-r--r--core/java/android/view/accessibility/AccessibilityCache.java467
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java90
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java161
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java332
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java422
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfoCache.java329
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeProvider.java13
-rw-r--r--core/java/android/view/accessibility/AccessibilityRecord.aidl19
-rw-r--r--core/java/android/view/accessibility/AccessibilityRecord.java4
-rw-r--r--core/java/android/view/accessibility/AccessibilityWindowInfo.aidl19
-rw-r--r--core/java/android/view/accessibility/AccessibilityWindowInfo.java574
-rw-r--r--core/java/android/view/accessibility/CaptioningManager.java39
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl2
-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/ClipRectAnimation.java59
-rw-r--r--core/java/android/view/animation/PathInterpolator.java203
-rw-r--r--core/java/android/view/animation/Transformation.java53
-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.java83
-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/PermissionRequest.java74
-rw-r--r--core/java/android/webkit/Plugin.java1
-rw-r--r--core/java/android/webkit/WebChromeClient.java24
-rw-r--r--core/java/android/webkit/WebResourceResponse.java2
-rw-r--r--core/java/android/webkit/WebView.java96
-rw-r--r--core/java/android/webkit/WebViewFactory.java2
-rw-r--r--core/java/android/webkit/WebViewProvider.java3
-rw-r--r--core/java/android/widget/AbsListView.java1552
-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.java746
-rw-r--r--core/java/android/widget/ActionMenuView.java696
-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.java71
-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.java16
-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.java58
-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.java15
-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/RemoteViews.java94
-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.java29
-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.java27
-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.java6
-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/Toolbar.java1048
-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
593 files changed, 47457 insertions, 14907 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 1e3d5be..2620c44 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -23,14 +23,18 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Log;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
import com.android.internal.os.HandlerCaller;
+import java.util.List;
+
/**
* An accessibility service runs in the background and receives callbacks by the system
* when {@link AccessibilityEvent}s are fired. Such events denote some state transition
@@ -180,28 +184,37 @@ import com.android.internal.os.HandlerCaller;
* event generation has settled down.</p>
* <h3>Event types</h3>
* <ul>
- * <li>{@link AccessibilityEvent#TYPE_VIEW_CLICKED}
- * <li>{@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED}
- * <li>{@link AccessibilityEvent#TYPE_VIEW_FOCUSED}
- * <li>{@link AccessibilityEvent#TYPE_VIEW_SELECTED}
- * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED}
- * <li>{@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}
- * <li>{@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED}
- * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START}
- * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END}
- * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}
- * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}
- * <li>{@link AccessibilityEvent#TYPE_VIEW_SCROLLED}
- * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED}
- * <li>{@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_CLICKED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_FOCUSED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_SELECTED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START}</li>
+ * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_SCROLLED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_ANNOUNCEMENT}</li>
+ * <li>{@link AccessibilityEvent#TYPE_GESTURE_DETECTION_START}</li>
+ * <li>{@link AccessibilityEvent#TYPE_GESTURE_DETECTION_END}</li>
+ * <li>{@link AccessibilityEvent#TYPE_TOUCH_INTERACTION_START}</li>
+ * <li>{@link AccessibilityEvent#TYPE_TOUCH_INTERACTION_END}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_WINDOWS_CHANGED}</li>
+ * <li>{@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED}</li>
* </ul>
* <h3>Feedback types</h3>
* <ul>
- * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}
- * <li>{@link AccessibilityServiceInfo#FEEDBACK_HAPTIC}
- * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}
- * <li>{@link AccessibilityServiceInfo#FEEDBACK_VISUAL}
- * <li>{@link AccessibilityServiceInfo#FEEDBACK_GENERIC}
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_HAPTIC}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_VISUAL}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_GENERIC}</li>
+ * <li>{@link AccessibilityServiceInfo#FEEDBACK_BRAILLE}</li>
* </ul>
* @see AccessibilityEvent
* @see AccessibilityServiceInfo
@@ -443,8 +456,41 @@ public abstract class AccessibilityService extends Service {
}
/**
+ * Gets the windows on the screen. This method returns only the windows
+ * that a sighted user can interact with, as opposed to all windows.
+ * For example, if there is a modal dialog shown and the user cannot touch
+ * anything behind it, then only the modal window will be reported
+ * (assuming it is the top one). For convenience the returned windows
+ * are ordered in a descending layer order, which is the windows that
+ * are higher in the Z-order are reported first.
+ * <p>
+ * <strong>Note:</strong> In order to access the windows your service has
+ * to declare the capability to retrieve window content by setting the
+ * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * Also the service has to opt-in to retrieve the interactive windows by
+ * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}
+ * flag.
+ * </p>
+ *
+ * @return The windows if there are windows and the service is can retrieve
+ * them, otherwise an empty list.
+ */
+ public List<AccessibilityWindowInfo> getWindows() {
+ return AccessibilityInteractionClient.getInstance().getWindows(mConnectionId);
+ }
+
+ /**
* Gets the root node in the currently active window if this service
- * can retrieve window content.
+ * can retrieve window content. The active window is the one that the user
+ * is currently touching or the window with input focus, if the user is not
+ * touching any window.
+ * <p>
+ * <strong>Note:</strong> In order to access the root node your service has
+ * to declare the capability to retrieve window content by setting the
+ * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * </p>
*
* @return The root node if this service can retrieve window content.
*/
@@ -480,6 +526,31 @@ public abstract class AccessibilityService extends Service {
}
/**
+ * Find the view that has the specified focus type. The search is performed
+ * across all windows.
+ * <p>
+ * <strong>Note:</strong> In order to access the windows your service has
+ * to declare the capability to retrieve window content by setting the
+ * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * Also the service has to opt-in to retrieve the interactive windows by
+ * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}
+ * flag.Otherwise, the search will be performed only in the active window.
+ * </p>
+ *
+ * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
+ * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
+ * @return The node info of the focused view or null.
+ *
+ * @see AccessibilityNodeInfo#FOCUS_INPUT
+ * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY
+ */
+ public AccessibilityNodeInfo findFocus(int focus) {
+ return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId,
+ AccessibilityNodeInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus);
+ }
+
+ /**
* Gets the an {@link AccessibilityServiceInfo} describing this
* {@link AccessibilityService}. This method is useful if one wants
* to change some of the dynamically configurable properties at
@@ -584,12 +655,13 @@ public abstract class AccessibilityService extends Service {
static final int NO_ID = -1;
- private static final int DO_SET_SET_CONNECTION = 10;
- private static final int DO_ON_INTERRUPT = 20;
- private static final int DO_ON_ACCESSIBILITY_EVENT = 30;
- private static final int DO_ON_GESTURE = 40;
- private static final int DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 50;
- private static final int DO_ON_KEY_EVENT = 60;
+ private static final int DO_SET_SET_CONNECTION = 1;
+ private static final int DO_ON_INTERRUPT = 2;
+ private static final int DO_ON_ACCESSIBILITY_EVENT = 3;
+ private static final int DO_ON_GESTURE = 4;
+ private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5;
+ private static final int DO_ON_KEY_EVENT = 6;
+ private static final int DO_ON_WINDOWS_CHANGED = 7;
private final HandlerCaller mCaller;
@@ -624,8 +696,8 @@ public abstract class AccessibilityService extends Service {
mCaller.sendMessage(message);
}
- public void clearAccessibilityNodeInfoCache() {
- Message message = mCaller.obtainMessage(DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE);
+ public void clearAccessibilityCache() {
+ Message message = mCaller.obtainMessage(DO_CLEAR_ACCESSIBILITY_CACHE);
mCaller.sendMessage(message);
}
@@ -635,6 +707,13 @@ public abstract class AccessibilityService extends Service {
mCaller.sendMessage(message);
}
+ @Override
+ public void onWindowsChanged(int[] windowIds) {
+ Message message = mCaller.obtainMessageO(DO_ON_WINDOWS_CHANGED, windowIds);
+ mCaller.sendMessage(message);
+ }
+
+ @Override
public void executeMessage(Message message) {
switch (message.what) {
case DO_ON_ACCESSIBILITY_EVENT: {
@@ -642,12 +721,19 @@ public abstract class AccessibilityService extends Service {
if (event != null) {
AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event);
mCallback.onAccessibilityEvent(event);
- event.recycle();
+ // Make sure the event is recycled.
+ try {
+ event.recycle();
+ } catch (IllegalStateException ise) {
+ /* ignore - best effort */
+ }
}
} return;
+
case DO_ON_INTERRUPT: {
mCallback.onInterrupt();
} return;
+
case DO_SET_SET_CONNECTION: {
mConnectionId = message.arg1;
IAccessibilityServiceConnection connection =
@@ -658,18 +744,22 @@ public abstract class AccessibilityService extends Service {
mCallback.onSetConnectionId(mConnectionId);
mCallback.onServiceConnected();
} else {
- AccessibilityInteractionClient.getInstance().removeConnection(mConnectionId);
+ AccessibilityInteractionClient.getInstance().removeConnection(
+ mConnectionId);
AccessibilityInteractionClient.getInstance().clearCache();
mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID);
}
} return;
+
case DO_ON_GESTURE: {
final int gestureId = message.arg1;
mCallback.onGesture(gestureId);
} return;
- case DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: {
+
+ case DO_CLEAR_ACCESSIBILITY_CACHE: {
AccessibilityInteractionClient.getInstance().clearCache();
} return;
+
case DO_ON_KEY_EVENT: {
KeyEvent event = (KeyEvent) message.obj;
try {
@@ -685,9 +775,40 @@ public abstract class AccessibilityService extends Service {
}
}
} finally {
- event.recycle();
+ // Make sure the event is recycled.
+ try {
+ event.recycle();
+ } catch (IllegalStateException ise) {
+ /* ignore - best effort */
+ }
}
} return;
+
+ case DO_ON_WINDOWS_CHANGED: {
+ final int[] windowIds = (int[]) message.obj;
+
+ // Update the cached windows first.
+ // NOTE: The cache will hold on to the windows so do not recycle.
+ if (windowIds != null) {
+ AccessibilityInteractionClient.getInstance().removeWindows(windowIds);
+ }
+
+ // Let the client know the windows changed.
+ AccessibilityEvent event = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_WINDOWS_CHANGED);
+ event.setEventTime(SystemClock.uptimeMillis());
+ event.setSealed(true);
+
+ mCallback.onAccessibilityEvent(event);
+
+ // Make sure the event is recycled.
+ try {
+ event.recycle();
+ } catch (IllegalStateException ise) {
+ /* ignore - best effort */
+ }
+ } break;
+
default :
Log.w(LOG_TAG, "Unknown message type " + message.what);
}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index bdc4fdd..4f9ba59 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -284,6 +284,27 @@ public class AccessibilityServiceInfo implements Parcelable {
public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 0x00000020;
/**
+ * This flag indicates to the system that the accessibility service wants
+ * to access content of all interactive windows. An interactive window is a
+ * window that can be touched by a sighted user when explore by touch is not
+ * enabled. If this flag is not set your service will not receive
+ * {@link android.view.accessibility.AccessibilityEvent#TYPE_WINDOWS_CHANGED}
+ * events, calling AccessibilityService{@link AccessibilityService#getWindows()
+ * AccessibilityService.getWindows()} will return an empty list, and {@link
+ * AccessibilityNodeInfo#getWindow() AccessibilityNodeInfo.getWindow()} will
+ * return null.
+ * <p>
+ * Services that want to set this flag have to declare the capability
+ * to retrieve window content in their meta-data by setting the attribute
+ * {@link android.R.attr#canRetrieveWindowContent canRetrieveWindowContent} to
+ * true, otherwise this flag will be ignored. For how to declare the meta-data
+ * of a service refer to {@value AccessibilityService#SERVICE_META_DATA}.
+ * </p>
+ * @see android.R.styleable#AccessibilityService_canRetrieveWindowContent
+ */
+ public static final int FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 0x00000040;
+
+ /**
* The event types an {@link AccessibilityService} is interested in.
* <p>
* <strong>Can be dynamically set at runtime.</strong>
@@ -302,6 +323,15 @@ public class AccessibilityServiceInfo implements Parcelable {
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SCROLLED
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED
* @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_INTERACTION_START
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_TOUCH_INTERACTION_END
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_ANNOUNCEMENT
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_GESTURE_DETECTION_START
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_GESTURE_DETECTION_END
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOWS_CHANGED
*/
public int eventTypes;
@@ -354,6 +384,7 @@ public class AccessibilityServiceInfo implements Parcelable {
* @see #FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY
* @see #FLAG_REQUEST_FILTER_KEY_EVENTS
* @see #FLAG_REPORT_VIEW_IDS
+ * @see #FLAG_RETRIEVE_INTERACTIVE_WINDOWS
*/
public int flags;
@@ -449,7 +480,7 @@ public class AccessibilityServiceInfo implements Parcelable {
com.android.internal.R.styleable.AccessibilityService_accessibilityFeedbackType,
0);
notificationTimeout = asAttributes.getInt(
- com.android.internal.R.styleable.AccessibilityService_notificationTimeout,
+ com.android.internal.R.styleable.AccessibilityService_notificationTimeout,
0);
flags = asAttributes.getInt(
com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0);
@@ -861,6 +892,8 @@ public class AccessibilityServiceInfo implements Parcelable {
return "FLAG_REPORT_VIEW_IDS";
case FLAG_REQUEST_FILTER_KEY_EVENTS:
return "FLAG_REQUEST_FILTER_KEY_EVENTS";
+ case FLAG_RETRIEVE_INTERACTIVE_WINDOWS:
+ return "FLAG_RETRIEVE_INTERACTIVE_WINDOWS";
default:
return null;
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index c5e3d43a..edd8727 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -18,6 +18,7 @@ package android.accessibilityservice;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityWindowInfo;
import android.view.KeyEvent;
/**
@@ -35,7 +36,9 @@ import android.view.KeyEvent;
void onGesture(int gesture);
- void clearAccessibilityNodeInfoCache();
+ void clearAccessibilityCache();
void onKeyEvent(in KeyEvent event, int sequence);
+
+ void onWindowsChanged(in int[] changedWindowIds);
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 3df06b5..5f7a17d 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -21,6 +21,7 @@ import android.accessibilityservice.AccessibilityServiceInfo;
import android.view.MagnificationSpec;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.view.accessibility.AccessibilityWindowInfo;
/**
* Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService.
@@ -53,6 +54,10 @@ interface IAccessibilityServiceConnection {
int action, in Bundle arguments, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long threadId);
+ AccessibilityWindowInfo getWindow(int windowId);
+
+ List<AccessibilityWindowInfo> getWindows();
+
AccessibilityServiceInfo getServiceInfo();
boolean performGlobalAction(int action);
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/alsa/AlsaCardsParser.java b/core/java/android/alsa/AlsaCardsParser.java
new file mode 100644
index 0000000..f9af979
--- /dev/null
+++ b/core/java/android/alsa/AlsaCardsParser.java
@@ -0,0 +1,116 @@
+/*
+ * 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 com.android.alsascan;
+
+import android.util.Slog;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Vector;
+
+/**
+ * @hide Retrieves information from an ALSA "cards" file.
+ */
+public class AlsaCardsParser {
+ private static final String TAG = "AlsaCardsParser";
+
+ private static LineTokenizer tokenizer_ = new LineTokenizer(" :[]");
+
+ public class AlsaCardRecord {
+ public int mCardNum = -1;
+ public String mField1 = "";
+ public String mCardName = "";
+ public String mCardDescription = "";
+
+ public AlsaCardRecord() {}
+
+ public boolean parse(String line, int lineIndex) {
+ int tokenIndex = 0;
+ int delimIndex = 0;
+ if (lineIndex == 0) {
+ // line # (skip)
+ tokenIndex = tokenizer_.nextToken(line, tokenIndex);
+ delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);
+
+ // mField1
+ tokenIndex = tokenizer_.nextToken(line, delimIndex);
+ delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);
+ mField1 = line.substring(tokenIndex, delimIndex);
+
+ // mCardName
+ tokenIndex = tokenizer_.nextToken(line, delimIndex);
+ // delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);
+ mCardName = line.substring(tokenIndex);
+ // done
+ } else if (lineIndex == 1) {
+ tokenIndex = tokenizer_.nextToken(line, 0);
+ if (tokenIndex != -1) {
+ mCardDescription = line.substring(tokenIndex);
+ }
+ }
+
+ return true;
+ }
+
+ public String textFormat() {
+ return mCardName + " : " + mCardDescription;
+ }
+ }
+
+ private Vector<AlsaCardRecord> cardRecords_ = new Vector<AlsaCardRecord>();
+
+ public void scan() {
+ cardRecords_.clear();
+ final String cardsFilePath = "/proc/asound/cards";
+ File cardsFile = new File(cardsFilePath);
+ try {
+ FileReader reader = new FileReader(cardsFile);
+ BufferedReader bufferedReader = new BufferedReader(reader);
+ String line = "";
+ while ((line = bufferedReader.readLine()) != null) {
+ AlsaCardRecord cardRecord = new AlsaCardRecord();
+ cardRecord.parse(line, 0);
+ cardRecord.parse(line = bufferedReader.readLine(), 1);
+ cardRecords_.add(cardRecord);
+ }
+ reader.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public AlsaCardRecord getCardRecordAt(int index) {
+ return cardRecords_.get(index);
+ }
+
+ public int getNumCardRecords() {
+ return cardRecords_.size();
+ }
+
+ public void Log() {
+ int numCardRecs = getNumCardRecords();
+ for (int index = 0; index < numCardRecs; ++index) {
+ Slog.w(TAG, "usb:" + getCardRecordAt(index).textFormat());
+ }
+ }
+
+ public AlsaCardsParser() {}
+}
diff --git a/core/java/android/alsa/AlsaDevicesParser.java b/core/java/android/alsa/AlsaDevicesParser.java
new file mode 100644
index 0000000..094c8a2
--- /dev/null
+++ b/core/java/android/alsa/AlsaDevicesParser.java
@@ -0,0 +1,275 @@
+/*
+ * 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 com.android.alsascan;
+
+import android.util.Slog;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Vector;
+
+/**
+ * @hide
+ * Retrieves information from an ALSA "devices" file.
+ */
+public class AlsaDevicesParser {
+ private static final String TAG = "AlsaDevicesParser";
+
+ private static final int kIndex_CardDeviceField = 5;
+ private static final int kStartIndex_CardNum = 6;
+ private static final int kEndIndex_CardNum = 8; // one past
+ private static final int kStartIndex_DeviceNum = 9;
+ private static final int kEndIndex_DeviceNum = 11; // one past
+ private static final int kStartIndex_Type = 14;
+
+ private static LineTokenizer mTokenizer = new LineTokenizer(" :[]-");
+
+ private boolean mHasCaptureDevices = false;
+ private boolean mHasPlaybackDevices = false;
+ private boolean mHasMIDIDevices = false;
+
+ public class AlsaDeviceRecord {
+ public static final int kDeviceType_Unknown = -1;
+ public static final int kDeviceType_Audio = 0;
+ public static final int kDeviceType_Control = 1;
+ public static final int kDeviceType_MIDI = 2;
+
+ public static final int kDeviceDir_Unknown = -1;
+ public static final int kDeviceDir_Capture = 0;
+ public static final int kDeviceDir_Playback = 1;
+
+ int mCardNum = -1;
+ int mDeviceNum = -1;
+ int mDeviceType = kDeviceType_Unknown;
+ int mDeviceDir = kDeviceDir_Unknown;
+
+ public AlsaDeviceRecord() {
+ }
+
+ public boolean parse(String line) {
+ // "0123456789012345678901234567890"
+ // " 2: [ 0-31]: digital audio playback"
+ // " 3: [ 0-30]: digital audio capture"
+ // " 35: [ 1] : control"
+ // " 36: [ 2- 0]: raw midi"
+
+ final int kToken_LineNum = 0;
+ final int kToken_CardNum = 1;
+ final int kToken_DeviceNum = 2;
+ final int kToken_Type0 = 3; // "digital", "control", "raw"
+ final int kToken_Type1 = 4; // "audio", "midi"
+ final int kToken_Type2 = 5; // "capture", "playback"
+
+ int tokenOffset = 0;
+ int delimOffset = 0;
+ int tokenIndex = kToken_LineNum;
+ while (true) {
+ tokenOffset = mTokenizer.nextToken(line, delimOffset);
+ if (tokenOffset == LineTokenizer.kTokenNotFound) {
+ break; // bail
+ }
+ delimOffset = mTokenizer.nextDelimiter(line, tokenOffset);
+ if (delimOffset == LineTokenizer.kTokenNotFound) {
+ delimOffset = line.length();
+ }
+ String token = line.substring(tokenOffset, delimOffset);
+
+ switch (tokenIndex) {
+ case kToken_LineNum:
+ // ignore
+ break;
+
+ case kToken_CardNum:
+ mCardNum = Integer.parseInt(token);
+ if (line.charAt(delimOffset) != '-') {
+ tokenIndex++; // no device # in the token stream
+ }
+ break;
+
+ case kToken_DeviceNum:
+ mDeviceNum = Integer.parseInt(token);
+ break;
+
+ case kToken_Type0:
+ if (token.equals("digital")) {
+ // NOP
+ } else if (token.equals("control")) {
+ mDeviceType = kDeviceType_Control;
+ } else if (token.equals("raw")) {
+ // NOP
+ }
+ break;
+
+ case kToken_Type1:
+ if (token.equals("audio")) {
+ mDeviceType = kDeviceType_Audio;
+ } else if (token.equals("midi")) {
+ mDeviceType = kDeviceType_MIDI;
+ mHasMIDIDevices = true;
+ }
+ break;
+
+ case kToken_Type2:
+ if (token.equals("capture")) {
+ mDeviceDir = kDeviceDir_Capture;
+ mHasCaptureDevices = true;
+ } else if (token.equals("playback")) {
+ mDeviceDir = kDeviceDir_Playback;
+ mHasPlaybackDevices = true;
+ }
+ break;
+ } // switch (tokenIndex)
+
+ tokenIndex++;
+ } // while (true)
+
+ return true;
+ } // parse()
+
+ public String textFormat() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[" + mCardNum + ":" + mDeviceNum + "]");
+
+ switch (mDeviceType) {
+ case kDeviceType_Unknown:
+ sb.append(" N/A");
+ break;
+ case kDeviceType_Audio:
+ sb.append(" Audio");
+ break;
+ case kDeviceType_Control:
+ sb.append(" Control");
+ break;
+ case kDeviceType_MIDI:
+ sb.append(" MIDI");
+ break;
+ }
+
+ switch (mDeviceDir) {
+ case kDeviceDir_Unknown:
+ sb.append(" N/A");
+ break;
+ case kDeviceDir_Capture:
+ sb.append(" Capture");
+ break;
+ case kDeviceDir_Playback:
+ sb.append(" Playback");
+ break;
+ }
+
+ return sb.toString();
+ }
+ }
+
+ private Vector<AlsaDeviceRecord>
+ deviceRecords_ = new Vector<AlsaDeviceRecord>();
+
+ private boolean isLineDeviceRecord(String line) {
+ return line.charAt(kIndex_CardDeviceField) == '[';
+ }
+
+ public AlsaDevicesParser() {
+ }
+
+ public int getNumDeviceRecords() {
+ return deviceRecords_.size();
+ }
+
+ public AlsaDeviceRecord getDeviceRecordAt(int index) {
+ return deviceRecords_.get(index);
+ }
+
+ public void Log() {
+ int numDevRecs = getNumDeviceRecords();
+ for (int index = 0; index < numDevRecs; ++index) {
+ Slog.w(TAG, "usb:" + getDeviceRecordAt(index).textFormat());
+ }
+ }
+
+ public boolean hasPlaybackDevices() {
+ return mHasPlaybackDevices;
+ }
+
+ public boolean hasPlaybackDevices(int card) {
+ for (int index = 0; index < deviceRecords_.size(); index++) {
+ AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
+ if (deviceRecord.mCardNum == card &&
+ deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
+ deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Playback) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasCaptureDevices() {
+ return mHasCaptureDevices;
+ }
+
+ public boolean hasCaptureDevices(int card) {
+ for (int index = 0; index < deviceRecords_.size(); index++) {
+ AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
+ if (deviceRecord.mCardNum == card &&
+ deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
+ deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Capture) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasMIDIDevices() {
+ return mHasMIDIDevices;
+ }
+
+ public boolean hasMIDIDevices(int card) {
+ for (int index = 0; index < deviceRecords_.size(); index++) {
+ AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
+ if (deviceRecord.mCardNum == card &&
+ deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_MIDI) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void scan() {
+ deviceRecords_.clear();
+
+ final String devicesFilePath = "/proc/asound/devices";
+ File devicesFile = new File(devicesFilePath);
+ try {
+ FileReader reader = new FileReader(devicesFile);
+ BufferedReader bufferedReader = new BufferedReader(reader);
+ String line = "";
+ while ((line = bufferedReader.readLine()) != null) {
+ if (isLineDeviceRecord(line)) {
+ AlsaDeviceRecord deviceRecord = new AlsaDeviceRecord();
+ deviceRecord.parse(line);
+ deviceRecords_.add(deviceRecord);
+ }
+ }
+ reader.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+} // class AlsaDevicesParser
+
diff --git a/core/java/android/alsa/LineTokenizer.java b/core/java/android/alsa/LineTokenizer.java
new file mode 100644
index 0000000..c138fc5
--- /dev/null
+++ b/core/java/android/alsa/LineTokenizer.java
@@ -0,0 +1,57 @@
+/*
+ * 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 com.android.alsascan;
+
+/**
+ * @hide
+ * Breaks lines in an ALSA "cards" or "devices" file into tokens.
+ * TODO(pmclean) Look into replacing this with String.split().
+ */
+public class LineTokenizer {
+ public static final int kTokenNotFound = -1;
+
+ private String mDelimiters = "";
+
+ public LineTokenizer(String delimiters) {
+ mDelimiters = delimiters;
+ }
+
+ int nextToken(String line, int startIndex) {
+ int len = line.length();
+ int offset = startIndex;
+ for (; offset < len; offset++) {
+ if (mDelimiters.indexOf(line.charAt(offset)) == -1) {
+ // past a delimiter
+ break;
+ }
+ }
+
+ return offset < len ? offset : kTokenNotFound;
+ }
+
+ int nextDelimiter(String line, int startIndex) {
+ int len = line.length();
+ int offset = startIndex;
+ for (; offset < len; offset++) {
+ if (mDelimiters.indexOf(line.charAt(offset)) != -1) {
+ // past a delimiter
+ break;
+ }
+ }
+
+ return offset < len ? offset : kTokenNotFound;
+ }
+}
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/RevealAnimator.java b/core/java/android/animation/RevealAnimator.java
new file mode 100644
index 0000000..77a536a
--- /dev/null
+++ b/core/java/android/animation/RevealAnimator.java
@@ -0,0 +1,141 @@
+/*
+ * 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.animation;
+
+import android.view.View;
+
+import java.util.ArrayList;
+
+/**
+ * Reveals a View with an animated clipping circle.
+ * The clipping is implemented efficiently by talking to a private reveal API on View.
+ * This hidden class currently only accessed by the {@link android.view.View}.
+ *
+ * @hide
+ */
+public class RevealAnimator extends ValueAnimator {
+ private final static String LOGTAG = "RevealAnimator";
+ private ValueAnimator.AnimatorListener mListener;
+ private ValueAnimator.AnimatorUpdateListener mUpdateListener;
+ private RevealCircle mReuseRevealCircle = new RevealCircle(0);
+ private RevealAnimator(final View clipView, final int x, final int y,
+ float startRadius, float endRadius, final boolean inverseClip) {
+
+ setObjectValues(new RevealCircle(startRadius), new RevealCircle(endRadius));
+ setEvaluator(new RevealCircleEvaluator(mReuseRevealCircle));
+
+ mUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ RevealCircle circle = (RevealCircle) animation.getAnimatedValue();
+ float radius = circle.getRadius();
+ clipView.setRevealClip(true, inverseClip, x, y, radius);
+ }
+ };
+ mListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ clipView.setRevealClip(false, false, 0, 0, 0);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ clipView.setRevealClip(false, false, 0, 0, 0);
+ }
+ };
+ addUpdateListener(mUpdateListener);
+ addListener(mListener);
+ }
+
+ public static RevealAnimator ofRevealCircle(View clipView, int x, int y,
+ float startRadius, float endRadius, boolean inverseClip) {
+ RevealAnimator anim = new RevealAnimator(clipView, x, y,
+ startRadius, endRadius, inverseClip);
+ return anim;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void removeAllUpdateListeners() {
+ super.removeAllUpdateListeners();
+ addUpdateListener(mUpdateListener);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void removeAllListeners() {
+ super.removeAllListeners();
+ addListener(mListener);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ArrayList<AnimatorListener> getListeners() {
+ ArrayList<AnimatorListener> allListeners =
+ (ArrayList<AnimatorListener>) super.getListeners().clone();
+ allListeners.remove(mListener);
+ return allListeners;
+ }
+
+ private class RevealCircle {
+ float mRadius;
+
+ public RevealCircle(float radius) {
+ mRadius = radius;
+ }
+
+ public void setRadius(float radius) {
+ mRadius = radius;
+ }
+
+ public float getRadius() {
+ return mRadius;
+ }
+ }
+
+ private class RevealCircleEvaluator implements TypeEvaluator<RevealCircle> {
+
+ private RevealCircle mRevealCircle;
+
+ public RevealCircleEvaluator() {
+ }
+
+ public RevealCircleEvaluator(RevealCircle reuseCircle) {
+ mRevealCircle = reuseCircle;
+ }
+
+ @Override
+ public RevealCircle evaluate(float fraction, RevealCircle startValue,
+ RevealCircle endValue) {
+ float currentRadius = startValue.mRadius
+ + ((endValue.mRadius - startValue.mRadius) * fraction);
+ if (mRevealCircle == null) {
+ return new RevealCircle(currentRadius);
+ } else {
+ mRevealCircle.setRadius(currentRadius);
+ return mRevealCircle;
+ }
+ }
+ }
+}
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..5338dd0 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
@@ -1106,28 +1124,27 @@ public class ValueAnimator extends Animator {
if (!mStartedDelay) {
mStartedDelay = true;
mDelayStartTime = currentTime;
- } else {
- if (mPaused) {
- if (mPauseTime < 0) {
- mPauseTime = currentTime;
- }
- return false;
- } else if (mResumed) {
- mResumed = false;
- if (mPauseTime > 0) {
- // Offset by the duration that the animation was paused
- mDelayStartTime += (currentTime - mPauseTime);
- }
+ }
+ if (mPaused) {
+ if (mPauseTime < 0) {
+ mPauseTime = currentTime;
}
- long deltaTime = currentTime - mDelayStartTime;
- if (deltaTime > mStartDelay) {
- // startDelay ended - start the anim and record the
- // mStartTime appropriately
- mStartTime = currentTime - (deltaTime - mStartDelay);
- mPlayingState = RUNNING;
- return true;
+ return false;
+ } else if (mResumed) {
+ mResumed = false;
+ if (mPauseTime > 0) {
+ // Offset by the duration that the animation was paused
+ mDelayStartTime += (currentTime - mPauseTime);
}
}
+ long deltaTime = currentTime - mDelayStartTime;
+ if (deltaTime > mStartDelay) {
+ // startDelay ended - start the anim and record the
+ // mStartTime appropriately
+ mStartTime = currentTime - (deltaTime - mStartDelay);
+ mPlayingState = RUNNING;
+ return true;
+ }
return false;
}
diff --git a/core/java/android/annotation/AnimRes.java b/core/java/android/annotation/AnimRes.java
new file mode 100644
index 0000000..56f8acf
--- /dev/null
+++ b/core/java/android/annotation/AnimRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be an anim resource reference (e.g. {@link android.R.anim#fade_in}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface AnimRes {
+}
diff --git a/core/java/android/annotation/AnimatorRes.java b/core/java/android/annotation/AnimatorRes.java
new file mode 100644
index 0000000..cd4c189
--- /dev/null
+++ b/core/java/android/annotation/AnimatorRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be an animator resource reference (e.g. {@link android.R.animator#fade_in}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface AnimatorRes {
+}
diff --git a/core/java/android/annotation/AnyRes.java b/core/java/android/annotation/AnyRes.java
new file mode 100644
index 0000000..44411a0
--- /dev/null
+++ b/core/java/android/annotation/AnyRes.java
@@ -0,0 +1,39 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be a resource reference of any type. If the specific type is known, use
+ * one of the more specific annotations instead, such as {@link StringRes} or
+ * {@link DrawableRes}.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface AnyRes {
+}
diff --git a/core/java/android/annotation/ArrayRes.java b/core/java/android/annotation/ArrayRes.java
new file mode 100644
index 0000000..1407af1
--- /dev/null
+++ b/core/java/android/annotation/ArrayRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be an array resource reference (e.g. {@link android.R.array#phoneTypes}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface ArrayRes {
+}
diff --git a/core/java/android/annotation/AttrRes.java b/core/java/android/annotation/AttrRes.java
new file mode 100644
index 0000000..285b80c
--- /dev/null
+++ b/core/java/android/annotation/AttrRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be an attribute reference (e.g. {@link android.R.attr#action}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface AttrRes {
+}
diff --git a/core/java/android/annotation/BoolRes.java b/core/java/android/annotation/BoolRes.java
new file mode 100644
index 0000000..f50785b
--- /dev/null
+++ b/core/java/android/annotation/BoolRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be a boolean resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface BoolRes {
+}
diff --git a/core/java/android/annotation/ColorRes.java b/core/java/android/annotation/ColorRes.java
new file mode 100644
index 0000000..061faa0
--- /dev/null
+++ b/core/java/android/annotation/ColorRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be a color resource reference (e.g. {@link android.R.color#black}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface ColorRes {
+}
diff --git a/core/java/android/annotation/DimenRes.java b/core/java/android/annotation/DimenRes.java
new file mode 100644
index 0000000..02ae00c
--- /dev/null
+++ b/core/java/android/annotation/DimenRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be a dimension resource reference (e.g. {@link android.R.dimen#app_icon_size}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface DimenRes {
+}
diff --git a/core/java/android/annotation/DrawableRes.java b/core/java/android/annotation/DrawableRes.java
new file mode 100644
index 0000000..ebefa1d
--- /dev/null
+++ b/core/java/android/annotation/DrawableRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be a drawable resource reference (e.g. {@link android.R.attr#alertDialogIcon}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface DrawableRes {
+}
diff --git a/core/java/android/annotation/FractionRes.java b/core/java/android/annotation/FractionRes.java
new file mode 100644
index 0000000..fd84d3e
--- /dev/null
+++ b/core/java/android/annotation/FractionRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be a fraction resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface FractionRes {
+}
diff --git a/core/java/android/annotation/IdRes.java b/core/java/android/annotation/IdRes.java
new file mode 100644
index 0000000..b286965
--- /dev/null
+++ b/core/java/android/annotation/IdRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be an id resource reference (e.g. {@link android.R.id#copy}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface IdRes {
+}
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/IntegerRes.java b/core/java/android/annotation/IntegerRes.java
new file mode 100644
index 0000000..5313f4a
--- /dev/null
+++ b/core/java/android/annotation/IntegerRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be an integer resource reference (e.g. {@link android.R.integer#config_shortAnimTime}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface IntegerRes {
+}
diff --git a/core/java/android/annotation/InterpolatorRes.java b/core/java/android/annotation/InterpolatorRes.java
new file mode 100644
index 0000000..8877a5f
--- /dev/null
+++ b/core/java/android/annotation/InterpolatorRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be an interpolator resource reference (e.g. {@link android.R.interpolator#cycle}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface InterpolatorRes {
+}
diff --git a/core/java/android/annotation/LayoutRes.java b/core/java/android/annotation/LayoutRes.java
new file mode 100644
index 0000000..15ba86f
--- /dev/null
+++ b/core/java/android/annotation/LayoutRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be a layout resource reference (e.g. {@link android.R.layout#list_content}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface LayoutRes {
+}
diff --git a/core/java/android/annotation/MenuRes.java b/core/java/android/annotation/MenuRes.java
new file mode 100644
index 0000000..b6dcc46
--- /dev/null
+++ b/core/java/android/annotation/MenuRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be a menu resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface MenuRes {
+}
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/PluralsRes.java b/core/java/android/annotation/PluralsRes.java
new file mode 100644
index 0000000..31ac729
--- /dev/null
+++ b/core/java/android/annotation/PluralsRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be a plurals resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface PluralsRes {
+}
diff --git a/core/java/android/annotation/RawRes.java b/core/java/android/annotation/RawRes.java
new file mode 100644
index 0000000..39970b3
--- /dev/null
+++ b/core/java/android/annotation/RawRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be a raw resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface RawRes {
+}
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/annotation/StringRes.java b/core/java/android/annotation/StringRes.java
new file mode 100644
index 0000000..190b68a
--- /dev/null
+++ b/core/java/android/annotation/StringRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be a String resource reference (e.g. {@link android.R.string#ok}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface StringRes {
+}
diff --git a/core/java/android/annotation/StyleRes.java b/core/java/android/annotation/StyleRes.java
new file mode 100644
index 0000000..4453b8d
--- /dev/null
+++ b/core/java/android/annotation/StyleRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 integer parameter, field or method return value is expected
+ * to be a style resource reference (e.g. {@link android.R.style#TextAppearance}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface StyleRes {
+}
diff --git a/core/java/android/annotation/StyleableRes.java b/core/java/android/annotation/StyleableRes.java
new file mode 100644
index 0000000..3c1895e
--- /dev/null
+++ b/core/java/android/annotation/StyleableRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 integer parameter, field or method return value is expected
+ * to be a styleable resource reference (e.g. {@link android.R.styleable#TextView_text}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface StyleableRes {
+}
diff --git a/core/java/android/annotation/XmlRes.java b/core/java/android/annotation/XmlRes.java
new file mode 100644
index 0000000..5fb8a4a
--- /dev/null
+++ b/core/java/android/annotation/XmlRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.Documented;
+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 an integer parameter, field or method return value is expected
+ * to be an XML resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface XmlRes {
+}
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index c4ddf1f..9818c33 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -16,10 +16,15 @@
package android.app;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.view.ActionMode;
import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
@@ -27,28 +32,57 @@ import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.Window;
import android.widget.SpinnerAdapter;
+import android.widget.Toolbar;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
/**
- * A window feature at the top of the activity that may display the activity title, navigation
- * modes, and other interactive items.
+ * A primary toolbar within the activity that may display the activity title, application-level
+ * navigation affordances, and other interactive items.
+ *
* <p>Beginning with Android 3.0 (API level 11), the action bar appears at the top of an
* activity's window when the activity uses the system's {@link
* android.R.style#Theme_Holo Holo} theme (or one of its descendant themes), which is the default.
* You may otherwise add the action bar by calling {@link
* android.view.Window#requestFeature requestFeature(FEATURE_ACTION_BAR)} or by declaring it in a
* custom theme with the {@link android.R.styleable#Theme_windowActionBar windowActionBar} property.
- * <p>By default, the action bar shows the application icon on
+ * </p>
+ *
+ * <p>Beginning with Android L (API level 21), the action bar may be represented by any
+ * Toolbar widget within the application layout. The application may signal to the Activity
+ * which Toolbar should be treated as the Activity's action bar. Activities that use this
+ * feature should use one of the supplied <code>.NoActionBar</code> themes, set the
+ * {@link android.R.styleable#Theme_windowActionBar windowActionBar} attribute to <code>false</code>
+ * or otherwise not request the window feature.</p>
+ *
+ * <p>By adjusting the window features requested by the theme and the layouts used for
+ * an Activity's content view, an app can use the standard system action bar on older platform
+ * releases and the newer inline toolbars on newer platform releases. The <code>ActionBar</code>
+ * object obtained from the Activity can be used to control either configuration transparently.</p>
+ *
+ * <p>When using the Holo themes the action bar shows the application icon on
* the left, followed by the activity title. If your activity has an options menu, you can make
* select items accessible directly from the action bar as "action items". You can also
* modify various characteristics of the action bar or remove it completely.</p>
+ *
+ * <p>When using the Quantum themes (default in API 21 or newer) the navigation button
+ * (formerly "Home") takes over the space previously occupied by the application icon.
+ * Apps wishing to express a stronger branding should use their brand colors heavily
+ * in the action bar and other application chrome or use a {@link #setLogo(int) logo}
+ * in place of their standard title text.</p>
+ *
* <p>From your activity, you can retrieve an instance of {@link ActionBar} by calling {@link
* android.app.Activity#getActionBar getActionBar()}.</p>
+ *
* <p>In some cases, the action bar may be overlayed by another bar that enables contextual actions,
* using an {@link android.view.ActionMode}. For example, when the user selects one or more items in
* your activity, you can enable an action mode that offers actions specific to the selected
* items, with a UI that temporarily replaces the action bar. Although the UI may occupy the
* same space, the {@link android.view.ActionMode} APIs are distinct and independent from those for
- * {@link ActionBar}.
+ * {@link ActionBar}.</p>
+ *
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For information about how to use the action bar, including how to add action items, navigation
@@ -57,11 +91,21 @@ 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
* will dispatch onOptionsItemSelected to the host Activity with
* a MenuItem with item ID android.R.id.home.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public static final int NAVIGATION_MODE_STANDARD = 0;
@@ -69,15 +113,38 @@ public abstract class ActionBar {
* List navigation mode. Instead of static title text this mode
* presents a list menu for navigation within the activity.
* e.g. this might be presented to the user as a dropdown list.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public static final int NAVIGATION_MODE_LIST = 1;
/**
* Tab navigation mode. Instead of static title text this mode
* presents a series of tabs for navigation within the activity.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
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.
@@ -264,6 +331,11 @@ public abstract class ActionBar {
* within the dropdown navigation menu.
* @param callback An OnNavigationListener that will receive events when the user
* selects a navigation item.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract void setListNavigationCallbacks(SpinnerAdapter adapter,
OnNavigationListener callback);
@@ -272,6 +344,11 @@ public abstract class ActionBar {
* Set the selected navigation item in list or tabbed navigation modes.
*
* @param position Position of the item to select.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract void setSelectedNavigationItem(int position);
@@ -279,6 +356,11 @@ public abstract class ActionBar {
* Get the position of the selected navigation item in list or tabbed navigation modes.
*
* @return Position of the selected item.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract int getSelectedNavigationIndex();
@@ -286,6 +368,11 @@ public abstract class ActionBar {
* Get the number of navigation items present in the current navigation mode.
*
* @return Number of navigation items.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract int getNavigationItemCount();
@@ -341,7 +428,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 +443,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 +518,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
@@ -483,7 +570,13 @@ public abstract class ActionBar {
* </ul>
*
* @return The current navigation mode.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
+ @NavigationMode
public abstract int getNavigationMode();
/**
@@ -493,8 +586,13 @@ public abstract class ActionBar {
* @see #NAVIGATION_MODE_STANDARD
* @see #NAVIGATION_MODE_LIST
* @see #NAVIGATION_MODE_TABS
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
- public abstract void setNavigationMode(int mode);
+ public abstract void setNavigationMode(@NavigationMode int mode);
/**
* @return The current set of display options.
@@ -514,6 +612,11 @@ public abstract class ActionBar {
* @return A new Tab
*
* @see #addTab(Tab)
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract Tab newTab();
@@ -522,6 +625,11 @@ public abstract class ActionBar {
* If this is the first tab to be added it will become the selected tab.
*
* @param tab Tab to add
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract void addTab(Tab tab);
@@ -530,6 +638,11 @@ public abstract class ActionBar {
*
* @param tab Tab to add
* @param setSelected True if the added tab should become the selected tab.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract void addTab(Tab tab, boolean setSelected);
@@ -540,6 +653,11 @@ public abstract class ActionBar {
*
* @param tab The tab to add
* @param position The new position of the tab
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract void addTab(Tab tab, int position);
@@ -550,6 +668,11 @@ public abstract class ActionBar {
* @param tab The tab to add
* @param position The new position of the tab
* @param setSelected True if the added tab should become the selected tab.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract void addTab(Tab tab, int position, boolean setSelected);
@@ -558,6 +681,11 @@ public abstract class ActionBar {
* and another tab will be selected if present.
*
* @param tab The tab to remove
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract void removeTab(Tab tab);
@@ -566,11 +694,21 @@ public abstract class ActionBar {
* and another tab will be selected if present.
*
* @param position Position of the tab to remove
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract void removeTabAt(int position);
/**
* Remove all tabs from the action bar and deselect the current tab.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract void removeAllTabs();
@@ -580,6 +718,11 @@ public abstract class ActionBar {
* <p>Note: If you want to select by index, use {@link #setSelectedNavigationItem(int)}.</p>
*
* @param tab Tab to select
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract void selectTab(Tab tab);
@@ -588,6 +731,11 @@ public abstract class ActionBar {
* one tab present.
*
* @return The currently selected tab or null
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract Tab getSelectedTab();
@@ -596,12 +744,22 @@ public abstract class ActionBar {
*
* @param index Index value in the range 0-get
* @return
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract Tab getTabAt(int index);
/**
* Returns the number of tabs currently registered with the action bar.
* @return Tab count
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public abstract int getTabCount();
@@ -774,8 +932,38 @@ public abstract class ActionBar {
*/
public void setHomeActionContentDescription(int resId) { }
+ /** @hide */
+ public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) {
+ }
+
+ /** @hide */
+ public void setShowHideAnimationEnabled(boolean enabled) {
+ }
+
+ /** @hide */
+ public void onConfigurationChanged(Configuration config) {
+ }
+
+ /** @hide */
+ public void dispatchMenuVisibilityChanged(boolean visible) {
+ }
+
+ /** @hide */
+ public void captureSharedElements(Map<String, View> sharedElements) {
+ }
+
+ /** @hide */
+ public ActionMode startActionMode(ActionMode.Callback callback) {
+ return null;
+ }
+
/**
* Listener interface for ActionBar navigation events.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public interface OnNavigationListener {
/**
@@ -808,6 +996,11 @@ public abstract class ActionBar {
* A tab in the action bar.
*
* <p>Tabs manage the hiding and showing of {@link Fragment}s.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public static abstract class Tab {
/**
@@ -959,6 +1152,11 @@ public abstract class ActionBar {
/**
* Callback interface invoked when a tab is focused, unfocused, added, or removed.
+ *
+ * @deprecated Action bar navigation modes are deprecated and not supported by inline
+ * toolbar action bars. Consider using other
+ * <a href="http://developer.android.com/design/patterns/navigation.html">common
+ * navigation patterns</a> instead.
*/
public interface TabListener {
/**
@@ -1000,31 +1198,31 @@ public abstract class ActionBar {
*
* @attr ref android.R.styleable#ActionBar_LayoutParams_layout_gravity
*/
- public static class LayoutParams extends MarginLayoutParams {
+ public static class LayoutParams extends ViewGroup.MarginLayoutParams {
/**
* Gravity for the view associated with these LayoutParams.
*
* @see android.view.Gravity
*/
@ViewDebug.ExportedProperty(category = "layout", mapping = {
- @ViewDebug.IntToString(from = -1, to = "NONE"),
- @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"),
- @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"),
- @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"),
- @ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"),
- @ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"),
- @ViewDebug.IntToString(from = Gravity.START, to = "START"),
- @ViewDebug.IntToString(from = Gravity.END, to = "END"),
- @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"),
- @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"),
- @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
- @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"),
- @ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"),
- @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL")
+ @ViewDebug.IntToString(from = -1, to = "NONE"),
+ @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"),
+ @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"),
+ @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"),
+ @ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"),
+ @ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"),
+ @ViewDebug.IntToString(from = Gravity.START, to = "START"),
+ @ViewDebug.IntToString(from = Gravity.END, to = "END"),
+ @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"),
+ @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"),
+ @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
+ @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"),
+ @ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"),
+ @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL")
})
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,
@@ -1037,11 +1235,11 @@ public abstract class ActionBar {
public LayoutParams(int width, int height) {
super(width, height);
- this.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
}
public LayoutParams(int width, int height, int gravity) {
super(width, height);
+
this.gravity = gravity;
}
@@ -1051,12 +1249,14 @@ public abstract class ActionBar {
public LayoutParams(LayoutParams source) {
super(source);
-
- this.gravity = source.gravity;
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
+
+ public LayoutParams(MarginLayoutParams source) {
+ super(source);
+ }
}
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 63c9fec..b18eb98 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -16,11 +16,19 @@
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.SuperNotCalledException;
-import com.android.internal.app.ActionBarImpl;
+import android.widget.Toolbar;
+import com.android.internal.app.WindowDecorActionBar;
+import com.android.internal.app.ToolbarActionBar;
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 +92,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;
@@ -714,7 +724,7 @@ public class Activity extends ContextThemeWrapper
/*package*/ boolean mWindowAdded = false;
/*package*/ boolean mVisibleFromServer = false;
/*package*/ boolean mVisibleFromClient = true;
- /*package*/ ActionBarImpl mActionBar = null;
+ /*package*/ ActionBar mActionBar = null;
private boolean mEnableDefaultActionBarUp;
private CharSequence mTitle;
@@ -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;
}
@@ -1386,6 +1398,9 @@ public class Activity extends ContextThemeWrapper
protected void onStop() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
+ if (mWindow != null) {
+ mWindow.restoreViewVisibilityAfterTransitionToCallee();
+ }
getApplication().dispatchActivityStopped(this);
mTranslucentCallback = null;
mCalled = true;
@@ -1551,6 +1566,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 +1646,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 +1659,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,16 +1907,41 @@ public class Activity extends ContextThemeWrapper
*
* @return The Activity's ActionBar, or null if it does not have one.
*/
+ @Nullable
public ActionBar getActionBar() {
- initActionBar();
+ initWindowDecorActionBar();
return mActionBar;
}
+
+ /**
+ * Set a {@link android.widget.Toolbar Toolbar} to act as the {@link ActionBar} for this
+ * Activity window.
+ *
+ * <p>When set to a non-null value the {@link #getActionBar()} method will return
+ * an {@link ActionBar} object that can be used to control the given toolbar as if it were
+ * a traditional window decor action bar. The toolbar's menu will be populated with the
+ * Activity's options menu and the navigation button will be wired through the standard
+ * {@link android.R.id#home home} menu select action.</p>
+ *
+ * <p>In order to use a Toolbar within the Activity's window content the application
+ * must not request the window feature {@link Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.</p>
+ *
+ * @param actionBar Toolbar to set as the Activity's action bar
+ */
+ public void setActionBar(@Nullable Toolbar actionBar) {
+ if (getActionBar() instanceof WindowDecorActionBar) {
+ throw new IllegalStateException("This Activity already has an action bar supplied " +
+ "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " +
+ "android:windowActionBar to false in your theme to use a Toolbar instead.");
+ }
+ mActionBar = new ToolbarActionBar(actionBar);
+ }
/**
* Creates a new ActionBar, locates the inflated ActionBarView,
* initializes the ActionBar with the view, and sets mActionBar.
*/
- private void initActionBar() {
+ private void initWindowDecorActionBar() {
Window window = getWindow();
// Initializing the window decor can change window feature flags.
@@ -1909,7 +1952,7 @@ public class Activity extends ContextThemeWrapper
return;
}
- mActionBar = new ActionBarImpl(this);
+ mActionBar = new WindowDecorActionBar(this);
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
mWindow.setDefaultIcon(mActivityInfo.getIconResource());
@@ -1927,7 +1970,7 @@ public class Activity extends ContextThemeWrapper
*/
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
- initActionBar();
+ initWindowDecorActionBar();
}
/**
@@ -1947,7 +1990,7 @@ public class Activity extends ContextThemeWrapper
*/
public void setContentView(View view) {
getWindow().setContentView(view);
- initActionBar();
+ initWindowDecorActionBar();
}
/**
@@ -1963,7 +2006,7 @@ public class Activity extends ContextThemeWrapper
*/
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
- initActionBar();
+ initWindowDecorActionBar();
}
/**
@@ -1975,7 +2018,42 @@ public class Activity extends ContextThemeWrapper
*/
public void addContentView(View view, ViewGroup.LayoutParams params) {
getWindow().addContentView(view, params);
- initActionBar();
+ initWindowDecorActionBar();
+ }
+
+ /**
+ * 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();
}
/**
@@ -1985,7 +2063,17 @@ public class Activity extends ContextThemeWrapper
public void setFinishOnTouchOutside(boolean finish) {
mWindow.setCloseOnTouchOutside(finish);
}
-
+
+ /** @hide */
+ @IntDef({
+ DEFAULT_KEYS_DISABLE,
+ DEFAULT_KEYS_DIALER,
+ DEFAULT_KEYS_SHORTCUT,
+ DEFAULT_KEYS_SEARCH_LOCAL,
+ DEFAULT_KEYS_SEARCH_GLOBAL})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface DefaultKeyMode {}
+
/**
* Use with {@link #setDefaultKeyMode} to turn off default handling of
* keys.
@@ -2055,7 +2143,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
@@ -2213,7 +2301,7 @@ public class Activity extends ContextThemeWrapper
*/
public void onBackPressed() {
if (!mFragments.popBackStackImmediate()) {
- finish();
+ finishWithTransition();
}
}
@@ -2528,6 +2616,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;
}
@@ -2574,7 +2663,7 @@ public class Activity extends ContextThemeWrapper
*/
public boolean onMenuOpened(int featureId, Menu menu) {
if (featureId == Window.FEATURE_ACTION_BAR) {
- initActionBar();
+ initWindowDecorActionBar();
if (mActionBar != null) {
mActionBar.dispatchMenuVisibilityChanged(true);
} else {
@@ -2655,7 +2744,7 @@ public class Activity extends ContextThemeWrapper
break;
case Window.FEATURE_ACTION_BAR:
- initActionBar();
+ initWindowDecorActionBar();
mActionBar.dispatchMenuVisibilityChanged(false);
break;
}
@@ -3025,6 +3114,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 +3202,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 +3324,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 +3348,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 +3365,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 +3432,7 @@ public class Activity extends ContextThemeWrapper
* Convenience for calling
* {@link android.view.Window#getLayoutInflater}.
*/
+ @NonNull
public LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
@@ -3348,10 +3440,11 @@ 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) {
- initActionBar();
+ initWindowDecorActionBar();
if (mActionBar != null) {
mMenuInflater = new MenuInflater(mActionBar.getThemedContext(), this);
} else {
@@ -3386,16 +3479,20 @@ 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)) {
+ options = ActivityOptions.makeSceneTransitionAnimation().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 +3505,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 +3521,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.startExitTransitionToCallee(options);
+ }
+ }
if (mParent == null) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
@@ -3505,7 +3613,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 +3645,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 +3707,7 @@ public class Activity extends ContextThemeWrapper
*/
@Override
public void startActivity(Intent intent) {
- startActivity(intent, null);
+ this.startActivity(intent, null);
}
/**
@@ -3625,7 +3733,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 +3782,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 +3801,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 +3828,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 +3856,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 +3890,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 +3940,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 +3963,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 +3993,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 +4017,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 +4043,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 +4068,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 +4101,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 +4200,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 +4223,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);
@@ -4276,6 +4387,23 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Reverses the Activity Scene entry Transition and triggers the calling Activity
+ * to reverse its exit Transition. When the exit Transition completes,
+ * {@link #finish()} is called. If no entry Transition was used, finish() is called
+ * immediately and the Activity exit Transition is run.
+ * @see android.view.Window#setTriggerEarlySceneTransition(boolean, boolean)
+ * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.View, String)
+ */
+ public void finishWithTransition() {
+ mWindow.startExitTransitionToCaller(new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ });
+ }
+
+ /**
* Force finish another activity that you had previously started with
* {@link #startActivityForResult}.
*
@@ -4305,7 +4433,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 +4494,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 +4522,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 +4544,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 +4616,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 +4662,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 +4704,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);
@@ -4603,6 +4744,36 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Set a label and icon to be used in the Recents task display. When {@link
+ * ActivityManager#getRecentTasks} is called, the activities of each task are
+ * traversed in order from the topmost activity to the bottommost. As soon as one activity is
+ * found with either a non-null label or a non-null icon set by this call the traversal is
+ * ended. For each task those values will be returned in {@link
+ * ActivityManager.RecentTaskInfo#activityLabel} and {@link
+ * ActivityManager.RecentTaskInfo#activityIcon}.
+ *
+ * @see ActivityManager#getRecentTasks
+ * @see ActivityManager.RecentTaskInfo
+ *
+ * @param activityLabel The label to use in the RecentTaskInfo.
+ * @param activityIcon The Bitmap to use in the RecentTaskInfo.
+ */
+ public void setActivityLabelAndIcon(CharSequence activityLabel, Bitmap activityIcon) {
+ final Bitmap scaledIcon;
+ if (activityIcon != null) {
+ final int size = ActivityManager.getLauncherLargeIconSizeInner(this);
+ scaledIcon = Bitmap.createScaledBitmap(activityIcon, size, size, true);
+ } else {
+ scaledIcon = null;
+ }
+ try {
+ ActivityManagerNative.getDefault().setActivityLabelAndIcon(mToken, activityLabel,
+ scaledIcon);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Sets the visibility of the progress bar in the title.
* <p>
* In order for the progress bar to be shown, the feature must be requested
@@ -4639,7 +4810,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 +4868,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 +4904,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 +5163,7 @@ public class Activity extends ContextThemeWrapper
*
* @see ActionMode
*/
+ @Nullable
public ActionMode startActionMode(ActionMode.Callback callback) {
return mWindow.getDecorView().startActionMode(callback);
}
@@ -5005,9 +5179,10 @@ 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();
+ initWindowDecorActionBar();
if (mActionBar != null) {
return mActionBar.startActionMode(callback);
}
@@ -5148,6 +5323,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 +5366,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 +5390,7 @@ public class Activity extends ContextThemeWrapper
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
-
+
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
@@ -5227,6 +5413,34 @@ public class Activity extends ContextThemeWrapper
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
+ Window.SceneTransitionListener 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(options, sceneTransitionListener);
}
/** @hide */
@@ -5240,7 +5454,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 +5611,7 @@ public class Activity extends ContextThemeWrapper
}
}
}
-
+
mStopped = true;
}
mResumed = false;
@@ -5412,7 +5626,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 +5654,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 +5670,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..c027e99 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
@@ -502,6 +509,24 @@ public class ActivityManager {
*/
public int stackId;
+ /**
+ * The id of the user the task was running as.
+ * @hide
+ */
+ public int userId;
+
+ /**
+ * The label of the highest activity in the task stack to have set a label using
+ * {@link Activity#setActivityLabelAndIcon(CharSequence, android.graphics.Bitmap)}.
+ */
+ public CharSequence activityLabel;
+
+ /**
+ * The Bitmap icon of the highest activity in the task stack to set a Bitmap using
+ * {@link Activity#setActivityLabelAndIcon(CharSequence, android.graphics.Bitmap)}.
+ */
+ public Bitmap activityIcon;
+
public RecentTaskInfo() {
}
@@ -523,20 +548,28 @@ public class ActivityManager {
ComponentName.writeToParcel(origActivity, dest);
TextUtils.writeToParcel(description, dest,
Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ TextUtils.writeToParcel(activityLabel, dest,
+ Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ if (activityIcon == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ activityIcon.writeToParcel(dest, 0);
+ }
dest.writeInt(stackId);
+ dest.writeInt(userId);
}
public void readFromParcel(Parcel source) {
id = source.readInt();
persistentId = source.readInt();
- if (source.readInt() != 0) {
- baseIntent = Intent.CREATOR.createFromParcel(source);
- } else {
- baseIntent = null;
- }
+ baseIntent = source.readInt() > 0 ? Intent.CREATOR.createFromParcel(source) : null;
origActivity = ComponentName.readFromParcel(source);
description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ activityLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ activityIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null;
stackId = source.readInt();
+ userId = source.readInt();
}
public static final Creator<RecentTaskInfo> CREATOR
@@ -560,7 +593,7 @@ public class ActivityManager {
* {@link android.content.Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} flag.
*/
public static final int RECENT_WITH_EXCLUDED = 0x0001;
-
+
/**
* Provides a list that does not contain any
* recent tasks that currently are not available to the user.
@@ -568,6 +601,13 @@ public class ActivityManager {
public static final int RECENT_IGNORE_UNAVAILABLE = 0x0002;
/**
+ * Provides a list that contains recent tasks for all
+ * profiles of a user.
+ * @hide
+ */
+ public static final int RECENT_INCLUDE_PROFILES = 0x0004;
+
+ /**
* Return a list of the tasks that the user has recently launched, with
* the most recent being first and older ones after in order.
*
@@ -933,6 +973,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
@@ -1938,7 +1988,11 @@ public class ActivityManager {
* @return dimensions of square icons in terms of pixels
*/
public int getLauncherLargeIconSize() {
- final Resources res = mContext.getResources();
+ return getLauncherLargeIconSizeInner(mContext);
+ }
+
+ static int getLauncherLargeIconSizeInner(Context context) {
+ final Resources res = context.getResources();
final int size = res.getDimensionPixelSize(android.R.dimen.app_icon_size);
final int sw = res.getConfiguration().smallestScreenWidthDp;
@@ -2222,4 +2276,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 14c495f..44c74d8 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();
@@ -1244,7 +1253,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;
}
@@ -1686,6 +1697,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();
@@ -1816,6 +1836,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);
@@ -2041,6 +2072,48 @@ 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;
+ }
+
+ case SET_ACTIVITY_LABEL_ICON_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ CharSequence activityLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(data);
+ Bitmap activityIcon = data.readInt() > 0
+ ? Bitmap.CREATOR.createFromParcel(data) : null;
+ setActivityLabelAndIcon(token, activityLabel, activityIcon);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -2786,6 +2859,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();
@@ -3615,10 +3701,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();
}
@@ -4229,6 +4318,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();
@@ -4371,6 +4473,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();
@@ -4686,5 +4803,74 @@ 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;
+ }
+
+ @Override
+ public void setActivityLabelAndIcon(IBinder token, CharSequence activityLabel,
+ Bitmap activityIcon) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ TextUtils.writeToParcel(activityLabel, data, 0);
+ if (activityIcon != null) {
+ data.writeInt(1);
+ activityIcon.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
+ mRemote.transact(SET_ACTIVITY_LABEL_ICON_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 87b1e24..4384580 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -20,16 +20,26 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+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 +100,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 +135,13 @@ 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 static final int MSG_SET_LISTENER = 100;
+ private static final int MSG_HIDE_SHARED_ELEMENTS = 101;
+ private static final int MSG_PREPARE_RESTORE = 102;
+ private static final int MSG_RESTORE = 103;
private String mPackageName;
private int mAnimationType = ANIM_NONE;
@@ -111,6 +153,9 @@ public class ActivityOptions {
private int mStartWidth;
private int mStartHeight;
private IRemoteCallback mAnimationStartedListener;
+ private ResultReceiver mTransitionCompleteListener;
+ private ArrayList<String> mSharedElementNames;
+ private ArrayList<String> mLocalElementNames;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -156,11 +201,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 +231,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 +350,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 +410,35 @@ 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 = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER);
+ mSharedElementNames = opts.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
+ mLocalElementNames = opts.getStringArrayList(KEY_LOCAL_ELEMENT_NAMES);
+ break;
}
}
@@ -380,6 +493,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) {
+ 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);
+ mTransitionCompleteListener.send(MSG_SET_LISTENER, bundle);
+ }
+ }
+
+ /** @hide */
+ public void dispatchSharedElementsReady() {
+ if (mTransitionCompleteListener != null) {
+ mTransitionCompleteListener.send(MSG_HIDE_SHARED_ELEMENTS, null);
+ }
+ }
+
+ /** @hide */
+ public void dispatchPrepareRestore() {
+ if (mTransitionCompleteListener != null) {
+ mTransitionCompleteListener.send(MSG_PREPARE_RESTORE, null);
+ }
+ }
+
+ /** @hide */
+ public void dispatchRestore(Bundle sharedElementsArgs) {
+ if (mTransitionCompleteListener != null) {
+ mTransitionCompleteListener.send(MSG_RESTORE, sharedElementsArgs);
+ }
+ }
+
+ /** @hide */
public void abort() {
if (mAnimationStartedListener != null) {
try {
@@ -405,19 +566,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 +589,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 +604,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 +642,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 +658,159 @@ 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.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionCompleteListener);
+ }
+ 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();
+ void restore(Bundle sharedElementState);
+ void prepareForRestore();
+ }
+
+ private static class ExitTransitionListener extends ResultReceiver
+ implements Transition.TransitionListener {
+ private boolean mSharedElementNotified;
+ private IRemoteCallback mTransitionCompleteCallback;
+ private boolean mExitComplete;
+ private boolean mSharedElementComplete;
+ private SharedElementSource mSharedElementSource;
+
+ public ExitTransitionListener(Transition exitTransition, Transition sharedElementTransition,
+ SharedElementSource sharedElementSource) {
+ super(null);
+ mSharedElementSource = sharedElementSource;
+ exitTransition.addListener(this);
+ sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ mSharedElementComplete = true;
+ notifySharedElement();
+ transition.removeListener(this);
+ }
+ });
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ switch (resultCode) {
+ case MSG_SET_LISTENER:
+ IBinder listener = resultData.getBinder(KEY_TRANSITION_TARGET_LISTENER);
+ mTransitionCompleteCallback = IRemoteCallback.Stub.asInterface(listener);
+ ArrayList<String> sharedElementNames
+ = resultData.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
+ mSharedElementSource.acceptedSharedElements(sharedElementNames);
+ notifySharedElement();
+ notifyExit();
+ break;
+ case MSG_HIDE_SHARED_ELEMENTS:
+ mSharedElementSource.hideSharedElements();
+ break;
+ case MSG_PREPARE_RESTORE:
+ mSharedElementSource.prepareForRestore();
+ break;
+ case MSG_RESTORE:
+ mSharedElementSource.restore(resultData);
+ break;
+ }
+ }
+
+ @Override
+ public void onTransitionStart(Transition transition) {
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ mExitComplete = true;
+ notifyExit();
+ transition.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..965f815 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;
@@ -1583,10 +1592,10 @@ public final class ActivityThread {
/**
* Creates the top level resources for the given package.
*/
- Resources getTopLevelResources(String resDir, String[] overlayDirs,
+ Resources getTopLevelResources(String resDir, String[] overlayDirs, String[] libDirs,
int displayId, Configuration overrideConfiguration,
LoadedApk pkgInfo) {
- return mResourcesManager.getTopLevelResources(resDir, overlayDirs, displayId,
+ return mResourcesManager.getTopLevelResources(resDir, overlayDirs, libDirs, displayId,
overrideConfiguration, pkgInfo.getCompatibilityInfo(), null);
}
@@ -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 079cf7a..b616c1e 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -780,6 +780,25 @@ public class AppOpsManager {
}
}
+ /**
+ * Set a non-persisted restriction on an audio operation at a stream-level.
+ * Restrictions are temporary additional constraints imposed on top of the persisted rules
+ * defined by {@link #setMode}.
+ *
+ * @param code The operation to restrict.
+ * @param stream The {@link android.media.AudioManager} stream type.
+ * @param mode The restriction mode (MODE_IGNORED,MODE_ERRORED) or MODE_ALLOWED to unrestrict.
+ * @param exceptionPackages Optional list of packages to exclude from the restriction.
+ * @hide
+ */
+ public void setRestriction(int code, int stream, int mode, String[] exceptionPackages) {
+ try {
+ final int uid = Binder.getCallingUid();
+ mService.setAudioRestriction(code, stream, uid, mode, exceptionPackages);
+ } catch (RemoteException e) {
+ }
+ }
+
/** @hide */
public void resetAllModes() {
try {
@@ -1009,6 +1028,35 @@ public class AppOpsManager {
}
/**
+ * Like {@link #checkOp} but at a stream-level for audio operations.
+ * @hide
+ */
+ public int checkAudioOp(int op, int stream, int uid, String packageName) {
+ try {
+ final int mode = mService.checkAudioOperation(op, stream, uid, packageName);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
+ }
+ return mode;
+ } catch (RemoteException e) {
+ }
+ return MODE_IGNORED;
+ }
+
+ /**
+ * Like {@link #checkAudioOp} but instead of throwing a {@link SecurityException} it
+ * returns {@link #MODE_ERRORED}.
+ * @hide
+ */
+ public int checkAudioOpNoThrow(int op, int stream, int uid, String packageName) {
+ try {
+ return mService.checkAudioOperation(op, stream, uid, packageName);
+ } catch (RemoteException e) {
+ }
+ return MODE_IGNORED;
+ }
+
+ /**
* Make note of an application performing an operation. Note that you must pass
* in both the uid and name of the application to be checked; this function will verify
* that these two match, and if not, return {@link #MODE_IGNORED}. If this call
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 c5e6ac4..ab62427 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -29,6 +29,7 @@ import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageInstallObserver;
+import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
@@ -57,8 +58,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*/
@@ -825,7 +824,7 @@ final class ApplicationPackageManager extends PackageManager {
}
Resources r = mContext.mMainThread.getTopLevelResources(
app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir,
- app.resourceDirs, Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo);
+ app.resourceDirs, null, Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo);
if (r != null) {
return r;
}
@@ -1093,7 +1092,7 @@ final class ApplicationPackageManager extends PackageManager {
public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags,
String installerPackageName) {
try {
- mPM.installPackage(packageURI, observer, flags, installerPackageName);
+ mPM.installPackageEtc(packageURI, observer, null, flags, installerPackageName);
} catch (RemoteException e) {
// Should never happen!
}
@@ -1104,20 +1103,58 @@ final class ApplicationPackageManager extends PackageManager {
int flags, String installerPackageName, Uri verificationURI,
ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) {
try {
- mPM.installPackageWithVerification(packageURI, observer, flags, installerPackageName,
- verificationURI, manifestDigest, encryptionParams);
+ mPM.installPackageWithVerificationEtc(packageURI, observer, null, flags,
+ installerPackageName, verificationURI, manifestDigest, encryptionParams);
} catch (RemoteException e) {
// Should never happen!
}
}
@Override
- public void installPackageWithVerificationAndEncryption(Uri packageURI,
+ public void installPackageWithVerificationAndEncryption(Uri packageURI,
IPackageInstallObserver observer, int flags, String installerPackageName,
VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
try {
- mPM.installPackageWithVerificationAndEncryption(packageURI, observer, flags,
- installerPackageName, verificationParams, encryptionParams);
+ mPM.installPackageWithVerificationAndEncryptionEtc(packageURI, observer, null,
+ flags, installerPackageName, verificationParams, encryptionParams);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
+ // Expanded observer-API versions
+ @Override
+ public void installPackage(Uri packageURI, PackageInstallObserver observer,
+ int flags, String installerPackageName) {
+ try {
+ mPM.installPackageEtc(packageURI, null, observer.mObserver,
+ flags, installerPackageName);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
+ @Override
+ public void installPackageWithVerification(Uri packageURI,
+ PackageInstallObserver observer, int flags, String installerPackageName,
+ Uri verificationURI, ManifestDigest manifestDigest,
+ ContainerEncryptionParams encryptionParams) {
+ try {
+ mPM.installPackageWithVerificationEtc(packageURI, null, observer.mObserver, flags,
+ installerPackageName, verificationURI, manifestDigest, encryptionParams);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
+ @Override
+ public void installPackageWithVerificationAndEncryption(Uri packageURI,
+ PackageInstallObserver observer, int flags, String installerPackageName,
+ VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
+ try {
+ mPM.installPackageWithVerificationAndEncryptionEtc(packageURI, null,
+ observer.mObserver, flags, installerPackageName, verificationParams,
+ encryptionParams);
} catch (RemoteException e) {
// Should never happen!
}
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 924d656..77b5485 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;
@@ -35,7 +36,9 @@ import android.content.ReceiverCallNotAllowedException;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ILauncherApps;
import android.content.pm.IPackageManager;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
@@ -64,18 +67,23 @@ 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;
import android.net.NetworkPolicyManager;
+import android.net.NetworkScoreManager;
import android.net.Uri;
import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
import android.net.wifi.IWifiManager;
import android.net.wifi.WifiManager;
+import android.net.wifi.hotspot.IWifiHotspotManager;
+import android.net.wifi.hotspot.WifiHotspotManager;
import android.net.wifi.p2p.IWifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager;
import android.nfc.NfcManager;
+import android.os.BatteryManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
@@ -99,6 +107,8 @@ import android.os.storage.StorageManager;
import android.print.IPrintManager;
import android.print.PrintManager;
import android.telephony.TelephonyManager;
+import android.tv.ITvInputManager;
+import android.tv.TvInputManager;
import android.content.ClipboardManager;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
@@ -115,6 +125,7 @@ import android.view.textservice.TextServicesManager;
import android.accounts.AccountManager;
import android.accounts.IAccountManager;
import android.app.admin.DevicePolicyManager;
+import android.app.trust.TrustManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
@@ -395,6 +406,11 @@ class ContextImpl extends Context {
return new DownloadManager(ctx.getContentResolver(), ctx.getPackageName());
}});
+ registerService(BATTERY_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ return new BatteryManager();
+ }});
+
registerService(NFC_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return new NfcManager(ctx);
@@ -463,7 +479,8 @@ class ContextImpl extends Context {
outerContext.getApplicationInfo().targetSdkVersion,
com.android.internal.R.style.Theme_Dialog,
com.android.internal.R.style.Theme_Holo_Dialog,
- com.android.internal.R.style.Theme_DeviceDefault_Dialog)),
+ com.android.internal.R.style.Theme_DeviceDefault_Dialog,
+ com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog)),
ctx.mMainThread.getHandler());
}});
@@ -550,6 +567,13 @@ class ContextImpl extends Context {
return new WifiManager(ctx.getOuterContext(), service);
}});
+ registerService(WIFI_HOTSPOT_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(WIFI_HOTSPOT_SERVICE);
+ IWifiHotspotManager service = IWifiHotspotManager.Stub.asInterface(b);
+ return new WifiHotspotManager(ctx.getOuterContext(), service);
+ }});
+
registerService(WIFI_P2P_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(WIFI_P2P_SERVICE);
@@ -592,6 +616,14 @@ class ContextImpl extends Context {
}
});
+ registerService(LAUNCHER_APPS_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(LAUNCHER_APPS_SERVICE);
+ ILauncherApps service = ILauncherApps.Stub.asInterface(b);
+ return new LauncherApps(ctx, service);
+ }
+ });
+
registerService(PRINT_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE);
@@ -604,6 +636,31 @@ 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);
+ }
+ });
+ registerService(TRUST_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(TRUST_SERVICE);
+ return new TrustManager(b);
+ }
+ });
+
+ registerService(TV_INPUT_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder iBinder = ServiceManager.getService(TV_INPUT_SERVICE);
+ ITvInputManager service = ITvInputManager.Stub.asInterface(iBinder);
+ return new TvInputManager(service, UserHandle.myUserId());
+ }});
+
+ registerService(NETWORK_SCORE_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ return new NetworkScoreManager(ctx);
+ }
+ });
}
static ContextImpl getImpl(Context context) {
@@ -1480,13 +1537,13 @@ class ContextImpl extends Context {
private void validateServiceIntent(Intent service) {
if (service.getComponent() == null && service.getPackage() == null) {
- if (true || getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.KITKAT) {
+ if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) {
+ IllegalArgumentException ex = new IllegalArgumentException(
+ "Service Intent must be explicit: " + service);
+ throw ex;
+ } else {
Log.w(TAG, "Implicit intents with startService are not safe: " + service
+ " " + Debug.getCallers(2, 3));
- //IllegalArgumentException ex = new IllegalArgumentException(
- // "Service Intent must be explicit: " + service);
- //Log.e(TAG, "This will become an error", ex);
- //throw ex;
}
}
}
@@ -2036,8 +2093,9 @@ class ContextImpl extends Context {
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
resources = mResourcesManager.getTopLevelResources(
- packageInfo.getResDir(), packageInfo.getOverlayDirs(), displayId,
- overrideConfiguration, compatInfo, activityToken);
+ packageInfo.getResDir(), packageInfo.getOverlayDirs(),
+ packageInfo.getApplicationInfo().sharedLibraryFiles,
+ displayId, overrideConfiguration, compatInfo, activityToken);
}
}
mResources = resources;
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index a8277b5..07583fd 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -17,8 +17,7 @@
package android.app;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import com.android.internal.app.ActionBarImpl;
+import com.android.internal.app.WindowDecorActionBar;
import com.android.internal.policy.PolicyManager;
import android.content.ComponentName;
@@ -88,7 +87,7 @@ public class Dialog implements DialogInterface, Window.Callback,
final WindowManager mWindowManager;
Window mWindow;
View mDecor;
- private ActionBarImpl mActionBar;
+ private ActionBar mActionBar;
/**
* This field should be made private, so it is hidden from the SDK.
* {@hide}
@@ -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
@@ -269,7 +280,7 @@ public class Dialog implements DialogInterface, Window.Callback,
final ApplicationInfo info = mContext.getApplicationInfo();
mWindow.setDefaultIcon(info.icon);
mWindow.setDefaultLogo(info.logo);
- mActionBar = new ActionBarImpl(this);
+ mActionBar = new WindowDecorActionBar(this);
}
WindowManager.LayoutParams l = mWindow.getAttributes();
@@ -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 4e2a57d..bfbd339 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 */
@@ -241,7 +242,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;
@@ -344,6 +346,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;
@@ -362,6 +365,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;
@@ -415,6 +420,22 @@ 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;
+
+ /** @hide */
+ public void setActivityLabelAndIcon(IBinder token, CharSequence activityLabel,
+ Bitmap activityBitmap) throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -701,4 +722,15 @@ 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;
+ int SET_ACTIVITY_LABEL_ICON_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+217;
}
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/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index 087f83c..7036aea 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -76,8 +76,9 @@ oneway interface IBackupAgent {
* @param callbackBinder Binder on which to indicate operation completion,
* passed here as a convenience to the agent.
*/
- void doRestore(in ParcelFileDescriptor data, int appVersionCode,
- in ParcelFileDescriptor newState, int token, IBackupManager callbackBinder);
+ void doRestore(in ParcelFileDescriptor data,
+ int appVersionCode, in ParcelFileDescriptor newState,
+ int token, IBackupManager callbackBinder);
/**
* Perform a "full" backup to the given file descriptor. The output file is presumed
@@ -112,8 +113,23 @@ oneway interface IBackupAgent {
* @param path Relative path of the file within its semantic domain.
* @param mode Access mode of the file system entity, e.g. 0660.
* @param mtime Last modification time of the file system entity.
+ * @param token Opaque token identifying this transaction. This must
+ * be echoed back to the backup service binder once the agent is
+ * finished restoring the application based on the restore data
+ * contents.
+ * @param callbackBinder Binder on which to indicate operation completion,
+ * passed here as a convenience to the agent.
*/
void doRestoreFile(in ParcelFileDescriptor data, long size,
int type, String domain, String path, long mode, long mtime,
int token, IBackupManager callbackBinder);
+
+ /**
+ * Out of band: instruct the agent to crash within the client process. This is used
+ * when the backup infrastructure detects a semantic error post-hoc and needs to
+ * pass the problem back to the app.
+ *
+ * @param message The message to be passed to the agent's application in an exception.
+ */
+ void fail(String message);
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 9911467..bb6eeda 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -31,7 +31,7 @@ interface INotificationManager
void enqueueToast(String pkg, ITransientNotification callback, int duration);
void cancelToast(String pkg, ITransientNotification callback);
- void enqueueNotificationWithTag(String pkg, String basePkg, String tag, int id,
+ void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
in Notification notification, inout int[] idReceived, int userId);
void cancelNotificationWithTag(String pkg, String tag, int id, int userId);
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 09bf829..347de97 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -19,6 +19,8 @@ package android.app;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.graphics.Bitmap;
import android.view.InputEvent;
+import android.view.WindowContentFrameStats;
+import android.view.WindowAnimationFrameStats;
import android.os.ParcelFileDescriptor;
/**
@@ -26,7 +28,7 @@ import android.os.ParcelFileDescriptor;
* on behalf of an instrumentation that it runs. These operations require
* special permissions which the shell user has but the instrumentation does
* not. Running privileged operations by the shell user on behalf of an
- * instrumentation is needed for running UiTestCases.
+ * instrumentation is needed for running UiTestCases.
*
* {@hide}
*/
@@ -37,4 +39,8 @@ interface IUiAutomationConnection {
boolean setRotation(int rotation);
Bitmap takeScreenshot(int width, int height);
void shutdown();
+ boolean clearWindowContentFrameStats(int windowId);
+ WindowContentFrameStats getWindowContentFrameStats(int windowId);
+ void clearWindowAnimationFrameStats();
+ WindowAnimationFrameStats getWindowAnimationFrameStats();
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index d409352..3ae8bfc 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -41,6 +41,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.util.AndroidRuntimeException;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.DisplayAdjustments;
import android.view.Display;
@@ -48,6 +49,8 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.Enumeration;
@@ -485,7 +488,7 @@ public final class LoadedApk {
public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir, mOverlayDirs,
- Display.DEFAULT_DISPLAY, null, this);
+ mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
}
return mResources;
}
@@ -530,10 +533,101 @@ public final class LoadedApk {
}
}
}
-
+
+ // Rewrite the R 'constants' for all library apks.
+ SparseArray<String> packageIdentifiers = getAssets(mActivityThread)
+ .getAssignedPackageIdentifiers();
+ final int N = packageIdentifiers.size();
+ for (int i = 0; i < N; i++) {
+ final int id = packageIdentifiers.keyAt(i);
+ if (id == 0x01 || id == 0x7f) {
+ continue;
+ }
+
+ rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
+ }
+
return app;
}
+ private void rewriteIntField(Field field, int packageId) throws IllegalAccessException {
+ int requiredModifiers = Modifier.STATIC | Modifier.PUBLIC;
+ int bannedModifiers = Modifier.FINAL;
+
+ int mod = field.getModifiers();
+ if ((mod & requiredModifiers) != requiredModifiers ||
+ (mod & bannedModifiers) != 0) {
+ throw new IllegalArgumentException("Field " + field.getName() +
+ " is not rewritable");
+ }
+
+ if (field.getType() != int.class && field.getType() != Integer.class) {
+ throw new IllegalArgumentException("Field " + field.getName() +
+ " is not an integer");
+ }
+
+ try {
+ int resId = field.getInt(null);
+ field.setInt(null, (resId & 0x00ffffff) | (packageId << 24));
+ } catch (IllegalAccessException e) {
+ // This should not occur (we check above if we can write to it)
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ private void rewriteIntArrayField(Field field, int packageId) {
+ int requiredModifiers = Modifier.STATIC | Modifier.PUBLIC;
+
+ if ((field.getModifiers() & requiredModifiers) != requiredModifiers) {
+ throw new IllegalArgumentException("Field " + field.getName() +
+ " is not rewritable");
+ }
+
+ if (field.getType() != int[].class) {
+ throw new IllegalArgumentException("Field " + field.getName() +
+ " is not an integer array");
+ }
+
+ try {
+ int[] array = (int[]) field.get(null);
+ for (int i = 0; i < array.length; i++) {
+ array[i] = (array[i] & 0x00ffffff) | (packageId << 24);
+ }
+ } catch (IllegalAccessException e) {
+ // This should not occur (we check above if we can write to it)
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ private void rewriteRValues(ClassLoader cl, String packageName, int id) {
+ try {
+ final Class<?> rClazz = cl.loadClass(packageName + ".R");
+ Class<?>[] declaredClasses = rClazz.getDeclaredClasses();
+ for (Class<?> clazz : declaredClasses) {
+ try {
+ if (clazz.getSimpleName().equals("styleable")) {
+ for (Field field : clazz.getDeclaredFields()) {
+ if (field.getType() == int[].class) {
+ rewriteIntArrayField(field, id);
+ }
+ }
+
+ } else {
+ for (Field field : clazz.getDeclaredFields()) {
+ rewriteIntField(field, id);
+ }
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Failed to rewrite R values for " +
+ clazz.getName(), e);
+ }
+ }
+
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Failed to rewrite R values", e);
+ }
+ }
+
public void removeContextRegistrations(Context context,
String who, String what) {
final boolean reportRegistrationLeaks = StrictMode.vmRegistrationLeaksEnabled();
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 e6b5de1..fe629f6 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -17,11 +17,14 @@
package android.app;
import com.android.internal.R;
+import com.android.internal.util.LegacyNotificationUtil;
+import android.annotation.IntDef;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.PorterDuff;
import android.media.AudioManager;
import android.net.Uri;
import android.os.BadParcelableException;
@@ -37,6 +40,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 +206,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 +371,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 +417,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;
+
/**
* Notification category: incoming call (voice or video) or similar synchronous communication request.
*/
@@ -588,6 +633,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
@@ -716,6 +762,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.
*/
@@ -814,6 +867,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
@@ -894,6 +957,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
}
@@ -908,6 +982,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);
@@ -1019,6 +1094,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);
+ }
}
/**
@@ -1152,6 +1243,9 @@ public class Notification implements Parcelable
if (bigContentView != null) {
bigContentView.setUser(user);
}
+ if (headsUpContentView != null) {
+ headsUpContentView.setUser(user);
+ }
}
/**
@@ -1213,6 +1307,11 @@ 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;
+ private final LegacyNotificationUtil mLegacyNotificationUtil;
+ private ArrayList<String> mPeople;
/**
* Constructs a new Builder with the defaults:
@@ -1240,6 +1339,14 @@ public class Notification implements Parcelable
mWhen = System.currentTimeMillis();
mAudioStreamType = STREAM_DEFAULT;
mPriority = PRIORITY_DEFAULT;
+ mPeople = new ArrayList<String>();
+
+ // TODO: Decide on targetSdk from calling app whether to use quantum theme.
+ mQuantumTheme = true;
+
+ // TODO: Decide on targetSdk from calling app whether to instantiate the processor at
+ // all.
+ mLegacyNotificationUtil = LegacyNotificationUtil.getInstance();
}
/**
@@ -1602,7 +1709,7 @@ public class Notification implements Parcelable
*
* @see Notification#priority
*/
- public Builder setPriority(int pri) {
+ public Builder setPriority(@Priority int pri) {
mPriority = pri;
return this;
}
@@ -1618,6 +1725,16 @@ public class Notification implements Parcelable
}
/**
+ * Add a person that is relevant to this notification.
+ *
+ * @see Notification#EXTRA_PEOPLE
+ */
+ public Builder addPerson(String handle) {
+ mPeople.add(handle);
+ return this;
+ }
+
+ /**
* Merge additional metadata into this notification.
*
* <p>Values within the Bundle will replace existing extras values in this Builder.
@@ -1704,6 +1821,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;
@@ -1717,42 +1858,50 @@ public class Notification implements Parcelable
boolean showLine3 = false;
boolean showLine2 = false;
int smallIconImageViewId = R.id.icon;
- if (mLargeIcon != null) {
- 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,
"setBackgroundResource", R.drawable.notification_bg_low);
}
+ if (mLargeIcon != null) {
+ contentView.setImageViewBitmap(R.id.icon, mLargeIcon);
+ processLegacyLargeIcon(mLargeIcon, contentView);
+ smallIconImageViewId = R.id.right_icon;
+ }
if (mSmallIcon != 0) {
contentView.setImageViewResource(smallIconImageViewId, mSmallIcon);
contentView.setViewVisibility(smallIconImageViewId, View.VISIBLE);
+ if (mLargeIcon != null) {
+ processLegacySmallIcon(mSmallIcon, smallIconImageViewId, contentView);
+ } else {
+ processLegacyLargeIcon(mSmallIcon, contentView);
+ }
+
} else {
contentView.setViewVisibility(smallIconImageViewId, View.GONE);
}
if (mContentTitle != null) {
- contentView.setTextViewText(R.id.title, mContentTitle);
+ contentView.setTextViewText(R.id.title, processLegacyText(mContentTitle));
}
if (mContentText != null) {
- contentView.setTextViewText(R.id.text, mContentText);
+ contentView.setTextViewText(R.id.text, processLegacyText(mContentText));
showLine3 = true;
}
if (mContentInfo != null) {
- contentView.setTextViewText(R.id.info, mContentInfo);
+ contentView.setTextViewText(R.id.info, processLegacyText(mContentInfo));
contentView.setViewVisibility(R.id.info, View.VISIBLE);
showLine3 = true;
} else if (mNumber > 0) {
final int tooBig = mContext.getResources().getInteger(
R.integer.status_bar_notification_info_maxnum);
if (mNumber > tooBig) {
- contentView.setTextViewText(R.id.info, mContext.getResources().getString(
- R.string.status_bar_notification_info_overflow));
+ contentView.setTextViewText(R.id.info, processLegacyText(
+ mContext.getResources().getString(
+ R.string.status_bar_notification_info_overflow)));
} else {
NumberFormat f = NumberFormat.getIntegerInstance();
- contentView.setTextViewText(R.id.info, f.format(mNumber));
+ contentView.setTextViewText(R.id.info, processLegacyText(f.format(mNumber)));
}
contentView.setViewVisibility(R.id.info, View.VISIBLE);
showLine3 = true;
@@ -1762,9 +1911,9 @@ public class Notification implements Parcelable
// Need to show three lines?
if (mSubText != null) {
- contentView.setTextViewText(R.id.text, mSubText);
+ contentView.setTextViewText(R.id.text, processLegacyText(mSubText));
if (mContentText != null) {
- contentView.setTextViewText(R.id.text2, mContentText);
+ contentView.setTextViewText(R.id.text2, processLegacyText(mContentText));
contentView.setViewVisibility(R.id.text2, View.VISIBLE);
showLine2 = true;
} else {
@@ -1835,7 +1984,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
}
}
@@ -1856,24 +2005,94 @@ 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);
+ button.setTextViewText(R.id.action0, processLegacyText(action.title));
if (!tombstone) {
button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
}
button.setContentDescription(R.id.action0, action.title);
+ processLegacyAction(action, button);
return button;
}
/**
+ * @return Whether we are currently building a notification from a legacy (an app that
+ * doesn't create quantum notifications by itself) app.
+ */
+ private boolean isLegacy() {
+ return mLegacyNotificationUtil != null;
+ }
+
+ private void processLegacyAction(Action action, RemoteViews button) {
+ if (isLegacy()) {
+ if (mLegacyNotificationUtil.isGrayscale(mContext, action.icon)) {
+ button.setTextViewCompoundDrawablesRelativeColorFilter(R.id.action0, 0,
+ mContext.getResources().getColor(
+ R.color.notification_action_legacy_color_filter),
+ PorterDuff.Mode.MULTIPLY);
+ }
+ }
+ }
+
+ private CharSequence processLegacyText(CharSequence charSequence) {
+ if (isLegacy()) {
+ return mLegacyNotificationUtil.invertCharSequenceColors(charSequence);
+ } else {
+ return charSequence;
+ }
+ }
+
+ private void processLegacyLargeIcon(int largeIconId, RemoteViews contentView) {
+ if (isLegacy()) {
+ processLegacyLargeIcon(
+ mLegacyNotificationUtil.isGrayscale(mContext, largeIconId),
+ contentView);
+ }
+ }
+
+ private void processLegacyLargeIcon(Bitmap largeIcon, RemoteViews contentView) {
+ if (isLegacy()) {
+ processLegacyLargeIcon(
+ mLegacyNotificationUtil.isGrayscale(largeIcon),
+ contentView);
+ }
+ }
+
+ private void processLegacyLargeIcon(boolean isGrayscale, RemoteViews contentView) {
+ if (isLegacy() && isGrayscale) {
+ contentView.setInt(R.id.icon, "setBackgroundResource",
+ R.drawable.notification_icon_legacy_bg_inset);
+ }
+ }
+
+ private void processLegacySmallIcon(int smallIconDrawableId, int smallIconImageViewId,
+ RemoteViews contentView) {
+ if (isLegacy()) {
+ if (mLegacyNotificationUtil.isGrayscale(mContext, smallIconDrawableId)) {
+ contentView.setDrawableParameters(smallIconImageViewId, false, -1,
+ mContext.getResources().getColor(
+ R.color.notification_action_legacy_color_filter),
+ PorterDuff.Mode.MULTIPLY, -1);
+ }
+ }
+ }
+
+ /**
* Apply the unstyled operations and return a new {@link Notification} object.
* @hide
*/
@@ -1899,6 +2118,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;
}
@@ -1911,6 +2131,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;
}
@@ -1935,6 +2161,9 @@ public class Notification implements Parcelable
if (mLargeIcon != null) {
extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
}
+ if (!mPeople.isEmpty()) {
+ extras.putStringArray(EXTRA_PEOPLE, mPeople.toArray(new String[mPeople.size()]));
+ }
}
/**
@@ -1975,6 +2204,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;
+ }
}
/**
@@ -2039,7 +2311,7 @@ public class Notification implements Parcelable
mSummaryTextSet ? mSummaryText
: mBuilder.mSubText;
if (overflowText != null) {
- contentView.setTextViewText(R.id.text, overflowText);
+ contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(overflowText));
contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE);
contentView.setViewVisibility(R.id.line3, View.VISIBLE);
} else {
@@ -2060,6 +2332,7 @@ public class Notification implements Parcelable
if (mBigContentTitle != null) {
extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
}
+ extras.putString(EXTRA_TEMPLATE, this.getClass().getName());
}
/**
@@ -2143,7 +2416,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);
@@ -2242,14 +2515,14 @@ 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
contentView.setViewPadding(R.id.line1, 0, 0, 0, 0);
}
- contentView.setTextViewText(R.id.big_text, mBigText);
+ contentView.setTextViewText(R.id.big_text, mBuilder.processLegacyText(mBigText));
contentView.setViewVisibility(R.id.big_text, View.VISIBLE);
contentView.setViewVisibility(R.id.text2, View.GONE);
@@ -2336,7 +2609,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);
@@ -2354,7 +2627,7 @@ public class Notification implements Parcelable
CharSequence str = mTexts.get(i);
if (str != null && !str.equals("")) {
contentView.setViewVisibility(rowIds[i], View.VISIBLE);
- contentView.setTextViewText(rowIds[i], str);
+ contentView.setTextViewText(rowIds[i], mBuilder.processLegacyText(str));
}
i++;
}
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/PackageInstallObserver.java b/core/java/android/app/PackageInstallObserver.java
new file mode 100644
index 0000000..dacffb4
--- /dev/null
+++ b/core/java/android/app/PackageInstallObserver.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+import android.content.pm.IPackageInstallObserver2;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+/**
+ * @hide
+ *
+ * New-style observer for package installers to use.
+ */
+public class PackageInstallObserver {
+ IPackageInstallObserver2.Stub mObserver = new IPackageInstallObserver2.Stub() {
+ @Override
+ public void packageInstalled(String pkgName, Bundle extras, int result)
+ throws RemoteException {
+ PackageInstallObserver.this.packageInstalled(pkgName, extras, result);
+ }
+ };
+
+ /**
+ * This method will be called to report the result of the package installation attempt.
+ *
+ * @param pkgName Name of the package whose installation was attempted
+ * @param extras If non-null, this Bundle contains extras providing additional information
+ * about an install failure. See {@link android.content.pm.PackageManager} for
+ * documentation about which extras apply to various failures; in particular the
+ * strings named EXTRA_FAILURE_*.
+ * @param result The numeric success or failure code indicating the basic outcome
+ */
+ public void packageInstalled(String pkgName, Bundle extras, int result) {
+ }
+}
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 7129e9e..cf14202 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/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 728f372..a67faa0 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -144,14 +144,16 @@ public class ResourcesManager {
* Creates the top level Resources for applications with the given compatibility info.
*
* @param resDir the resource directory.
+ * @param overlayDirs the resource overlay directories.
+ * @param libDirs the shared library resource dirs this app references.
* @param compatInfo the compability info. Must not be null.
* @param token the application token for determining stack bounds.
*/
- public Resources getTopLevelResources(String resDir, String[] overlayDirs, int displayId,
- Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
+ public Resources getTopLevelResources(String resDir, String[] overlayDirs, String[] libDirs,
+ int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo,
+ IBinder token) {
final float scale = compatInfo.applicationScale;
- ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale,
- token);
+ ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token);
Resources r;
synchronized (this) {
// Resources is app scale dependent.
@@ -186,6 +188,15 @@ public class ResourcesManager {
}
}
+ if (libDirs != null) {
+ for (String libDir : libDirs) {
+ if (assets.addAssetPath(libDir) == 0) {
+ Slog.w(TAG, "Asset path '" + libDir +
+ "' does not exist or contains no resources.");
+ }
+ }
+ }
+
//Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
DisplayMetrics dm = getDisplayMetricsLocked(displayId);
Configuration config;
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/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 498fa42..9405325 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -33,12 +33,16 @@ import android.view.Display;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.Surface;
+import android.view.WindowAnimationFrameStats;
+import android.view.WindowContentFrameStats;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.TimeoutException;
/**
@@ -269,10 +273,10 @@ public final class UiAutomation {
* @param action The action to perform.
* @return Whether the action was successfully performed.
*
- * @see AccessibilityService#GLOBAL_ACTION_BACK
- * @see AccessibilityService#GLOBAL_ACTION_HOME
- * @see AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS
- * @see AccessibilityService#GLOBAL_ACTION_RECENTS
+ * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_BACK
+ * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_HOME
+ * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS
+ * @see android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_RECENTS
*/
public final boolean performGlobalAction(int action) {
final IAccessibilityServiceConnection connection;
@@ -293,6 +297,28 @@ public final class UiAutomation {
}
/**
+ * Find the view that has the specified focus type. The search is performed
+ * across all windows.
+ * <p>
+ * <strong>Note:</strong> In order to access the windows you have to opt-in
+ * to retrieve the interactive windows by setting the
+ * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
+ * Otherwise, the search will be performed only in the active window.
+ * </p>
+ *
+ * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
+ * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
+ * @return The node info of the focused view or null.
+ *
+ * @see AccessibilityNodeInfo#FOCUS_INPUT
+ * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY
+ */
+ public AccessibilityNodeInfo findFocus(int focus) {
+ return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId,
+ AccessibilityNodeInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus);
+ }
+
+ /**
* Gets the an {@link AccessibilityServiceInfo} describing this UiAutomation.
* This method is useful if one wants to change some of the dynamically
* configurable properties at runtime.
@@ -346,6 +372,33 @@ public final class UiAutomation {
}
/**
+ * Gets the windows on the screen. This method returns only the windows
+ * that a sighted user can interact with, as opposed to all windows.
+ * For example, if there is a modal dialog shown and the user cannot touch
+ * anything behind it, then only the modal window will be reported
+ * (assuming it is the top one). For convenience the returned windows
+ * are ordered in a descending layer order, which is the windows that
+ * are higher in the Z-order are reported first.
+ * <p>
+ * <strong>Note:</strong> In order to access the windows you have to opt-in
+ * to retrieve the interactive windows by setting the
+ * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
+ * </p>
+ *
+ * @return The windows if there are windows such, otherwise an empty list.
+ */
+ public List<AccessibilityWindowInfo> getWindows() {
+ final int connectionId;
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ connectionId = mConnectionId;
+ }
+ // Calling out without a lock held.
+ return AccessibilityInteractionClient.getInstance()
+ .getWindows(connectionId);
+ }
+
+ /**
* Gets the root {@link AccessibilityNodeInfo} in the active window.
*
* @return The root info.
@@ -632,7 +685,7 @@ public final class UiAutomation {
* potentially undesirable actions such as calling 911 or posting on public forums etc.
*
* @param enable whether to run in a "monkey" mode or not. Default is not.
- * @see {@link ActivityManager#isUserAMonkey()}
+ * @see {@link android.app.ActivityManager#isUserAMonkey()}
*/
public void setRunAsMonkey(boolean enable) {
synchronized (mLock) {
@@ -645,6 +698,148 @@ public final class UiAutomation {
}
}
+ /**
+ * Clears the frame statistics for the content of a given window. These
+ * statistics contain information about the most recently rendered content
+ * frames.
+ *
+ * @param windowId The window id.
+ * @return Whether the window is present and its frame statistics
+ * were cleared.
+ *
+ * @see android.view.WindowContentFrameStats
+ * @see #getWindowContentFrameStats(int)
+ * @see #getWindows()
+ * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
+ */
+ public boolean clearWindowContentFrameStats(int windowId) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ try {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Clearing content frame stats for window: " + windowId);
+ }
+ // Calling out without a lock held.
+ return mUiAutomationConnection.clearWindowContentFrameStats(windowId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error clearing window content frame stats!", re);
+ }
+ return false;
+ }
+
+ /**
+ * Gets the frame statistics for a given window. These statistics contain
+ * information about the most recently rendered content frames.
+ * <p>
+ * A typical usage requires clearing the window frame statistics via {@link
+ * #clearWindowContentFrameStats(int)} followed by an interaction with the UI and
+ * finally getting the window frame statistics via calling this method.
+ * </p>
+ * <pre>
+ * // Assume we have at least one window.
+ * final int windowId = getWindows().get(0).getId();
+ *
+ * // Start with a clean slate.
+ * uiAutimation.clearWindowContentFrameStats(windowId);
+ *
+ * // Do stuff with the UI.
+ *
+ * // Get the frame statistics.
+ * WindowContentFrameStats stats = uiAutomation.getWindowContentFrameStats(windowId);
+ * </pre>
+ *
+ * @param windowId The window id.
+ * @return The window frame statistics, or null if the window is not present.
+ *
+ * @see android.view.WindowContentFrameStats
+ * @see #clearWindowContentFrameStats(int)
+ * @see #getWindows()
+ * @see AccessibilityWindowInfo#getId() AccessibilityWindowInfo.getId()
+ */
+ public WindowContentFrameStats getWindowContentFrameStats(int windowId) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ try {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Getting content frame stats for window: " + windowId);
+ }
+ // Calling out without a lock held.
+ return mUiAutomationConnection.getWindowContentFrameStats(windowId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error getting window content frame stats!", re);
+ }
+ return null;
+ }
+
+ /**
+ * Clears the window animation rendering statistics. These statistics contain
+ * information about the most recently rendered window animation frames, i.e.
+ * for window transition animations.
+ *
+ * @see android.view.WindowAnimationFrameStats
+ * @see #getWindowAnimationFrameStats()
+ * @see android.R.styleable#WindowAnimation
+ */
+ public void clearWindowAnimationFrameStats() {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ try {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Clearing window animation frame stats");
+ }
+ // Calling out without a lock held.
+ mUiAutomationConnection.clearWindowAnimationFrameStats();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error clearing window animation frame stats!", re);
+ }
+ }
+
+ /**
+ * Gets the window animation frame statistics. These statistics contain
+ * information about the most recently rendered window animation frames, i.e.
+ * for window transition animations.
+ *
+ * <p>
+ * A typical usage requires clearing the window animation frame statistics via
+ * {@link #clearWindowAnimationFrameStats()} followed by an interaction that causes
+ * a window transition which uses a window animation and finally getting the window
+ * animation frame statistics by calling this method.
+ * </p>
+ * <pre>
+ * // Start with a clean slate.
+ * uiAutimation.clearWindowAnimationFrameStats();
+ *
+ * // Do stuff to trigger a window transition.
+ *
+ * // Get the frame statistics.
+ * WindowAnimationFrameStats stats = uiAutomation.getWindowAnimationFrameStats();
+ * </pre>
+ *
+ * @return The window animation frame statistics.
+ *
+ * @see android.view.WindowAnimationFrameStats
+ * @see #clearWindowAnimationFrameStats()
+ * @see android.R.styleable#WindowAnimation
+ */
+ public WindowAnimationFrameStats getWindowAnimationFrameStats() {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ try {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Getting window animation frame stats");
+ }
+ // Calling out without a lock held.
+ return mUiAutomationConnection.getWindowAnimationFrameStats();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error getting window animation frame stats!", re);
+ }
+ return null;
+ }
+
private static float getDegreesForRotation(int value) {
switch (value) {
case Surface.ROTATION_90: {
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 91b0d7c..fa40286 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -22,12 +22,15 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.hardware.input.InputManager;
import android.os.Binder;
+import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.view.IWindowManager;
import android.view.InputEvent;
import android.view.SurfaceControl;
+import android.view.WindowAnimationFrameStats;
+import android.view.WindowContentFrameStats;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.IAccessibilityManager;
@@ -47,6 +50,9 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Service.WINDOW_SERVICE));
+ private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager.Stub.asInterface(
+ ServiceManager.getService(Service.ACCESSIBILITY_SERVICE));
+
private final Object mLock = new Object();
private final Binder mToken = new Binder();
@@ -144,6 +150,76 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
}
@Override
+ public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ IBinder token = mAccessibilityManager.getWindowToken(windowId);
+ if (token == null) {
+ return false;
+ }
+ return mWindowManager.clearWindowContentFrameStats(token);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public WindowContentFrameStats getWindowContentFrameStats(int windowId) throws RemoteException {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ IBinder token = mAccessibilityManager.getWindowToken(windowId);
+ if (token == null) {
+ return null;
+ }
+ return mWindowManager.getWindowContentFrameStats(token);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void clearWindowAnimationFrameStats() {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ SurfaceControl.clearAnimationFrameStats();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public WindowAnimationFrameStats getWindowAnimationFrameStats() {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ WindowAnimationFrameStats stats = new WindowAnimationFrameStats();
+ SurfaceControl.getAnimationFrameStats(stats);
+ return stats;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void shutdown() {
synchronized (mLock) {
if (isConnectedLocked()) {
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index f291e82..e6e0f35 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -41,7 +41,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -219,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() {
@@ -245,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) {
@@ -278,7 +265,6 @@ public class WallpaperManager {
synchronized (this) {
mWallpaper = null;
mDefaultWallpaper = null;
- mHandler.removeMessages(MSG_CLEAR_WALLPAPER);
}
}
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 66fc816..3074b49 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -52,6 +52,22 @@ public final class DeviceAdminInfo implements Parcelable {
static final String TAG = "DeviceAdminInfo";
/**
+ * A type of policy that this device admin can use: device owner meta-policy
+ * for an admin that is designated as owner of the device.
+ *
+ * @hide
+ */
+ public static final int USES_POLICY_DEVICE_OWNER = -2;
+
+ /**
+ * A type of policy that this device admin can use: profile owner meta-policy
+ * for admins that have been installed as owner of some user profile.
+ *
+ * @hide
+ */
+ public static final int USES_POLICY_PROFILE_OWNER = -1;
+
+ /**
* A type of policy that this device admin can use: limit the passwords
* that the user can select, via {@link DevicePolicyManager#setPasswordQuality}
* and {@link DevicePolicyManager#setPasswordMinimumLength}.
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..725f808 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.app.action.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) {
@@ -1493,10 +1534,10 @@ public class DevicePolicyManager {
/**
* @hide
*/
- public void setActiveAdmin(ComponentName policyReceiver, boolean refreshing) {
+ public void setActiveAdmin(ComponentName policyReceiver, boolean refreshing, int userHandle) {
if (mService != null) {
try {
- mService.setActiveAdmin(policyReceiver, refreshing, UserHandle.myUserId());
+ mService.setActiveAdmin(policyReceiver, refreshing, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1504,6 +1545,13 @@ public class DevicePolicyManager {
}
/**
+ * @hide
+ */
+ public void setActiveAdmin(ComponentName policyReceiver, boolean refreshing) {
+ setActiveAdmin(policyReceiver, refreshing, UserHandle.myUserId());
+ }
+
+ /**
* Returns the DeviceAdminInfo as defined by the administrator's package info & meta-data
* @hide
*/
@@ -1681,4 +1729,155 @@ 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;
+ }
+
+ /**
+ * Sets the enabled state of the profile. A profile should be enabled only once it is ready to
+ * be used. Only the profile owner can call this.
+ *
+ * @see #isPRofileOwnerApp
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ */
+ public void setProfileEnabled(ComponentName admin) {
+ if (mService != null) {
+ try {
+ mService.setProfileEnabled(admin);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * 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 privileges
+ * 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..e4b2adc 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,14 @@ interface IDevicePolicyManager {
String getDeviceOwner();
String getDeviceOwnerName();
+ boolean setProfileOwner(String packageName, String ownerName, int userHandle);
+ String getProfileOwner(int userHandle);
+ String getProfileOwnerName(int userHandle);
+ void setProfileEnabled(in ComponentName who);
+
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/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 67c772b..3c31f8d 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -128,6 +128,13 @@ public abstract class BackupAgent extends ContextWrapper {
Handler mHandler = null;
+ Handler getHandler() {
+ if (mHandler == null) {
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+ return mHandler;
+ }
+
class SharedPrefsSynchronizer implements Runnable {
public final CountDownLatch mLatch = new CountDownLatch(1);
@@ -140,12 +147,9 @@ public abstract class BackupAgent extends ContextWrapper {
// Syncing shared preferences deferred writes needs to happen on the main looper thread
private void waitForSharedPrefs() {
- if (mHandler == null) {
- mHandler = new Handler(Looper.getMainLooper());
- }
-
+ Handler h = getHandler();
final SharedPrefsSynchronizer s = new SharedPrefsSynchronizer();
- mHandler.postAtFrontOfQueue(s);
+ h.postAtFrontOfQueue(s);
try {
s.mLatch.await();
} catch (InterruptedException e) { /* ignored */ }
@@ -680,5 +684,23 @@ public abstract class BackupAgent extends ContextWrapper {
}
}
}
+
+ @Override
+ public void fail(String message) {
+ getHandler().post(new FailRunnable(message));
+ }
+ }
+
+ static class FailRunnable implements Runnable {
+ private String mMessage;
+
+ FailRunnable(String message) {
+ mMessage = message;
+ }
+
+ @Override
+ public void run() {
+ throw new IllegalStateException(mMessage);
+ }
}
}
diff --git a/core/java/android/app/backup/BackupDataOutput.java b/core/java/android/app/backup/BackupDataOutput.java
index 3a070b6..fc5fb3d 100644
--- a/core/java/android/app/backup/BackupDataOutput.java
+++ b/core/java/android/app/backup/BackupDataOutput.java
@@ -17,6 +17,7 @@
package android.app.backup;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -76,7 +77,8 @@ public class BackupDataOutput {
/**
* Mark the beginning of one record in the backup data stream. This must be called before
* {@link #writeEntityData}.
- * @param key A string key that uniquely identifies the data record within the application
+ * @param key A string key that uniquely identifies the data record within the application.
+ * Keys whose first character is \uFF00 or higher are not valid.
* @param dataSize The size in bytes of this record's data. Passing a dataSize
* of -1 indicates that the record under this key should be deleted.
* @return The number of bytes written to the backup stream
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/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 12ee3b6..c629a2e 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -167,7 +167,7 @@ interface IBackupManager {
* are to be backed up. The <code>allApps</code> parameter supersedes this.
*/
void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
- boolean includeShared, boolean allApps, boolean allIncludesSystem,
+ boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem,
in String[] packageNames);
/**
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/view/IMagnificationCallbacks.aidl b/core/java/android/app/trust/ITrustListener.aidl
index 032d073..4680043 100644
--- a/core/java/android/view/IMagnificationCallbacks.aidl
+++ b/core/java/android/app/trust/ITrustListener.aidl
@@ -1,5 +1,6 @@
/*
-** Copyright 2012, The Android Open Source Project
+**
+** 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.
@@ -13,17 +14,13 @@
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-
-package android.view;
-
-import android.graphics.Region;
+package android.app.trust;
/**
+ * Private API to be notified about trust changes.
+ *
* {@hide}
*/
-oneway interface IMagnificationCallbacks {
- void onMagnifedBoundsChanged(in Region bounds);
- void onRectangleOnScreenRequested(int left, int top, int right, int bottom);
- void onRotationChanged(int rotation);
- void onUserContextChanged();
-}
+oneway interface ITrustListener {
+ void onTrustChanged(boolean enabled, int userId);
+} \ No newline at end of file
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
new file mode 100644
index 0000000..ad4ccbb
--- /dev/null
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -0,0 +1,31 @@
+/*
+**
+** 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.trust;
+
+import android.app.trust.ITrustListener;
+
+/**
+ * System private API to comunicate with trust service.
+ *
+ * {@hide}
+ */
+interface ITrustManager {
+ void reportUnlockAttempt(boolean successful, int userId);
+ void reportEnabledTrustAgentsChanged(int userId);
+ void registerTrustListener(in ITrustListener trustListener);
+ void unregisterTrustListener(in ITrustListener trustListener);
+}
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
new file mode 100644
index 0000000..e31c624
--- /dev/null
+++ b/core/java/android/app/trust/TrustManager.java
@@ -0,0 +1,134 @@
+/*
+ * 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.trust;
+
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+
+/**
+ * See {@link com.android.server.trust.TrustManagerService}
+ * @hide
+ */
+public class TrustManager {
+
+ private static final int MSG_TRUST_CHANGED = 1;
+
+ private static final String TAG = "TrustManager";
+
+ private final ITrustManager mService;
+ private final ArrayMap<TrustListener, ITrustListener> mTrustListeners;
+
+ public TrustManager(IBinder b) {
+ mService = ITrustManager.Stub.asInterface(b);
+ mTrustListeners = new ArrayMap<TrustListener, ITrustListener>();
+ }
+
+ /**
+ * Reports that user {@param userId} has tried to unlock the device.
+ *
+ * @param successful if true, the unlock attempt was successful.
+ *
+ * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+ */
+ public void reportUnlockAttempt(boolean successful, int userId) {
+ try {
+ mService.reportUnlockAttempt(successful, userId);
+ } catch (RemoteException e) {
+ onError(e);
+ }
+ }
+
+ /**
+ * Reports that the list of enabled trust agents changed for user {@param userId}.
+ *
+ * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+ */
+ public void reportEnabledTrustAgentsChanged(int userId) {
+ try {
+ mService.reportEnabledTrustAgentsChanged(userId);
+ } catch (RemoteException e) {
+ onError(e);
+ }
+ }
+
+ /**
+ * Registers a listener for trust events.
+ *
+ * Requires the {@link android.Manifest.permission#TRUST_LISTENER} permission.
+ */
+ public void registerTrustListener(final TrustListener trustListener) {
+ try {
+ ITrustListener.Stub iTrustListener = new ITrustListener.Stub() {
+ @Override
+ public void onTrustChanged(boolean enabled, int userId) throws RemoteException {
+ mHandler.obtainMessage(MSG_TRUST_CHANGED, (enabled ? 1 : 0), userId,
+ trustListener).sendToTarget();
+ }
+ };
+ mService.registerTrustListener(iTrustListener);
+ mTrustListeners.put(trustListener, iTrustListener);
+ } catch (RemoteException e) {
+ onError(e);
+ }
+ }
+
+ /**
+ * Unregisters a listener for trust events.
+ *
+ * Requires the {@link android.Manifest.permission#TRUST_LISTENER} permission.
+ */
+ public void unregisterTrustListener(final TrustListener trustListener) {
+ ITrustListener iTrustListener = mTrustListeners.remove(trustListener);
+ if (iTrustListener != null) {
+ try {
+ mService.unregisterTrustListener(iTrustListener);
+ } catch (RemoteException e) {
+ onError(e);
+ }
+ }
+ }
+
+ private void onError(Exception e) {
+ Log.e(TAG, "Error while calling TrustManagerService", e);
+ }
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_TRUST_CHANGED:
+ ((TrustListener)msg.obj).onTrustChanged(msg.arg1 != 0, msg.arg2);
+ break;
+ }
+ }
+ };
+
+ public interface TrustListener {
+
+ /**
+ * Reports that the trust state has changed.
+ * @param enabled if true, the system believes the environment to be trusted.
+ * @param userId the user, for which the trust changed.
+ */
+ void onTrustChanged(boolean enabled, int userId);
+ }
+}
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..dd3a871 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;
@@ -215,6 +214,12 @@ public class AppWidgetManager {
public static final String EXTRA_CUSTOM_INFO = "customInfo";
/**
+ * An intent extra attached to the {@link #ACTION_APPWIDGET_HOST_RESTORED} broadcast,
+ * indicating the integer ID of the host whose widgets have just been restored.
+ */
+ public static final String EXTRA_HOST_ID = "hostId";
+
+ /**
* An intent extra to pass to the AppWidget picker containing a {@link java.util.List} of
* {@link android.os.Bundle} objects to mix in to the list of AppWidgets that are
* installed. It will be added to the extras object on the {@link android.content.Intent}
@@ -311,6 +316,86 @@ public class AppWidgetManager {
public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED";
/**
+ * Sent to providers after AppWidget state related to the provider has been restored from
+ * backup. The intent contains information about how to translate AppWidget ids from the
+ * restored data to their new equivalents.
+ *
+ * <p>The intent will contain the following extras:
+ *
+ * <table>
+ * <tr>
+ * <td>{@link #EXTRA_APPWIDGET_OLD_IDS}</td>
+ * <td>The set of appWidgetIds represented in a restored backup that have been successfully
+ * incorporated into the current environment. This may be all of the AppWidgets known
+ * to this application, or just a subset. Each entry in this array of appWidgetIds has
+ * a corresponding entry in the {@link #EXTRA_APPWIDGET_IDS} extra.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXTRA_APPWIDGET_IDS}</td>
+ * <td>The set of appWidgetIds now valid for this application. The app should look at
+ * its restored widget configuration and translate each appWidgetId in the
+ * {@link #EXTRA_APPWIDGET_OLD_IDS} array to its new value found at the corresponding
+ * index within this array.</td>
+ * </tr>
+ * </table>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @see {@link #ACTION_APPWIDGET_HOST_RESTORED} for the corresponding host broadcast
+ */
+ public static final String ACTION_APPWIDGET_RESTORED
+ = "android.appwidget.action.APPWIDGET_RESTORED";
+
+ /**
+ * Sent to widget hosts after AppWidget state related to the host has been restored from
+ * backup. The intent contains information about how to translate AppWidget ids from the
+ * restored data to their new equivalents. If an application maintains multiple separate
+ * widget hosts instances, it will receive this broadcast separately for each one.
+ *
+ * <p>The intent will contain the following extras:
+ *
+ * <table>
+ * <tr>
+ * <td>{@link #EXTRA_APPWIDGET_OLD_IDS}</td>
+ * <td>The set of appWidgetIds represented in a restored backup that have been successfully
+ * incorporated into the current environment. This may be all of the AppWidgets known
+ * to this application, or just a subset. Each entry in this array of appWidgetIds has
+ * a corresponding entry in the {@link #EXTRA_APPWIDGET_IDS} extra.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXTRA_APPWIDGET_IDS}</td>
+ * <td>The set of appWidgetIds now valid for this application. The app should look at
+ * its restored widget configuration and translate each appWidgetId in the
+ * {@link #EXTRA_APPWIDGET_OLD_IDS} array to its new value found at the corresponding
+ * index within this array.</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXTRA_HOST_ID}</td>
+ * <td>The integer ID of the widget host instance whose state has just been restored.</td>
+ * </tr>
+ * </table>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @see {@link #ACTION_APPWIDGET_RESTORED} for the corresponding provider broadcast
+ */
+ public static final String ACTION_APPWIDGET_HOST_RESTORED
+ = "android.appwidget.action.APPWIDGET_HOST_RESTORED";
+
+ /**
+ * An intent extra that contains multiple appWidgetIds. These are id values as
+ * they were provided to the application during a recent restore from backup. It is
+ * attached to the {@link #ACTION_APPWIDGET_RESTORED} broadcast intent.
+ *
+ * <p>
+ * The value will be an int array that can be retrieved like this:
+ * {@sample frameworks/base/tests/appwidgets/AppWidgetHostTest/src/com/android/tests/appwidgethost/TestAppWidgetProvider.java getExtra_EXTRA_APPWIDGET_IDS}
+ */
+ public static final String EXTRA_APPWIDGET_OLD_IDS = "appWidgetOldIds";
+
+ /**
* Field for the manifest meta-data tag.
*
* @see AppWidgetProviderInfo
diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java
index edf142b..ab91edf 100644
--- a/core/java/android/appwidget/AppWidgetProvider.java
+++ b/core/java/android/appwidget/AppWidgetProvider.java
@@ -66,15 +66,13 @@ public class AppWidgetProvider extends BroadcastReceiver {
this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
}
}
- }
- else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
+ } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
this.onDeleted(context, new int[] { appWidgetId });
}
- }
- else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
+ } else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)
&& extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {
@@ -83,19 +81,28 @@ public class AppWidgetProvider extends BroadcastReceiver {
this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
appWidgetId, widgetExtras);
}
- }
- else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
+ } else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
this.onEnabled(context);
- }
- else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
+ } else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
this.onDisabled(context);
+ } else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
+ int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
+ if (oldIds != null && oldIds.length > 0) {
+ this.onRestored(context, oldIds, newIds);
+ this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);
+ }
+ }
}
}
// END_INCLUDE(onReceive)
/**
- * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} broadcast when
- * this AppWidget provider is being asked to provide {@link android.widget.RemoteViews RemoteViews}
+ * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} and
+ * {@link AppWidgetManager#ACTION_APPWIDGET_RESTORED} broadcasts when this AppWidget
+ * provider is being asked to provide {@link android.widget.RemoteViews RemoteViews}
* for a set of AppWidgets. Override this method to implement your own AppWidget functionality.
*
* {@more}
@@ -123,8 +130,8 @@ public class AppWidgetProvider extends BroadcastReceiver {
* running.
* @param appWidgetManager A {@link AppWidgetManager} object you can call {@link
* AppWidgetManager#updateAppWidget} on.
- * @param appWidgetId The appWidgetId of the widget who's size changed.
- * @param newOptions The appWidgetId of the widget who's size changed.
+ * @param appWidgetId The appWidgetId of the widget whose size changed.
+ * @param newOptions The appWidgetId of the widget whose size changed.
*
* @see AppWidgetManager#ACTION_APPWIDGET_OPTIONS_CHANGED
*/
@@ -181,4 +188,24 @@ public class AppWidgetProvider extends BroadcastReceiver {
*/
public void onDisabled(Context context) {
}
+
+ /**
+ * Called in response to the {@link AppWidgetManager#ACTION_APPWIDGET_RESTORED} broadcast
+ * when instances of this AppWidget provider have been restored from backup. If your
+ * provider maintains any persistent data about its widget instances, override this method
+ * to remap the old AppWidgetIds to the new values and update any other app state that may
+ * be relevant.
+ *
+ * <p>This callback will be followed immediately by a call to {@link #onUpdate} so your
+ * provider can immediately generate new RemoteViews suitable for its newly-restored set
+ * of instances.
+ *
+ * {@more}
+ *
+ * @param context
+ * @param oldWidgetIds
+ * @param newWidgetIds
+ */
+ public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
+ }
}
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/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 6f929f2..7b709ac 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -162,7 +162,8 @@ public final class BluetoothA2dp implements BluetoothProfile {
Intent intent = new Intent(IBluetoothA2dp.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth A2DP Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 646be06..e79deec 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -19,11 +19,9 @@ package android.bluetooth;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
-import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.Message;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -39,7 +37,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 e7ab8de..ff3af7c 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -16,20 +16,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/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 8ee955d..f88a173 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -280,7 +280,8 @@ public final class BluetoothHeadset implements BluetoothProfile {
Intent intent = new Intent(IBluetoothHeadset.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth Headset Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java
index 2e950fa..4949c24 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;
@@ -488,7 +487,8 @@ public final class BluetoothHealth implements BluetoothProfile {
Intent intent = new Intent(IBluetoothHealth.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth Health Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java
index c48b15d..554df3e 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;
@@ -260,7 +259,8 @@ public final class BluetoothInputDevice implements BluetoothProfile {
Intent intent = new Intent(IBluetoothInputDevice.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth HID Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 92a2f1e..7f57acf 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -22,9 +22,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.os.RemoteException;
-import android.os.IBinder;
-import android.os.ServiceManager;
+import android.os.*;
import android.util.Log;
/**
@@ -106,7 +104,8 @@ public final class BluetoothMap implements BluetoothProfile {
Intent intent = new Intent(IBluetoothMap.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth MAP Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index b7a37f4..4f81f98 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;
@@ -146,7 +145,8 @@ public final class BluetoothPan implements BluetoothProfile {
Intent intent = new Intent(IBluetoothPan.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth Pan Service with " + intent);
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index 7f45652..dc01fc7 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;
/**
@@ -161,7 +160,8 @@ public class BluetoothPbap {
Intent intent = new Intent(IBluetoothPbap.class.getName());
ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
intent.setComponent(comp);
- if (comp == null || !mContext.bindService(intent, mConnection, 0)) {
+ if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+ android.os.Process.myUserHandle())) {
Log.e(TAG, "Could not bind to Bluetooth Pbap Service with " + intent);
return false;
}
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 5057cf4..ff92d82 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,
@@ -222,6 +242,16 @@ public abstract class Context {
public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0080;
/**
+ * @hide Flag for {@link #bindService}: Treat the binding as hosting
+ * an activity, an unbinding as the activity going in the background.
+ * That is, when unbinding, the process when empty will go on the activity
+ * LRU list instead of the regular one, keeping it around more aggressively
+ * than it otherwise would be. This is intended for use with IMEs to try
+ * to keep IME processes around for faster keyboard switching.
+ */
+ public static final int BIND_TREAT_LIKE_ACTIVITY = 0x08000000;
+
+ /**
* @hide An idea that is not yet implemented.
* Flag for {@link #bindService}: If binding from an activity, consider
* this service to be visible like the binding activity is. That is,
@@ -356,6 +386,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 +538,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
@@ -680,7 +723,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
@@ -713,7 +757,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);
@@ -777,7 +821,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();
@@ -846,6 +890,7 @@ public abstract class Context {
*
* @see #getCacheDir
*/
+ @Nullable
public abstract File getExternalCacheDir();
/**
@@ -879,7 +924,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();
@@ -966,7 +1011,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
@@ -1112,7 +1158,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
@@ -1128,7 +1174,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.");
}
@@ -1247,7 +1293,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;
/**
@@ -1297,11 +1343,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,
@@ -1328,7 +1374,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
@@ -1372,15 +1418,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,
@@ -1415,7 +1461,7 @@ public abstract class Context {
* @see #sendBroadcast(Intent, String)
*/
public abstract void sendBroadcastAsUser(Intent intent, UserHandle user,
- String receiverPermission);
+ @Nullable String receiverPermission);
/**
* Version of
@@ -1448,8 +1494,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
@@ -1514,8 +1561,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},
@@ -1575,8 +1622,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
@@ -1643,7 +1690,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);
/**
@@ -1677,8 +1725,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
@@ -1704,9 +1754,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>
@@ -1765,6 +1816,7 @@ public abstract class Context {
* @see #stopService
* @see #bindService
*/
+ @Nullable
public abstract ComponentName startService(Intent service);
/**
@@ -1852,8 +1904,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
@@ -1874,7 +1926,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
@@ -1899,8 +1951,68 @@ 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_HOTSPOT_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,
+ LAUNCHER_APPS_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,
+ BATTERY_SERVICE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ServiceName {}
/**
* Return the handle to a system-level service by name. The class of the
@@ -1949,6 +2061,8 @@ public abstract class Context {
* <dd> An {@link android.app.UiModeManager} for controlling UI modes.
* <dt> {@link #DOWNLOAD_SERVICE} ("download")
* <dd> A {@link android.app.DownloadManager} for requesting HTTP downloads
+ * <dt> {@link #BATTERY_SERVICE} ("batterymanager")
+ * <dd> A {@link android.os.BatteryManager} for managing battery state
* </dl>
*
* <p>Note: System services obtained via this API may be closely associated with
@@ -2002,8 +2116,10 @@ public abstract class Context {
* @see android.app.UiModeManager
* @see #DOWNLOAD_SERVICE
* @see android.app.DownloadManager
+ * @see #BATTERY_SERVICE
+ * @see android.os.BatteryManager
*/
- public abstract Object getSystemService(String name);
+ public abstract Object getSystemService(@ServiceName @NonNull String name);
/**
* Use with {@link #getSystemService} to retrieve a
@@ -2221,6 +2337,16 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.wifi.hotspot.WifiHotspotManager} for handling management of
+ * Wi-Fi hotspot access.
+ *
+ * @see #getSystemService
+ * @see android.net.wifi.hotspot.WifiHotspotManager
+ */
+ public static final String WIFI_HOTSPOT_SERVICE = "wifihotspot";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
* android.net.wifi.p2p.WifiP2pManager} for handling management of
* Wi-Fi peer-to-peer connections.
*
@@ -2261,6 +2387,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.
*
@@ -2351,6 +2486,14 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.os.BatteryManager} for managing battery state.
+ *
+ * @see #getSystemService
+ */
+ public static final String BATTERY_SERVICE = "batterymanager";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.nfc.NfcManager} for using NFC.
*
* @see #getSystemService
@@ -2434,6 +2577,16 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.content.pm.LauncherApps} for querying and monitoring launchable apps across
+ * profiles of a user.
+ *
+ * @see #getSystemService
+ * @see android.content.pm.LauncherApps
+ */
+ public static final String LAUNCHER_APPS_SERVICE = "launcherapps";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.app.AppOpsManager} for tracking application operations
* on the device.
*
@@ -2449,7 +2602,6 @@ public abstract class Context {
*
* @see #getSystemService
* @see android.hardware.camera2.CameraManager
- * @hide
*/
public static final String CAMERA_SERVICE = "camera";
@@ -2473,6 +2625,32 @@ public abstract class Context {
public static final String CONSUMER_IR_SERVICE = "consumer_ir";
/**
+ * {@link android.app.trust.TrustManager} for managing trust agents.
+ * @see #getSystemService
+ * @see android.app.trust.TrustManager
+ * @hide
+ */
+ public static final String TRUST_SERVICE = "trust";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.tv.TvInputManager} for interacting with TV inputs on the
+ * device.
+ *
+ * @see #getSystemService
+ * @see android.tv.TvInputManager
+ */
+ public static final String TV_INPUT_SERVICE = "tv_input";
+
+ /**
+ * {@link android.net.NetworkScoreManager} for managing network scoring.
+ * @see #getSystemService
+ * @see android.net.NetworkScoreManager
+ * @hide
+ */
+ public static final String NETWORK_SCORE_SERVICE = "network_score";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
@@ -2488,7 +2666,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
@@ -2511,7 +2690,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
@@ -2529,7 +2709,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
@@ -2544,7 +2725,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
@@ -2565,7 +2746,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
@@ -2581,7 +2762,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
@@ -2617,7 +2798,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
@@ -2636,7 +2817,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
@@ -2659,7 +2840,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
@@ -2682,7 +2864,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
@@ -2701,7 +2883,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
@@ -2713,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.
@@ -2725,8 +2908,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
@@ -2748,7 +2932,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
@@ -2770,7 +2954,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
@@ -2789,7 +2973,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
@@ -2801,7 +2985,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.
@@ -2813,8 +2997,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
@@ -2872,7 +3063,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
@@ -2908,7 +3099,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
@@ -2928,7 +3120,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 95e27e2..a7d5606 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;
@@ -2193,6 +2196,11 @@ public class Intent implements Parcelable, Cloneable {
/**
* Broadcast Action: Wired Headset plugged in or unplugged.
*
+ * You <em>cannot</em> receive this through components declared
+ * in manifests, only by explicitly registering for it with
+ * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * Context.registerReceiver()}.
+ *
* <p>The intent will have the following extra values:
* <ul>
* <li><em>state</em> - 0 for unplugged, 1 for plugged. </li>
@@ -2623,6 +2631,23 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.USER_INFO_CHANGED";
/**
+ * Broadcast sent to the primary user when an associated managed profile is added (the profile
+ * was created and is ready to be used). Carries an extra {@link #EXTRA_USER} that specifies
+ * the UserHandle of the profile that was added. This is only sent to registered receivers,
+ * not manifest receivers.
+ */
+ public static final String ACTION_MANAGED_PROFILE_ADDED =
+ "android.intent.action.MANAGED_PROFILE_ADDED";
+
+ /**
+ * Broadcast sent to the primary user when an associated managed profile is removed. Carries an
+ * extra {@link #EXTRA_USER} that specifies the UserHandle of the profile that was removed. This
+ * is only sent to registered receivers, not manifest receivers.
+ */
+ public static final String ACTION_MANAGED_PROFILE_REMOVED =
+ "android.intent.action.MANAGED_PROFILE_REMOVED";
+
+ /**
* Sent when the user taps on the clock widget in the system's "quick settings" area.
*/
public static final String ACTION_QUICK_CLOCK =
@@ -2899,6 +2924,14 @@ public class Intent implements Parcelable, Cloneable {
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_CAR_MODE = "android.intent.category.CAR_MODE";
+ /**
+ * An activity that provides a user interface for adjusting notification preferences for its
+ * containing application. Optional but recommended for apps that post
+ * {@link android.app.Notification Notifications}.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_NOTIFICATION_PREFERENCES = "android.intent.category.NOTIFICATION_PREFERENCES";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Application launch intent categories (see addCategory()).
@@ -3308,15 +3341,23 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.extra.ALLOW_MULTIPLE";
/**
- * The userHandle carried with broadcast intents related to addition, removal and switching of
- * users
- * - {@link #ACTION_USER_ADDED}, {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
+ * The integer userHandle carried with broadcast intents related to addition, removal and
+ * switching of users and managed profiles - {@link #ACTION_USER_ADDED},
+ * {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
+ *
* @hide
*/
public static final String EXTRA_USER_HANDLE =
"android.intent.extra.user_handle";
/**
+ * The UserHandle carried with broadcasts intents related to addition and removal of managed
+ * profiles - {@link #ACTION_MANAGED_PROFILE_ADDED} and {@link #ACTION_MANAGED_PROFILE_REMOVED}.
+ */
+ public static final String EXTRA_USER =
+ "android.intent.extra.user";
+
+ /**
* Extra used in the response from a BroadcastReceiver that handles
* {@link #ACTION_GET_RESTRICTION_ENTRIES}. The type of the extra is
* <code>ArrayList&lt;RestrictionEntry&gt;</code>.
@@ -3373,6 +3414,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
@@ -3471,7 +3518,16 @@ public class Intent implements Parcelable, Cloneable {
*/
public static final int FLAG_ACTIVITY_NEW_TASK = 0x10000000;
/**
- * <strong>Do not use this flag unless you are implementing your own
+ * This flag is used to create a new task and launch an activity into it.
+ * This flag is always paired with either {@link #FLAG_ACTIVITY_NEW_DOCUMENT}
+ * or {@link #FLAG_ACTIVITY_NEW_TASK}. In both cases these flags alone would
+ * search through existing tasks for ones matching this Intent. Only if no such
+ * task is found would a new task be created. When paired with
+ * FLAG_ACTIVITY_MULTIPLE_TASK both of these behaviors are modified to skip
+ * the search for a matching task and unconditionally start a new task.
+ *
+ * <strong>When used with {@link #FLAG_ACTIVITY_NEW_TASK} do not use this
+ * flag unless you are implementing your own
* top-level application launcher.</strong> Used in conjunction with
* {@link #FLAG_ACTIVITY_NEW_TASK} to disable the
* behavior of bringing an existing task to the foreground. When set,
@@ -3483,12 +3539,18 @@ public class Intent implements Parcelable, Cloneable {
* you should not use this flag unless you provide some way for a user to
* return back to the tasks you have launched.</strong>
*
- * <p>This flag is ignored if
- * {@link #FLAG_ACTIVITY_NEW_TASK} is not set.
+ * See {@link #FLAG_ACTIVITY_NEW_DOCUMENT} for details of this flag's use for
+ * creating new document tasks.
+ *
+ * <p>This flag is ignored if one of {@link #FLAG_ACTIVITY_NEW_TASK} or
+ * {@link #FLAG_ACTIVITY_NEW_TASK} is not also set.
*
* <p>See
* <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
* Stack</a> for more information about tasks.
+ *
+ * @see #FLAG_ACTIVITY_NEW_DOCUMENT
+ * @see #FLAG_ACTIVITY_NEW_TASK
*/
public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 0x08000000;
/**
@@ -3595,6 +3657,34 @@ public class Intent implements Parcelable, Cloneable {
*/
public static final int FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET = 0x00080000;
/**
+ * This flag is used to break out "documents" into separate tasks that can
+ * be reached via the Recents mechanism. Such a document is any kind of
+ * item for which an application may want to maintain multiple simultaneous
+ * instances. Examples might be text files, web pages, spreadsheets, or
+ * emails. Each such document will be in a separate task in the Recents list.
+ *
+ * <p>When set, the activity specified by this Intent will launch into a
+ * separate task rooted at that activity. The activity launched must be
+ * defined with {@link android.R.attr#launchMode} "standard" or "singleTop".
+ *
+ * <p>If FLAG_ACTIVITY_NEW_DOCUMENT is used without
+ * {@link #FLAG_ACTIVITY_MULTIPLE_TASK} then the activity manager will
+ * search for an existing task with a matching target activity and Intent
+ * data URI and relaunch that task, first finishing all activities down to
+ * the root activity and then calling the root activity's
+ * {@link android.app.Activity#onNewIntent(Intent)} method. If no existing
+ * task's root activity matches the Intent's data URI then a new task will
+ * be launched with the target activity as root.
+ *
+ * <p>When paired with {@link #FLAG_ACTIVITY_MULTIPLE_TASK} this will
+ * always create a new task. Thus the same document may be made to appear
+ * more than one time in Recents.
+ *
+ * @see #FLAG_ACTIVITY_MULTIPLE_TASK
+ */
+ public static final int FLAG_ACTIVITY_NEW_DOCUMENT =
+ FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | FLAG_ACTIVITY_NEW_TASK;
+ /**
* If set, this flag will prevent the normal {@link android.app.Activity#onUserLeaveHint}
* callback from occurring on the current frontmost activity before it is
* paused as the newly-started activity is brought to the front.
@@ -6260,6 +6350,7 @@ public class Intent implements Parcelable, Cloneable {
* @see #FLAG_ACTIVITY_FORWARD_RESULT
* @see #FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
* @see #FLAG_ACTIVITY_MULTIPLE_TASK
+ * @see #FLAG_ACTIVITY_NEW_DOCUMENT
* @see #FLAG_ACTIVITY_NEW_TASK
* @see #FLAG_ACTIVITY_NO_ANIMATION
* @see #FLAG_ACTIVITY_NO_HISTORY
@@ -6417,6 +6508,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.
@@ -6510,10 +6616,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)) {
@@ -7339,4 +7447,9 @@ public class Intent implements Parcelable, Cloneable {
String htmlText = htmlTexts != null ? htmlTexts.get(which) : null;
return new ClipData.Item(text, htmlText, null, uri);
}
+
+ /** @hide */
+ public boolean isDocument() {
+ return (mFlags & FLAG_ACTIVITY_NEW_DOCUMENT) == FLAG_ACTIVITY_NEW_DOCUMENT;
+ }
}
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..9916476 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
@@ -185,6 +189,13 @@ public class ActivityInfo extends ComponentInfo
*/
public static final int FLAG_IMMERSIVE = 0x0800;
/**
+ * Bit in {@link #flags} indicating that this activity is to be persisted across
+ * reboots for display in the Recents list.
+ * {@link android.R.attr#persistable}
+ * @hide
+ */
+ public static final int FLAG_PERSISTABLE = 0x1000;
+ /**
* @hide Bit in {@link #flags}: If set, this component will only be seen
* by the primary user. Only works with broadcast receivers. Set from the
* {@link android.R.attr#primaryUserOnly} attribute.
@@ -212,6 +223,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 +356,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 0172509..0d1b262 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -344,7 +344,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}
@@ -471,7 +471,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.
@@ -523,7 +529,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);
}
@@ -592,6 +599,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;
@@ -633,6 +641,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);
@@ -673,6 +682,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/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
new file mode 100644
index 0000000..796b113
--- /dev/null
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -0,0 +1,38 @@
+/**
+ * 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.content.pm;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.ResolveInfo;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.UserHandle;
+import java.util.List;
+
+/**
+ * {@hide}
+ */
+interface ILauncherApps {
+ void addOnAppsChangedListener(in IOnAppsChangedListener listener);
+ void removeOnAppsChangedListener(in IOnAppsChangedListener listener);
+ List<ResolveInfo> getLauncherActivities(String packageName, in UserHandle user);
+ ResolveInfo resolveActivity(in Intent intent, in UserHandle user);
+ void startActivityAsUser(in ComponentName component, in Rect sourceBounds,
+ in Bundle opts, in UserHandle user);
+}
diff --git a/core/java/android/content/pm/IOnAppsChangedListener.aidl b/core/java/android/content/pm/IOnAppsChangedListener.aidl
new file mode 100644
index 0000000..796b58d
--- /dev/null
+++ b/core/java/android/content/pm/IOnAppsChangedListener.aidl
@@ -0,0 +1,30 @@
+/**
+ * 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.content.pm;
+
+import android.os.UserHandle;
+
+/**
+ * {@hide}
+ */
+oneway interface IOnAppsChangedListener {
+ void onPackageRemoved(in UserHandle user, String packageName);
+ void onPackageAdded(in UserHandle user, String packageName);
+ void onPackageChanged(in UserHandle user, String packageName);
+ void onPackagesAvailable(in UserHandle user, in String[] packageNames, boolean replacing);
+ void onPackagesUnavailable(in UserHandle user, in String[] packageNames, boolean replacing);
+}
diff --git a/core/java/android/content/pm/IPackageInstallObserver2.aidl b/core/java/android/content/pm/IPackageInstallObserver2.aidl
new file mode 100644
index 0000000..2602ab5
--- /dev/null
+++ b/core/java/android/content/pm/IPackageInstallObserver2.aidl
@@ -0,0 +1,45 @@
+/*
+**
+** 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.content.pm;
+
+import android.os.Bundle;
+
+/**
+ * API for installation callbacks from the Package Manager. In certain result cases
+ * additional information will be provided.
+ * @hide
+ */
+oneway interface IPackageInstallObserver2 {
+ /**
+ * The install operation has completed. {@code returnCode} holds a numeric code
+ * indicating success or failure. In certain cases the {@code extras} Bundle will
+ * contain additional details:
+ *
+ * <p><table>
+ * <tr>
+ * <td>INSTALL_FAILED_DUPLICATE_PERMISSION</td>
+ * <td>Two strings are provided in the extras bundle: EXTRA_EXISTING_PERMISSION
+ * is the name of the permission that the app is attempting to define, and
+ * EXTRA_EXISTING_PACKAGE is the package name of the app which has already
+ * defined the permission.</td>
+ * </tr>
+ * </table>
+ */
+ void packageInstalled(in String packageName, in Bundle extras, int returnCode);
+}
+
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 20002ad..ae0899f 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -25,6 +25,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.ContainerEncryptionParams;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageInstallObserver;
+import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageMoveObserver;
@@ -237,6 +238,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.
@@ -402,6 +407,21 @@ interface IPackageManager {
in VerificationParams verificationParams,
in ContainerEncryptionParams encryptionParams);
+ /** Expanded observer versions */
+ void installPackageEtc(in Uri packageURI, IPackageInstallObserver observer,
+ IPackageInstallObserver2 observer2, int flags, in String installerPackageName);
+
+ void installPackageWithVerificationEtc(in Uri packageURI,
+ in IPackageInstallObserver observer, IPackageInstallObserver2 observer2,
+ int flags, in String installerPackageName, in Uri verificationURI,
+ in ManifestDigest manifestDigest, in ContainerEncryptionParams encryptionParams);
+
+ void installPackageWithVerificationAndEncryptionEtc(in Uri packageURI,
+ in IPackageInstallObserver observer, in IPackageInstallObserver2 observer2,
+ int flags, in String installerPackageName,
+ in VerificationParams verificationParams,
+ in ContainerEncryptionParams encryptionParams);
+
int installExistingPackageAsUser(String packageName, int userId);
void verifyPendingInstall(int id, int verificationCode);
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
new file mode 100644
index 0000000..92b9146
--- /dev/null
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -0,0 +1,146 @@
+/*
+ * 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.content.pm;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Bitmap.Config;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+/**
+ * A representation of an activity that can belong to this user or a managed
+ * profile associated with this user. It can be used to query the label, icon
+ * and badged icon for the activity.
+ */
+public class LauncherActivityInfo {
+ private static final boolean DEBUG = false;
+ private final PackageManager mPm;
+ private final UserManager mUm;
+
+ private ActivityInfo mActivityInfo;
+ private ComponentName mComponentName;
+ private UserHandle mUser;
+ // TODO: Fetch this value from PM
+ private long mFirstInstallTime;
+
+ /**
+ * Create a launchable activity object for a given ResolveInfo and user.
+ *
+ * @param context The context for fetching resources.
+ * @param info ResolveInfo from which to create the LauncherActivityInfo.
+ * @param user The UserHandle of the profile to which this activity belongs.
+ */
+ LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user) {
+ this(context);
+ this.mActivityInfo = info.activityInfo;
+ this.mComponentName = LauncherApps.getComponentName(info);
+ this.mUser = user;
+ }
+
+ LauncherActivityInfo(Context context) {
+ mPm = context.getPackageManager();
+ mUm = UserManager.get(context);
+ }
+
+ /**
+ * Returns the component name of this activity.
+ *
+ * @return ComponentName of the activity
+ */
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
+ * Returns the user handle of the user profile that this activity belongs to.
+ *
+ * @return The UserHandle of the profile.
+ */
+ public UserHandle getUser() {
+ return mUser;
+ }
+
+ /**
+ * Retrieves the label for the activity.
+ *
+ * @return The label for the activity.
+ */
+ public CharSequence getLabel() {
+ return mActivityInfo.loadLabel(mPm);
+ }
+
+ /**
+ * Returns the icon for this activity, without any badging for the profile.
+ * @param density The preferred density of the icon, zero for default density.
+ * @see #getBadgedIcon(int)
+ * @return The drawable associated with the activity
+ */
+ public Drawable getIcon(int density) {
+ // TODO: Use density
+ return mActivityInfo.loadIcon(mPm);
+ }
+
+ /**
+ * Returns the application flags from the ApplicationInfo of the activity.
+ *
+ * @return Application flags
+ */
+ public int getApplicationFlags() {
+ return mActivityInfo.applicationInfo.flags;
+ }
+
+ /**
+ * Returns the time at which the package was first installed.
+ * @return The time of installation of the package, in milliseconds.
+ */
+ public long getFirstInstallTime() {
+ return mFirstInstallTime;
+ }
+
+ /**
+ * Returns the activity icon with badging appropriate for the profile.
+ * @param density Optional density for the icon, or 0 to use the default density.
+ * @return A badged icon for the activity.
+ */
+ public Drawable getBadgedIcon(int density) {
+ // TODO: Handle density
+ if (mUser.equals(android.os.Process.myUserHandle())) {
+ return mActivityInfo.loadIcon(mPm);
+ }
+ Drawable originalIcon = mActivityInfo.loadIcon(mPm);
+ if (originalIcon == null) {
+ if (DEBUG) {
+ Log.w("LauncherActivityInfo", "Couldn't find icon for activity");
+ }
+ originalIcon = mPm.getDefaultActivityIcon();
+ }
+ if (originalIcon instanceof BitmapDrawable) {
+ return mUm.getBadgedDrawableForUser(
+ originalIcon, mUser);
+ }
+ return originalIcon;
+ }
+}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
new file mode 100644
index 0000000..5187181
--- /dev/null
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -0,0 +1,286 @@
+/*
+ * 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.content.pm;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ILauncherApps;
+import android.content.pm.IOnAppsChangedListener;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Class for retrieving a list of launchable activities for the current user and any associated
+ * managed profiles. This is mainly for use by launchers. Apps can be queried for each user profile.
+ * Since the PackageManager will not deliver package broadcasts for other profiles, you can register
+ * for package changes here.
+ */
+public class LauncherApps {
+
+ static final String TAG = "LauncherApps";
+ static final boolean DEBUG = false;
+
+ private Context mContext;
+ private ILauncherApps mService;
+
+ private List<OnAppsChangedListener> mListeners
+ = new ArrayList<OnAppsChangedListener>();
+
+ /**
+ * Callbacks for changes to this and related managed profiles.
+ */
+ public interface OnAppsChangedListener {
+ /**
+ * Indicates that a package was removed from the specified profile.
+ *
+ * @param user The UserHandle of the profile that generated the change.
+ * @param packageName The name of the package that was removed.
+ */
+ void onPackageRemoved(UserHandle user, String packageName);
+
+ /**
+ * Indicates that a package was added to the specified profile.
+ *
+ * @param user The UserHandle of the profile that generated the change.
+ * @param packageName The name of the package that was added.
+ */
+ void onPackageAdded(UserHandle user, String packageName);
+
+ /**
+ * Indicates that a package was modified in the specified profile.
+ *
+ * @param user The UserHandle of the profile that generated the change.
+ * @param packageName The name of the package that has changed.
+ */
+ void onPackageChanged(UserHandle user, String packageName);
+
+ /**
+ * Indicates that one or more packages have become available. For
+ * example, this can happen when a removable storage card has
+ * reappeared.
+ *
+ * @param user The UserHandle of the profile that generated the change.
+ * @param packageNames The names of the packages that have become
+ * available.
+ * @param replacing Indicates whether these packages are replacing
+ * existing ones.
+ */
+ void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing);
+
+ /**
+ * Indicates that one or more packages have become unavailable. For
+ * example, this can happen when a removable storage card has been
+ * removed.
+ *
+ * @param user The UserHandle of the profile that generated the change.
+ * @param packageNames The names of the packages that have become
+ * unavailable.
+ * @param replacing Indicates whether the packages are about to be
+ * replaced with new versions.
+ */
+ void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing);
+ }
+
+ /** @hide */
+ public LauncherApps(Context context, ILauncherApps service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Retrieves a list of launchable activities that match {@link Intent#ACTION_MAIN} and
+ * {@link Intent#CATEGORY_LAUNCHER}, for a specified user.
+ *
+ * @param packageName The specific package to query. If null, it checks all installed packages
+ * in the profile.
+ * @param user The UserHandle of the profile.
+ * @return List of launchable activities. Can be an empty list but will not be null.
+ */
+ public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
+ List<ResolveInfo> activities = null;
+ try {
+ activities = mService.getLauncherActivities(packageName, user);
+ } catch (RemoteException re) {
+ }
+ if (activities == null) {
+ return Collections.EMPTY_LIST;
+ }
+ ArrayList<LauncherActivityInfo> lais = new ArrayList<LauncherActivityInfo>();
+ final int count = activities.size();
+ for (int i = 0; i < count; i++) {
+ ResolveInfo ri = activities.get(i);
+ LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri, user);
+ if (DEBUG) {
+ Log.v(TAG, "Returning activity for profile " + user + " : "
+ + lai.getComponentName());
+ }
+ lais.add(lai);
+ }
+ return lais;
+ }
+
+ static ComponentName getComponentName(ResolveInfo ri) {
+ return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
+ }
+
+ /**
+ * Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
+ * returns null.
+ *
+ * @param intent The intent to find a match for.
+ * @param user The profile to look in for a match.
+ * @return An activity info object if there is a match.
+ */
+ public LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) {
+ try {
+ ResolveInfo ri = mService.resolveActivity(intent, user);
+ if (ri != null) {
+ LauncherActivityInfo info = new LauncherActivityInfo(mContext, ri, user);
+ return info;
+ }
+ } catch (RemoteException re) {
+ return null;
+ }
+ return null;
+ }
+
+ /**
+ * Starts an activity in the specified profile.
+ *
+ * @param component The ComponentName of the activity to launch
+ * @param sourceBounds The Rect containing the source bounds of the clicked icon
+ * @param opts Options to pass to startActivity
+ * @param user The UserHandle of the profile
+ */
+ public void startActivityForProfile(ComponentName component, Rect sourceBounds,
+ Bundle opts, UserHandle user) {
+ if (DEBUG) {
+ Log.i(TAG, "StartActivityForProfile " + component + " " + user.getIdentifier());
+ }
+ try {
+ mService.startActivityAsUser(component, sourceBounds, opts, user);
+ } catch (RemoteException re) {
+ // Oops!
+ }
+ }
+
+ /**
+ * Adds a listener for changes to packages in current and managed profiles.
+ *
+ * @param listener The listener to add.
+ */
+ public synchronized void addOnAppsChangedListener(OnAppsChangedListener listener) {
+ if (listener != null && !mListeners.contains(listener)) {
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ try {
+ mService.addOnAppsChangedListener(mAppsChangedListener);
+ } catch (RemoteException re) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes a listener that was previously added.
+ *
+ * @param listener The listener to remove.
+ * @see #addOnAppsChangedListener(OnAppsChangedListener)
+ */
+ public synchronized void removeOnAppsChangedListener(OnAppsChangedListener listener) {
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ try {
+ mService.removeOnAppsChangedListener(mAppsChangedListener);
+ } catch (RemoteException re) {
+ }
+ }
+ }
+
+ private IOnAppsChangedListener.Stub mAppsChangedListener = new IOnAppsChangedListener.Stub() {
+
+ @Override
+ public void onPackageRemoved(UserHandle user, String packageName) throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackageRemoved " + user.getIdentifier() + "," + packageName);
+ }
+ synchronized (LauncherApps.this) {
+ for (OnAppsChangedListener listener : mListeners) {
+ listener.onPackageRemoved(user, packageName);
+ }
+ }
+ }
+
+ @Override
+ public void onPackageChanged(UserHandle user, String packageName) throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackageChanged " + user.getIdentifier() + "," + packageName);
+ }
+ synchronized (LauncherApps.this) {
+ for (OnAppsChangedListener listener : mListeners) {
+ listener.onPackageChanged(user, packageName);
+ }
+ }
+ }
+
+ @Override
+ public void onPackageAdded(UserHandle user, String packageName) throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackageAdded " + user.getIdentifier() + "," + packageName);
+ }
+ synchronized (LauncherApps.this) {
+ for (OnAppsChangedListener listener : mListeners) {
+ listener.onPackageAdded(user, packageName);
+ }
+ }
+ }
+
+ @Override
+ public void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing)
+ throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackagesAvailable " + user.getIdentifier() + "," + packageNames);
+ }
+ synchronized (LauncherApps.this) {
+ for (OnAppsChangedListener listener : mListeners) {
+ listener.onPackagesAvailable(user, packageNames, replacing);
+ }
+ }
+ }
+
+ @Override
+ public void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing)
+ throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onPackagesUnavailable " + user.getIdentifier() + "," + packageNames);
+ }
+ synchronized (LauncherApps.this) {
+ for (OnAppsChangedListener listener : mListeners) {
+ listener.onPackagesUnavailable(user, packageNames, replacing);
+ }
+ }
+ }
+ };
+}
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 226f5a6..d981cc1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,8 +16,10 @@
package android.content.pm;
+import android.annotation.IntDef;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -33,6 +35,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 +194,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.
@@ -677,12 +686,26 @@ public abstract class PackageManager {
/**
* Installation failed return code: this is passed to the {@link IPackageInstallObserver} by
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the system failed to install the package because it is attempting to define a
+ * permission that is already defined by some existing package.
+ *
+ * <p>The package name of the app which has already defined the permission is passed to
+ * a {@link IPackageInstallObserver2}, if any, as the {@link #EXTRA_EXISTING_PACKAGE}
+ * string extra; and the name of the permission being redefined is passed in the
+ * {@link #EXTRA_EXISTING_PERMISSION} string extra.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_DUPLICATE_PERMISSION = -112;
+
+ /**
+ * Installation failed return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
* if the system failed to install the package because its packaged native code did not
* match any of the ABIs supported by the system.
*
* @hide
*/
- public static final int INSTALL_FAILED_NO_MATCHING_ABIS = -112;
+ public static final int INSTALL_FAILED_NO_MATCHING_ABIS = -113;
/**
* Internal return code for NativeLibraryHelper methods to indicate that the package
@@ -691,7 +714,7 @@ public abstract class PackageManager {
*
* @hide
*/
- public static final int NO_NATIVE_LIBRARIES = -113;
+ public static final int NO_NATIVE_LIBRARIES = -114;
/**
* Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the
@@ -1314,6 +1337,13 @@ public abstract class PackageManager {
public static final String FEATURE_BACKUP = "android.software.backup";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports managed profiles for enterprise users.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_MANAGEDPROFILES = "android.software.managedprofiles";
+
+ /**
* Action to external storage service to clean out removed apps.
* @hide
*/
@@ -1403,6 +1433,24 @@ public abstract class PackageManager {
= "android.content.pm.extra.PERMISSION_LIST";
/**
+ * String extra for {@link IPackageInstallObserver2} in the 'extras' Bundle in case of
+ * {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}. This extra names the package which provides
+ * the existing definition for the permission.
+ * @hide
+ */
+ public static final String EXTRA_FAILURE_EXISTING_PACKAGE
+ = "android.content.pm.extra.FAILURE_EXISTING_PACKAGE";
+
+ /**
+ * String extra for {@link IPackageInstallObserver2} in the 'extras' Bundle in case of
+ * {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}. This extra names the permission that is
+ * being redundantly defined by the package being installed.
+ * @hide
+ */
+ public static final String EXTRA_FAILURE_EXISTING_PERMISSION
+ = "android.content.pm.extra.FAILURE_EXISTING_PERMISSION";
+
+ /**
* Retrieve overall information about an application package that is
* installed on the system.
* <p>
@@ -2781,11 +2829,14 @@ public abstract class PackageManager {
* 'content:' URI.
* @param observer An observer callback to get notified when the package installation is
* complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be
- * called when that happens. observer may be null to indicate that no callback is desired.
+ * called when that happens. This parameter must not be null.
* @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
* {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
* @param installerPackageName Optional package name of the application that is performing the
* installation. This identifies which market the package came from.
+ * @deprecated Use {@link #installPackage(Uri, IPackageInstallObserver2, int, String)}
+ * instead. This method will continue to be supported but the older observer interface
+ * will not get additional failure details.
*/
public abstract void installPackage(
Uri packageURI, IPackageInstallObserver observer, int flags,
@@ -2801,11 +2852,9 @@ public abstract class PackageManager {
* @param observer An observer callback to get notified when the package
* installation is complete.
* {@link IPackageInstallObserver#packageInstalled(String, int)}
- * will be called when that happens. observer may be null to
- * indicate that no callback is desired.
+ * will be called when that happens. This parameter must not be null.
* @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
- * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}
- * .
+ * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
* @param installerPackageName Optional package name of the application that
* is performing the installation. This identifies which market
* the package came from.
@@ -2818,6 +2867,10 @@ public abstract class PackageManager {
* these parameters describing the encryption and authentication
* used. May be {@code null}.
* @hide
+ * @deprecated Use {@link #installPackageWithVerification(Uri, IPackageInstallObserver2,
+ * int, String, Uri, ManifestDigest, ContainerEncryptionParams)} instead. This method will
+ * continue to be supported but the older observer interface will not get additional failure
+ * details.
*/
public abstract void installPackageWithVerification(Uri packageURI,
IPackageInstallObserver observer, int flags, String installerPackageName,
@@ -2834,11 +2887,9 @@ public abstract class PackageManager {
* @param observer An observer callback to get notified when the package
* installation is complete.
* {@link IPackageInstallObserver#packageInstalled(String, int)}
- * will be called when that happens. observer may be null to
- * indicate that no callback is desired.
+ * will be called when that happens. This parameter must not be null.
* @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
- * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}
- * .
+ * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
* @param installerPackageName Optional package name of the application that
* is performing the installation. This identifies which market
* the package came from.
@@ -2849,12 +2900,101 @@ public abstract class PackageManager {
* used. May be {@code null}.
*
* @hide
+ * @deprecated Use {@link #installPackageWithVerificationAndEncryption(Uri,
+ * IPackageInstallObserver2, int, String, VerificationParams,
+ * ContainerEncryptionParams)} instead. This method will continue to be
+ * supported but the older observer interface will not get additional failure details.
*/
+ @Deprecated
public abstract void installPackageWithVerificationAndEncryption(Uri packageURI,
IPackageInstallObserver observer, int flags, String installerPackageName,
VerificationParams verificationParams,
ContainerEncryptionParams encryptionParams);
+ // Package-install variants that take the new, expanded form of observer interface.
+ // Note that these *also* take the original observer type and will redundantly
+ // report the same information to that observer if supplied; but it is not required.
+
+ /**
+ * @hide
+ *
+ * Install a package. Since this may take a little while, the result will
+ * be posted back to the given observer. An installation will fail if the calling context
+ * lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
+ * package named in the package file's manifest is already installed, or if there's no space
+ * available on the device.
+ *
+ * @param packageURI The location of the package file to install. This can be a 'file:' or a
+ * 'content:' URI.
+ * @param observer An observer callback to get notified when the package installation is
+ * complete. {@link PackageInstallObserver#packageInstalled(String, Bundle, int)} will be
+ * called when that happens. This parameter must not be null.
+ * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
+ * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
+ * @param installerPackageName Optional package name of the application that is performing the
+ * installation. This identifies which market the package came from.
+ */
+ public abstract void installPackage(
+ Uri packageURI, PackageInstallObserver observer,
+ int flags, String installerPackageName);
+
+ /**
+ * Similar to
+ * {@link #installPackage(Uri, IPackageInstallObserver, int, String)} but
+ * with an extra verification file provided.
+ *
+ * @param packageURI The location of the package file to install. This can
+ * be a 'file:' or a 'content:' URI.
+ * @param observer An observer callback to get notified when the package installation is
+ * complete. {@link PackageInstallObserver#packageInstalled(String, Bundle, int)} will be
+ * called when that happens. This parameter must not be null.
+ * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
+ * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
+ * @param installerPackageName Optional package name of the application that
+ * is performing the installation. This identifies which market
+ * the package came from.
+ * @param verificationURI The location of the supplementary verification
+ * file. This can be a 'file:' or a 'content:' URI. May be
+ * {@code null}.
+ * @param manifestDigest an object that holds the digest of the package
+ * which can be used to verify ownership. May be {@code null}.
+ * @param encryptionParams if the package to be installed is encrypted,
+ * these parameters describing the encryption and authentication
+ * used. May be {@code null}.
+ * @hide
+ */
+ public abstract void installPackageWithVerification(Uri packageURI,
+ PackageInstallObserver observer, int flags, String installerPackageName,
+ Uri verificationURI, ManifestDigest manifestDigest,
+ ContainerEncryptionParams encryptionParams);
+
+ /**
+ * Similar to
+ * {@link #installPackage(Uri, IPackageInstallObserver, int, String)} but
+ * with an extra verification information provided.
+ *
+ * @param packageURI The location of the package file to install. This can
+ * be a 'file:' or a 'content:' URI.
+ * @param observer An observer callback to get notified when the package installation is
+ * complete. {@link PackageInstallObserver#packageInstalled(String, Bundle, int)} will be
+ * called when that happens. This parameter must not be null.
+ * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
+ * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
+ * @param installerPackageName Optional package name of the application that
+ * is performing the installation. This identifies which market
+ * the package came from.
+ * @param verificationParams an object that holds signal information to
+ * assist verification. May be {@code null}.
+ * @param encryptionParams if the package to be installed is encrypted,
+ * these parameters describing the encryption and authentication
+ * used. May be {@code null}.
+ *
+ * @hide
+ */
+ public abstract void installPackageWithVerificationAndEncryption(Uri packageURI,
+ PackageInstallObserver observer, int flags, String installerPackageName,
+ VerificationParams verificationParams, ContainerEncryptionParams encryptionParams);
+
/**
* If there is already an application with the given package name installed
* on the system for other users, also install it for the calling user.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index b3b6e09..8d8d220 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -57,7 +57,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.jar.JarEntry;
import java.util.jar.StrictJarFile;
import java.util.zip.ZipEntry;
@@ -306,6 +305,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 +988,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);
@@ -1009,13 +1009,14 @@ public class PackageParser {
pkg.mSharedUserLabel = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifest_sharedUserLabel, 0);
}
- sa.recycle();
pkg.installLocation = sa.getInteger(
com.android.internal.R.styleable.AndroidManifest_installLocation,
PARSE_DEFAULT_INSTALL_LOCATION);
pkg.applicationInfo.installLocation = pkg.installLocation;
+ sa.recycle();
+
/* Set the global "forward lock" flag */
if ((flags & PARSE_FORWARD_LOCK) != 0) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FORWARD_LOCK;
@@ -1104,7 +1105,6 @@ public class PackageParser {
if (!parseUsesPermission(pkg, res, parser, attrs, outError)) {
return null;
}
-
} else if (tagName.equals("uses-configuration")) {
ConfigurationInfo cPref = new ConfigurationInfo();
sa = res.obtainAttributes(attrs,
@@ -1986,6 +1986,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);
@@ -2445,6 +2447,11 @@ public class PackageParser {
a.info.flags |= ActivityInfo.FLAG_IMMERSIVE;
}
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_persistable, false)) {
+ a.info.flags |= ActivityInfo.FLAG_PERSISTABLE;
+ }
+
if (!receiver) {
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestActivity_hardwareAccelerated,
@@ -3285,19 +3292,23 @@ public class PackageParser {
if (packageName == null || packageName.length() == 0) {
Slog.i(TAG, "verifier package name was null; skipping");
return null;
- } else if (encodedPublicKey == null) {
- Slog.i(TAG, "verifier " + packageName + " public key was null; skipping");
}
- PublicKey publicKey = parsePublicKey(encodedPublicKey);
- if (publicKey != null) {
- return new VerifierInfo(packageName, publicKey);
+ final PublicKey publicKey = parsePublicKey(encodedPublicKey);
+ if (publicKey == null) {
+ Slog.i(TAG, "Unable to parse verifier public key for " + packageName);
+ return null;
}
- return null;
+ return new VerifierInfo(packageName, publicKey);
}
- public static final PublicKey parsePublicKey(String encodedPublicKey) {
+ public static final PublicKey parsePublicKey(final String encodedPublicKey) {
+ if (encodedPublicKey == null) {
+ Slog.i(TAG, "Could not parse null public key");
+ return null;
+ }
+
EncodedKeySpec keySpec;
try {
final byte[] encoded = Base64.decode(encodedPublicKey, Base64.DEFAULT);
@@ -3591,6 +3602,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/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 875e8de..4a743a5 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -140,12 +140,33 @@ public abstract class RegisteredServicesCache<V> {
mContext.registerReceiver(mExternalReceiver, sdFilter);
}
+ private final void handlePackageEvent(Intent intent, int userId) {
+ // Don't regenerate the services map when the package is removed or its
+ // ASEC container unmounted as a step in replacement. The subsequent
+ // _ADDED / _AVAILABLE call will regenerate the map in the final state.
+ final String action = intent.getAction();
+ // it's a new-component action if it isn't some sort of removal
+ final boolean isRemoval = Intent.ACTION_PACKAGE_REMOVED.equals(action)
+ || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action);
+ // if it's a removal, is it part of an update-in-place step?
+ final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+
+ if (isRemoval && replacing) {
+ // package is going away, but it's the middle of an upgrade: keep the current
+ // state and do nothing here. This clause is intentionally empty.
+ } else {
+ // either we're adding/changing, or it's a removal without replacement, so
+ // we need to recalculate the set of available services
+ generateServicesMap(userId);
+ }
+ }
+
private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
if (uid != -1) {
- generateServicesMap(UserHandle.getUserId(uid));
+ handlePackageEvent(intent, UserHandle.getUserId(uid));
}
}
};
@@ -154,7 +175,7 @@ public abstract class RegisteredServicesCache<V> {
@Override
public void onReceive(Context context, Intent intent) {
// External apps can't coexist with multi-user, so scan owner
- generateServicesMap(UserHandle.USER_OWNER);
+ handlePackageEvent(intent, UserHandle.USER_OWNER);
}
};
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 4c87830..f53aa4c 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_PROFILE_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 profileGroupId;
/** 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.profileGroupId = NO_PROFILE_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;
+ profileGroupId = orig.profileGroupId;
}
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(profileGroupId);
}
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;
+ profileGroupId = 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..0c04401 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -17,8 +17,8 @@
package android.content.res;
import android.os.ParcelFileDescriptor;
-import android.os.Trace;
import android.util.Log;
+import android.util.SparseArray;
import android.util.TypedValue;
import java.io.FileNotFoundException;
@@ -267,11 +267,9 @@ public final class AssetManager {
}
}
- /*package*/ final CharSequence getPooledString(int block, int id) {
- //System.out.println("Get pooled: block=" + block
- // + ", id=#" + Integer.toHexString(id)
- // + ", blocks=" + mStringBlocks);
- return mStringBlocks[block-1].get(id);
+ /*package*/ final CharSequence getPooledStringForCookie(int cookie, int id) {
+ // Cookies map to string blocks starting at 1.
+ return mStringBlocks[cookie - 1].get(id);
}
/**
@@ -536,6 +534,9 @@ public final class AssetManager {
}
public final class AssetInputStream extends InputStream {
+ /**
+ * @hide
+ */
public final int getAssetInt() {
throw new UnsupportedOperationException();
}
@@ -720,6 +721,9 @@ public final class AssetManager {
/*package*/ native static final boolean applyStyle(long theme,
int defStyleAttr, int defStyleRes, long xmlParser,
int[] inAttrs, int[] outValues, int[] outIndices);
+ /*package*/ native static final boolean resolveAttrs(long theme,
+ int defStyleAttr, int defStyleRes, int[] inValues,
+ int[] inAttrs, int[] outValues, int[] outIndices);
/*package*/ native final boolean retrieveAttributes(
long xmlParser, int[] inAttrs, int[] outValues, int[] outIndices);
/*package*/ native final int getArraySize(int resource);
@@ -735,6 +739,11 @@ public final class AssetManager {
/**
* {@hide}
*/
+ public native final SparseArray<String> getAssignedPackageIdentifiers();
+
+ /**
+ * {@hide}
+ */
public native static final int getGlobalAssetCount();
/**
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index bd23db4..5674154 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -16,12 +16,16 @@
package android.content.res;
+import android.graphics.Color;
+
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
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,17 +175,16 @@ 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;
final int innerDepth = parser.getDepth()+1;
int depth;
- int listAllocated = 20;
+ int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20);
+ int[] colorList = new int[stateSpecList.length];
int listSize = 0;
- int[] colorList = new int[listAllocated];
- int[][] stateSpecList = new int[listAllocated][];
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth=parser.getDepth()) >= innerDepth
@@ -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,25 +236,20 @@ public class ColorStateList implements Parcelable {
+ ": <item> tag requires a 'android:color' attribute.");
}
- if (listSize == 0 || stateSpec.length == 0) {
- mDefaultColor = color;
+ if (alphaRes != 0) {
+ alpha = r.getFraction(alphaRes, 1, 1);
}
-
- if (listSize + 1 >= listAllocated) {
- listAllocated = ArrayUtils.idealIntArraySize(listSize + 1);
- int[] ncolor = new int[listAllocated];
- System.arraycopy(colorList, 0, ncolor, 0, listSize);
+ // Apply alpha modulation.
+ final int alphaMod = MathUtils.constrain((int) (Color.alpha(color) * alpha), 0, 255);
+ color = (color & 0xFFFFFF) | (alphaMod << 24);
- int[][] nstate = new int[listAllocated][];
- System.arraycopy(stateSpecList, 0, nstate, 0, listSize);
-
- colorList = ncolor;
- stateSpecList = nstate;
+ if (listSize == 0 || stateSpec.length == 0) {
+ mDefaultColor = color;
}
- colorList[listSize] = color;
- stateSpecList[listSize] = stateSpec;
+ colorList = GrowingArrayUtils.append(colorList, listSize, color);
+ stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
listSize++;
}
@@ -259,7 +262,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 +302,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 +328,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 +345,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 3889ce0..4879c23 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -34,9 +34,9 @@ import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.TypedValue;
import android.util.LongSparseArray;
-import android.view.DisplayAdjustments;
import java.io.IOException;
import java.io.InputStream;
@@ -72,74 +72,94 @@ 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
// single-threaded, and after that these are immutable.
- private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
- private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
- = new LongSparseArray<Drawable.ConstantState>();
+ private static final LongSparseArray<ConstantState>[] sPreloadedDrawables;
+ private static final LongSparseArray<ConstantState> sPreloadedColorDrawables
+ = new LongSparseArray<ConstantState>();
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 ThemedCaches<ConstantState> mDrawableCache =
+ new ThemedCaches<ConstantState>();
+ private final ThemedCaches<ConstantState> mColorDrawableCache =
+ new ThemedCaches<ConstantState>();
+ private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache =
+ new LongSparseArray<WeakReference<ColorStateList>>();
- /*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 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;
+
+ @SuppressWarnings("unused")
private WeakReference<IBinder> mToken;
static {
sPreloadedDrawables = new LongSparseArray[2];
- sPreloadedDrawables[0] = new LongSparseArray<Drawable.ConstantState>();
- sPreloadedDrawables[1] = new LongSparseArray<Drawable.ConstantState>();
+ sPreloadedDrawables[0] = new LongSparseArray<ConstantState>();
+ sPreloadedDrawables[1] = new LongSparseArray<ConstantState>();
}
- /** @hide */
+ /**
+ * Returns the most appropriate default theme for the specified target SDK version.
+ * <ul>
+ * <li>Below API 11: Gingerbread
+ * <li>APIs 11 thru 14: Holo
+ * <li>APIs 14 thru XX: Device default dark
+ * <li>API XX and above: Device default light with dark action bar
+ * </ul>
+ *
+ * @param curTheme The current theme, or 0 if not specified.
+ * @param targetSdkVersion The target SDK version.
+ * @return A theme resource identifier
+ * @hide
+ */
public static int selectDefaultTheme(int curTheme, int targetSdkVersion) {
return selectSystemTheme(curTheme, targetSdkVersion,
com.android.internal.R.style.Theme,
com.android.internal.R.style.Theme_Holo,
- com.android.internal.R.style.Theme_DeviceDefault);
+ com.android.internal.R.style.Theme_DeviceDefault,
+ com.android.internal.R.style.Theme_DeviceDefault_Light_DarkActionBar);
}
-
+
/** @hide */
- public static int selectSystemTheme(int curTheme, int targetSdkVersion,
- int orig, int holo, int deviceDefault) {
+ public static int selectSystemTheme(int curTheme, int targetSdkVersion, int orig, int holo,
+ int dark, int deviceDefault) {
if (curTheme != 0) {
return curTheme;
}
@@ -149,9 +169,12 @@ public class Resources {
if (targetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return holo;
}
+ if (targetSdkVersion < Build.VERSION_CODES.CUR_DEVELOPMENT) {
+ return dark;
+ }
return deviceDefault;
}
-
+
/**
* This exception is thrown by the resource APIs when a requested resource
* can not be found.
@@ -513,7 +536,7 @@ public class Resources {
+ Integer.toHexString(id));
}
- TypedArray array = getCachedStyledAttributes(len);
+ TypedArray array = TypedArray.obtain(this, len);
array.mLength = mAssets.retrieveArray(id, array.mData);
array.mIndices[0] = 0;
@@ -681,12 +704,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 +735,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 +753,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 +809,7 @@ public class Resources {
}
}
- Drawable res = loadDrawable(value, id);
+ final Drawable res = loadDrawable(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
@@ -1211,6 +1268,10 @@ public class Resources {
*/
public void applyStyle(int resid, boolean force) {
AssetManager.applyThemeStyle(mTheme, resid, force);
+
+ // TODO: In very rare cases, we may end up with a hybrid theme
+ // that can't map to a single theme ID.
+ mThemeResId = resid;
}
/**
@@ -1224,6 +1285,8 @@ public class Resources {
*/
public void setTo(Theme other) {
AssetManager.copyTheme(mTheme, other.mTheme);
+
+ mThemeResId = other.mThemeResId;
}
/**
@@ -1246,11 +1309,10 @@ public class Resources {
* @see #obtainStyledAttributes(AttributeSet, int[], int, int)
*/
public TypedArray obtainStyledAttributes(int[] attrs) {
- int len = attrs.length;
- TypedArray array = getCachedStyledAttributes(len);
- array.mRsrcs = attrs;
- AssetManager.applyStyle(mTheme, 0, 0, 0, attrs,
- array.mData, array.mIndices);
+ final int len = attrs.length;
+ final TypedArray array = TypedArray.obtain(Resources.this, len);
+ array.mTheme = this;
+ AssetManager.applyStyle(mTheme, 0, 0, 0, attrs, array.mData, array.mIndices);
return array;
}
@@ -1274,14 +1336,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);
- array.mRsrcs = attrs;
-
- AssetManager.applyStyle(mTheme, 0, resid, 0, attrs,
- array.mData, array.mIndices);
+ public TypedArray obtainStyledAttributes(int resid, int[] attrs) throws NotFoundException {
+ final int len = attrs.length;
+ final TypedArray array = TypedArray.obtain(Resources.this, len);
+ array.mTheme = this;
if (false) {
int[] data = array.mData;
@@ -1308,6 +1366,7 @@ public class Resources {
}
System.out.println(s);
}
+ AssetManager.applyStyle(mTheme, 0, resid, 0, attrs, array.mData, array.mIndices);
return array;
}
@@ -1361,20 +1420,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 = TypedArray.obtain(Resources.this, 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.mRsrcs = attrs;
+ array.mTheme = this;
array.mXml = parser;
if (false) {
@@ -1410,6 +1467,45 @@ public class Resources {
}
/**
+ * Retrieve the values for a set of attributes in the Theme. The
+ * contents of the typed array are ultimately filled in by
+ * {@link Resources#getValue}.
+ *
+ * @param values The base set of attribute values, must be equal
+ * in length to {@code attrs} or {@code null}. All values
+ * must be of type {@link TypedValue#TYPE_ATTRIBUTE}.
+ * @param attrs The desired attributes to be retrieved.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies
+ * defaults values for the TypedArray. Can be
+ * 0 to not look for defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the TypedArray,
+ * used only if defStyleAttr is 0 or can not be found
+ * in the theme. Can be 0 to not look for defaults.
+ * @return Returns a TypedArray holding an array of the attribute
+ * values. Be sure to call {@link TypedArray#recycle()}
+ * when done with it.
+ * @hide
+ */
+ public TypedArray resolveAttributes(int[] values, int[] attrs,
+ int defStyleAttr, int defStyleRes) {
+ final int len = attrs.length;
+ if (values != null && len != values.length) {
+ throw new IllegalArgumentException(
+ "Base attribute values must be null or the same length as attrs");
+ }
+
+ final TypedArray array = TypedArray.obtain(Resources.this, len);
+ AssetManager.resolveAttrs(mTheme, defStyleAttr, defStyleRes,
+ values, attrs, array.mData, array.mIndices);
+ array.mTheme = this;
+ array.mXml = null;
+
+ return array;
+ }
+
+ /**
* Retrieve the value of an attribute in the Theme. The contents of
* <var>outValue</var> are ultimately filled in by
* {@link Resources#getValue}.
@@ -1426,8 +1522,7 @@ public class Resources {
* @return boolean Returns true if the attribute was found and
* <var>outValue</var> is valid, else false.
*/
- public boolean resolveAttribute(int resid, TypedValue outValue,
- boolean resolveRefs) {
+ public boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
boolean got = mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
if (false) {
System.out.println(
@@ -1439,6 +1534,30 @@ public class Resources {
}
/**
+ * Returns the resources to which this theme belongs.
+ *
+ * @return Resources to which this theme belongs.
+ */
+ public Resources getResources() {
+ return Resources.this;
+ }
+
+ /**
+ * 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 +1567,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,13 +1579,21 @@ public class Resources {
mTheme = mAssets.createTheme();
}
+ @SuppressWarnings("hiding")
private final AssetManager mAssets;
private final long mTheme;
+ /** Resource identifier for the theme. */
+ private int mThemeResId = 0;
+
// Needed by layoutlib.
- /*package*/ int getNativeTheme() {
+ /*package*/ long getNativeTheme() {
return mTheme;
}
+
+ /*package*/ int getAppliedStyleResId() {
+ return mThemeResId;
+ }
}
/**
@@ -1492,7 +1620,7 @@ public class Resources {
*/
public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
int len = attrs.length;
- TypedArray array = getCachedStyledAttributes(len);
+ TypedArray array = TypedArray.obtain(this, len);
// XXX note that for now we only work with compiled XML files.
// To support generic XML files we will need to manually parse
@@ -1502,7 +1630,6 @@ public class Resources {
mAssets.retrieveAttributes(parser.mParseState, attrs,
array.mData, array.mIndices);
- array.mRsrcs = attrs;
array.mXml = parser;
return array;
@@ -1574,7 +1701,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) {
@@ -1607,8 +1734,8 @@ public class Resources {
+ " final compat is " + mCompatibilityInfo);
}
- clearDrawableCacheLocked(mDrawableCache, configChanges);
- clearDrawableCacheLocked(mColorDrawableCache, configChanges);
+ clearDrawableCachesLocked(mDrawableCache, configChanges);
+ clearDrawableCachesLocked(mColorDrawableCache, configChanges);
mColorStateListCache.clear();
@@ -1621,18 +1748,25 @@ public class Resources {
}
}
+ private void clearDrawableCachesLocked(
+ ThemedCaches<ConstantState> caches, int configChanges) {
+ final int N = caches.size();
+ for (int i = 0; i < N; i++) {
+ clearDrawableCacheLocked(caches.valueAt(i), configChanges);
+ }
+ }
+
private void clearDrawableCacheLocked(
- LongSparseArray<WeakReference<ConstantState>> cache,
- int configChanges) {
- int N = cache.size();
+ LongSparseArray<WeakReference<ConstantState>> cache, int configChanges) {
if (DEBUG_CONFIG) {
Log.d(TAG, "Cleaning up drawables config changes: 0x"
+ Integer.toHexString(configChanges));
}
- for (int i=0; i<N; i++) {
- WeakReference<Drawable.ConstantState> ref = cache.valueAt(i);
+ final int N = cache.size();
+ for (int i = 0; i < N; i++) {
+ final WeakReference<ConstantState> ref = cache.valueAt(i);
if (ref != null) {
- Drawable.ConstantState cs = ref.get();
+ final ConstantState cs = ref.get();
if (cs != null) {
if (Configuration.needNewResources(
configChanges, cs.getChangingConfigurations())) {
@@ -1655,6 +1789,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.
@@ -1988,7 +2163,7 @@ public class Resources {
/**
* @hide
*/
- public LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() {
+ public LongSparseArray<ConstantState> getPreloadedDrawables() {
return sPreloadedDrawables[0];
}
@@ -2006,6 +2181,8 @@ public class Resources {
} catch (NotFoundException e) {
resName = "?";
}
+ // This should never happen in production, so we should log a
+ // warning even if we're not debugging.
Log.w(TAG, "Preloaded " + name + " resource #0x"
+ Integer.toHexString(resourceId)
+ " (" + resName + ") that varies with configuration!!");
@@ -2025,169 +2202,198 @@ 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);
+ }
}
}
- boolean isColorDrawable = false;
- if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
- value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ final boolean isColorDrawable;
+ final ThemedCaches<ConstantState> caches;
+ final long key;
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
+ && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
isColorDrawable = true;
+ caches = mColorDrawableCache;
+ key = value.data;
+ } else {
+ isColorDrawable = false;
+ caches = mDrawableCache;
+ key = (((long) value.assetCookie) << 32) | value.data;
}
- final long key = isColorDrawable ? value.data :
- (((long) value.assetCookie) << 32) | value.data;
- Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key);
-
- if (dr != null) {
- return dr;
+ // First, check whether we have a cached version of this drawable
+ // that's valid for the specified theme. This may apply a theme to a
+ // cached drawable that has themeable attributes but was not previously
+ // themed.
+ if (!mPreloading) {
+ final Drawable cachedDrawable = getCachedDrawable(caches, key, theme);
+ if (cachedDrawable != null) {
+ return cachedDrawable;
+ }
}
- Drawable.ConstantState cs;
+
+ // Next, check preloaded drawables. These are unthemed but may have
+ // themeable attributes.
+ final ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
+
+ final Drawable dr;
if (cs != null) {
- dr = cs.newDrawable(this);
+ dr = cs.newDrawable(this, theme);
+ } else if (isColorDrawable) {
+ dr = new ColorDrawable(value.data);
} else {
- if (isColorDrawable) {
- dr = new ColorDrawable(value.data);
- }
+ dr = loadDrawableForCookie(value, id, theme);
+ }
- if (dr == null) {
- if (value.string == null) {
- throw new NotFoundException(
- "Resource is not a Drawable (color or path): " + value);
- }
+ // If we were able to obtain a drawable, attempt to place it in the
+ // appropriate cache (e.g. no theme, themed, themeable).
+ if (dr != null) {
+ dr.setChangingConfigurations(value.changingConfigurations);
+ cacheDrawable(value, theme, isColorDrawable, caches, key, dr);
+ }
- String file = value.string.toString();
+ return dr;
+ }
+
+ private void cacheDrawable(TypedValue value, Theme theme, boolean isColorDrawable,
+ ThemedCaches<ConstantState> caches, long key, Drawable dr) {
+ final ConstantState cs = dr.getConstantState();
+ if (cs == null) {
+ return;
+ }
- if (TRACE_FOR_MISS_PRELOAD) {
- // Log only framework resources
- if ((id >>> 24) == 0x1) {
- final String name = getResourceName(id);
- if (name != null) android.util.Log.d(TAG, "Loading framework drawable #"
- + Integer.toHexString(id) + ": " + name
- + " at " + file);
+ if (mPreloading) {
+ // Preloaded drawables never have a theme, but may be themeable.
+ final int changingConfigs = cs.getChangingConfigurations();
+ if (isColorDrawable) {
+ if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) {
+ sPreloadedColorDrawables.put(key, cs);
+ }
+ } else {
+ if (verifyPreloadConfig(
+ changingConfigs, LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {
+ if ((changingConfigs & LAYOUT_DIR_CONFIG) == 0) {
+ // If this resource does not vary based on layout direction,
+ // we can put it in all of the preload maps.
+ sPreloadedDrawables[0].put(key, cs);
+ sPreloadedDrawables[1].put(key, cs);
+ } else {
+ // Otherwise, only in the layout dir we loaded it for.
+ sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs);
}
}
+ }
+ } else {
+ synchronized (mAccessLock) {
+ final LongSparseArray<WeakReference<ConstantState>> themedCache;
+ themedCache = caches.getOrCreate(theme == null ? 0 : theme.mThemeResId);
+ themedCache.put(key, new WeakReference<ConstantState>(cs));
+ }
+ }
+ }
- if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie "
- + value.assetCookie + ": " + file);
-
- if (file.endsWith(".xml")) {
- Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
- try {
- XmlResourceParser rp = loadXmlResourceParser(
- file, id, value.assetCookie, "drawable");
- dr = Drawable.createFromXml(this, rp);
- rp.close();
- } catch (Exception e) {
- Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
- NotFoundException rnf = new NotFoundException(
- "File " + file + " from drawable resource ID #0x"
- + Integer.toHexString(id));
- rnf.initCause(e);
- throw rnf;
- }
- Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ /**
+ * Loads a drawable from XML or resources stream.
+ */
+ private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {
+ if (value.string == null) {
+ throw new NotFoundException(
+ "Resource is not a Drawable (color or path): " + value);
+ }
- } else {
- Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
- try {
- InputStream is = mAssets.openNonAsset(
- value.assetCookie, file, AssetManager.ACCESS_STREAMING);
- // System.out.println("Opened file " + file + ": " + is);
- dr = Drawable.createFromResourceStream(this, value, is,
- file, null);
- is.close();
- // System.out.println("Created stream: " + dr);
- } catch (Exception e) {
- Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
- NotFoundException rnf = new NotFoundException(
- "File " + file + " from drawable resource ID #0x"
- + Integer.toHexString(id));
- rnf.initCause(e);
- throw rnf;
- }
- Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ final String file = value.string.toString();
+
+ if (TRACE_FOR_MISS_PRELOAD) {
+ // Log only framework resources
+ if ((id >>> 24) == 0x1) {
+ final String name = getResourceName(id);
+ if (name != null) {
+ Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id)
+ + ": " + name + " at " + file);
}
}
}
- if (dr != null) {
- dr.setChangingConfigurations(value.changingConfigurations);
- cs = dr.getConstantState();
- if (cs != null) {
- if (mPreloading) {
- final int changingConfigs = cs.getChangingConfigurations();
- if (isColorDrawable) {
- if (verifyPreloadConfig(changingConfigs, 0, value.resourceId,
- "drawable")) {
- sPreloadedColorDrawables.put(key, cs);
- }
- } else {
- if (verifyPreloadConfig(changingConfigs,
- LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {
- if ((changingConfigs&LAYOUT_DIR_CONFIG) == 0) {
- // If this resource does not vary based on layout direction,
- // we can put it in all of the preload maps.
- sPreloadedDrawables[0].put(key, cs);
- sPreloadedDrawables[1].put(key, cs);
- } else {
- // Otherwise, only in the layout dir we loaded it for.
- final LongSparseArray<Drawable.ConstantState> preloads
- = sPreloadedDrawables[mConfiguration.getLayoutDirection()];
- preloads.put(key, cs);
- }
- }
- }
- } else {
- synchronized (mAccessLock) {
- //Log.i(TAG, "Saving cached drawable @ #" +
- // Integer.toHexString(key.intValue())
- // + " in " + this + ": " + cs);
- if (isColorDrawable) {
- mColorDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
- } else {
- mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
- }
- }
- }
+ if (DEBUG_LOAD) {
+ Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file);
+ }
+
+ final Drawable dr;
+
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+ try {
+ if (file.endsWith(".xml")) {
+ final XmlResourceParser rp = loadXmlResourceParser(
+ file, id, value.assetCookie, "drawable");
+ dr = Drawable.createFromXmlThemed(this, rp, theme);
+ rp.close();
+ } else {
+ final InputStream is = mAssets.openNonAsset(
+ value.assetCookie, file, AssetManager.ACCESS_STREAMING);
+ dr = Drawable.createFromResourceStreamThemed(this, value, is, file, null, theme);
+ is.close();
}
+ } catch (Exception e) {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ final NotFoundException rnf = new NotFoundException(
+ "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
}
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return dr;
}
- private Drawable getCachedDrawable(
- LongSparseArray<WeakReference<ConstantState>> drawableCache,
- long key) {
+ private Drawable getCachedDrawable(ThemedCaches<ConstantState> caches, long key, Theme theme) {
synchronized (mAccessLock) {
- WeakReference<Drawable.ConstantState> wr = drawableCache.get(key);
- if (wr != null) { // we have the key
- Drawable.ConstantState entry = wr.get();
- if (entry != null) {
- //Log.i(TAG, "Returning cached drawable @ #" +
- // Integer.toHexString(((Integer)key).intValue())
- // + " in " + this + ": " + entry);
- return entry.newDrawable(this);
- }
- else { // our entry has been purged
- drawableCache.delete(key);
+ final int themeKey = theme != null ? theme.mThemeResId : 0;
+ final LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(themeKey);
+ if (themedCache != null) {
+ final Drawable themedDrawable = getCachedDrawableLocked(themedCache, key);
+ if (themedDrawable != null) {
+ return themedDrawable;
}
}
+
+ // No cached drawable, we'll need to create a new one.
+ return null;
+ }
+ }
+
+ private ConstantState getConstantStateLocked(
+ LongSparseArray<WeakReference<ConstantState>> drawableCache, long key) {
+ final WeakReference<ConstantState> wr = drawableCache.get(key);
+ if (wr != null) { // we have the key
+ final ConstantState entry = wr.get();
+ if (entry != null) {
+ //Log.i(TAG, "Returning cached drawable @ #" +
+ // Integer.toHexString(((Integer)key).intValue())
+ // + " in " + this + ": " + entry);
+ return entry;
+ } else { // our entry has been purged
+ drawableCache.delete(key);
+ }
+ }
+ return null;
+ }
+
+ private Drawable getCachedDrawableLocked(
+ LongSparseArray<WeakReference<ConstantState>> drawableCache, long key) {
+ final ConstantState entry = getConstantStateLocked(drawableCache, key);
+ if (entry != null) {
+ return entry.newDrawable(this);
}
return null;
}
@@ -2240,12 +2446,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();
@@ -2368,37 +2574,12 @@ public class Resources {
+ Integer.toHexString(id));
}
- private TypedArray getCachedStyledAttributes(int len) {
+ /*package*/ void recycleCachedStyledAttributes(TypedArray attrs) {
synchronized (mAccessLock) {
- TypedArray attrs = mCachedStyledAttributes;
- if (attrs != null) {
- mCachedStyledAttributes = null;
- if (DEBUG_ATTRIBUTES_CACHE) {
- mLastRetrievedAttrs = new RuntimeException("here");
- mLastRetrievedAttrs.fillInStackTrace();
- }
-
- attrs.mLength = len;
- int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
- if (attrs.mData.length >= fullLen) {
- return attrs;
- }
- attrs.mData = new int[fullLen];
- attrs.mIndices = new int[1+len];
- return attrs;
- }
- if (DEBUG_ATTRIBUTES_CACHE) {
- RuntimeException here = new RuntimeException("here");
- here.fillInStackTrace();
- if (mLastRetrievedAttrs != null) {
- Log.i(TAG, "Allocated new TypedArray of " + len + " in " + this, here);
- Log.i(TAG, "Last retrieved attributes here", mLastRetrievedAttrs);
- }
- mLastRetrievedAttrs = here;
+ final TypedArray cached = mCachedStyledAttributes;
+ if (cached == null || cached.mData.length < attrs.mData.length) {
+ mCachedStyledAttributes = attrs;
}
- return new TypedArray(this,
- new int[len*AssetManager.STYLE_NUM_ENTRIES],
- new int[1+len], len);
}
}
@@ -2412,4 +2593,21 @@ public class Resources {
updateConfiguration(null, null);
mAssets.ensureStringBlocks();
}
+
+ static class ThemedCaches<T> extends SparseArray<LongSparseArray<WeakReference<T>>> {
+ /**
+ * Returns the cache of drawables styled for the specified theme.
+ * <p>
+ * Drawables that have themeable attributes but were loaded without
+ * specifying a theme are cached at themeResId = 0.
+ */
+ public LongSparseArray<WeakReference<T>> getOrCreate(int themeResId) {
+ LongSparseArray<WeakReference<T>> result = get(themeResId);
+ if (result == null) {
+ result = new LongSparseArray<WeakReference<T>>(1);
+ put(themeResId, result);
+ }
+ return result;
+ }
+ }
}
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 83d48aa..15337ce 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -16,11 +16,11 @@
package android.content.res;
-import android.content.pm.ActivityInfo;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.Pools.SynchronizedPool;
import android.util.TypedValue;
import com.android.internal.util.XmlUtils;
@@ -32,62 +32,112 @@ import java.util.Arrays;
* {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
* or {@link Resources#obtainAttributes}. Be
* sure to call {@link #recycle} when done with them.
- *
+ *
* The indices used to retrieve values from this structure correspond to
* the positions of the attributes given to obtainStyledAttributes.
*/
public class TypedArray {
- private final Resources mResources;
+ private static final SynchronizedPool<TypedArray> mPool = new SynchronizedPool<TypedArray>(5);
+
+ static TypedArray obtain(Resources res, int len) {
+ final TypedArray attrs = mPool.acquire();
+ if (attrs != null) {
+ attrs.mLength = len;
+ attrs.mResources = res;
+ attrs.mMetrics = res.getDisplayMetrics();
+ attrs.mAssets = res.getAssets();
+ attrs.mRecycled = false;
+
+ final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
+ if (attrs.mData.length >= fullLen) {
+ return attrs;
+ }
+
+ attrs.mData = new int[fullLen];
+ attrs.mIndices = new int[1 + len];
+ return attrs;
+ }
+
+ return new TypedArray(res,
+ new int[len*AssetManager.STYLE_NUM_ENTRIES],
+ new int[1+len], len);
+ }
+
+ private Resources mResources;
+ private DisplayMetrics mMetrics;
+ private AssetManager mAssets;
+ private boolean mRecycled;
+
/*package*/ XmlBlock.Parser mXml;
- /*package*/ int[] mRsrcs;
+ /*package*/ Resources.Theme mTheme;
/*package*/ int[] mData;
/*package*/ int[] mIndices;
/*package*/ int mLength;
/*package*/ TypedValue mValue = new TypedValue();
-
+
/**
* Return the number of values in this array.
*/
public int length() {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
return mLength;
}
-
+
/**
* Return the number of indices in the array that actually have data.
*/
public int getIndexCount() {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
return mIndices[0];
}
-
+
/**
* Return an index in the array that has data.
- *
+ *
* @param at The index you would like to returned, ranging from 0 to
* {@link #getIndexCount()}.
- *
+ *
* @return The index at the given offset, which can be used with
* {@link #getValue} and related APIs.
*/
public int getIndex(int at) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
return mIndices[1+at];
}
-
+
/**
* Return the Resources object this array was loaded from.
*/
public Resources getResources() {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
return mResources;
}
-
+
/**
* Retrieve the styled string value for the attribute at <var>index</var>.
- *
+ *
* @param index Index of attribute to retrieve.
- *
- * @return CharSequence holding string data. May be styled. Returns
+ *
+ * @return CharSequence holding string data. May be styled. Returns
* null if the attribute is not defined.
*/
public CharSequence getText(int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -109,13 +159,17 @@ public class TypedArray {
/**
* Retrieve the string value for the attribute at <var>index</var>.
- *
+ *
* @param index Index of attribute to retrieve.
- *
+ *
* @return String holding string data. Any styling information is
* removed. Returns null if the attribute is not defined.
*/
public String getString(int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -143,14 +197,18 @@ public class TypedArray {
* attributes, or conversions from other types. As such, this method
* will only return strings for TypedArray objects that come from
* attributes in an XML file.
- *
+ *
* @param index Index of attribute to retrieve.
- *
+ *
* @return String holding string data. Any styling information is
* removed. Returns null if the attribute is not defined or is not
* an immediate string value.
*/
public String getNonResourceString(int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -163,12 +221,12 @@ public class TypedArray {
}
return null;
}
-
+
/**
* @hide
* Retrieve the string value for the attribute at <var>index</var> that is
* not allowed to change with the given configurations.
- *
+ *
* @param index Index of attribute to retrieve.
* @param allowedChangingConfigs Bit mask of configurations from
* {@link Configuration}.NATIVE_CONFIG_* that are allowed to change.
@@ -177,6 +235,10 @@ public class TypedArray {
* removed. Returns null if the attribute is not defined.
*/
public String getNonConfigurationString(int index, int allowedChangingConfigs) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -202,13 +264,17 @@ public class TypedArray {
/**
* Retrieve the boolean value for the attribute at <var>index</var>.
- *
+ *
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined.
- *
+ *
* @return Attribute boolean value, or defValue if not defined.
*/
public boolean getBoolean(int index, boolean defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -232,13 +298,17 @@ public class TypedArray {
/**
* Retrieve the integer value for the attribute at <var>index</var>.
- *
+ *
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined.
- *
+ *
* @return Attribute int value, or defValue if not defined.
*/
public int getInt(int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -262,12 +332,16 @@ public class TypedArray {
/**
* Retrieve the float value for the attribute at <var>index</var>.
- *
+ *
* @param index Index of attribute to retrieve.
- *
+ *
* @return Attribute float value, or defValue if not defined..
*/
public float getFloat(int index, float defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -292,20 +366,24 @@ public class TypedArray {
+ Integer.toHexString(type));
return defValue;
}
-
+
/**
* Retrieve the color value for the attribute at <var>index</var>. If
* the attribute references a color resource holding a complex
* {@link android.content.res.ColorStateList}, then the default color from
* the set is returned.
- *
+ *
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
- *
+ *
* @return Attribute color value, or defValue if not defined.
*/
public int getColor(int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -332,12 +410,16 @@ public class TypedArray {
* Retrieve the ColorStateList for the attribute at <var>index</var>.
* The value may be either a single solid color or a reference to
* a color or complex {@link android.content.res.ColorStateList} description.
- *
+ *
* @param index Index of attribute to retrieve.
- *
+ *
* @return ColorStateList for the attribute, or null if not defined.
*/
public ColorStateList getColorStateList(int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
final TypedValue value = mValue;
if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
return mResources.loadColorStateList(value, value.resourceId);
@@ -347,14 +429,18 @@ public class TypedArray {
/**
* Retrieve the integer value for the attribute at <var>index</var>.
- *
+ *
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
- *
+ *
* @return Attribute integer value, or defValue if not defined.
*/
public int getInteger(int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -370,22 +456,26 @@ public class TypedArray {
}
/**
- * Retrieve a dimensional unit attribute at <var>index</var>. Unit
- * conversions are based on the current {@link DisplayMetrics}
- * associated with the resources this {@link TypedArray} object
- * came from.
- *
+ * Retrieve a dimensional unit attribute at <var>index</var>. Unit
+ * conversions are based on the current {@link DisplayMetrics}
+ * associated with the resources this {@link TypedArray} object
+ * came from.
+ *
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
- *
- * @return Attribute dimension value multiplied by the appropriate
+ *
+ * @return Attribute dimension value multiplied by the appropriate
* metric, or defValue if not defined.
- *
+ *
* @see #getDimensionPixelOffset
* @see #getDimensionPixelSize
*/
public float getDimension(int index, float defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -393,7 +483,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"
@@ -406,18 +496,22 @@ public class TypedArray {
* {@link #getDimension}, except the returned value is converted to
* integer pixels for you. An offset conversion involves simply
* truncating the base value to an integer.
- *
+ *
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
- *
- * @return Attribute dimension value multiplied by the appropriate
+ *
+ * @return Attribute dimension value multiplied by the appropriate
* metric and truncated to integer pixels, or defValue if not defined.
- *
+ *
* @see #getDimension
* @see #getDimensionPixelSize
*/
public int getDimensionPixelOffset(int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -425,7 +519,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"
@@ -439,18 +533,22 @@ public class TypedArray {
* integer pixels for use as a size. A size conversion involves
* rounding the base value, and ensuring that a non-zero base value
* is at least one pixel in size.
- *
+ *
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
- *
- * @return Attribute dimension value multiplied by the appropriate
+ *
+ * @return Attribute dimension value multiplied by the appropriate
* metric and truncated to integer pixels, or defValue if not defined.
- *
+ *
* @see #getDimension
* @see #getDimensionPixelOffset
*/
public int getDimensionPixelSize(int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -458,7 +556,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"
@@ -470,14 +568,18 @@ public class TypedArray {
* {@link android.view.ViewGroup}'s layout_width and layout_height
* attributes. This is only here for performance reasons; applications
* should use {@link #getDimensionPixelSize}.
- *
+ *
* @param index Index of the attribute to retrieve.
* @param name Textual name of attribute for error reporting.
- *
- * @return Attribute dimension value multiplied by the appropriate
+ *
+ * @return Attribute dimension value multiplied by the appropriate
* metric and truncated to integer pixels.
*/
public int getLayoutDimension(int index, String name) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -486,27 +588,31 @@ 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()
+ ": You must supply a " + name + " attribute.");
}
-
+
/**
* Special version of {@link #getDimensionPixelSize} for retrieving
* {@link android.view.ViewGroup}'s layout_width and layout_height
* attributes. This is only here for performance reasons; applications
* should use {@link #getDimensionPixelSize}.
- *
+ *
* @param index Index of the attribute to retrieve.
* @param defValue The default value to return if this attribute is not
* default or contains the wrong type of data.
- *
- * @return Attribute dimension value multiplied by the appropriate
+ *
+ * @return Attribute dimension value multiplied by the appropriate
* metric and truncated to integer pixels.
*/
public int getLayoutDimension(int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -515,7 +621,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;
@@ -523,20 +629,24 @@ public class TypedArray {
/**
* Retrieve a fractional unit attribute at <var>index</var>.
- *
- * @param index Index of attribute to retrieve.
- * @param base The base value of this fraction. In other words, a
+ *
+ * @param index Index of attribute to retrieve.
+ * @param base The base value of this fraction. In other words, a
* standard fraction is multiplied by this value.
- * @param pbase The parent base value of this fraction. In other
+ * @param pbase The parent base value of this fraction. In other
* words, a parent fraction (nn%p) is multiplied by this
* value.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
- *
- * @return Attribute fractional value multiplied by the appropriate
- * base value, or defValue if not defined.
+ *
+ * @return Attribute fractional value multiplied by the appropriate
+ * base value, or defValue if not defined.
*/
public float getFraction(int index, int base, int pbase, float defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
@@ -553,19 +663,23 @@ public class TypedArray {
/**
* Retrieve the resource identifier for the attribute at
- * <var>index</var>. Note that attribute resource as resolved when
- * the overall {@link TypedArray} object is retrieved. As a
- * result, this function will return the resource identifier of the
- * final resource value that was found, <em>not</em> necessarily the
- * original resource that was specified by the attribute.
- *
+ * <var>index</var>. Note that attribute resource as resolved when
+ * the overall {@link TypedArray} object is retrieved. As a
+ * result, this function will return the resource identifier of the
+ * final resource value that was found, <em>not</em> necessarily the
+ * original resource that was specified by the attribute.
+ *
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
- *
+ *
* @return Attribute resource identifier, or defValue if not defined.
*/
public int getResourceId(int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) {
@@ -578,16 +692,43 @@ public class TypedArray {
}
/**
+ * Retrieve the theme attribute resource identifier for the attribute at
+ * <var>index</var>.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param defValue Value to return if the attribute is not defined or not a
+ * resource.
+ * @return Theme attribute resource identifier, or defValue if not defined.
+ * @hide
+ */
+ public int getThemeAttributeId(int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ if (data[index + AssetManager.STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) {
+ return data[index + AssetManager.STYLE_DATA];
+ }
+ return defValue;
+ }
+
+ /**
* Retrieve the Drawable for the attribute at <var>index</var>. This
* gets the resource ID of the selected attribute, and uses
* {@link Resources#getDrawable Resources.getDrawable} of the owning
* Resources object to retrieve its Drawable.
- *
+ *
* @param index Index of attribute to retrieve.
- *
+ *
* @return Drawable for the attribute, or null if not defined.
*/
public Drawable getDrawable(int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
final TypedValue value = mValue;
if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
if (false) {
@@ -599,7 +740,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;
}
@@ -609,12 +750,16 @@ public class TypedArray {
* This gets the resource ID of the selected attribute, and uses
* {@link Resources#getTextArray Resources.getTextArray} of the owning
* Resources object to retrieve its String[].
- *
+ *
* @param index Index of attribute to retrieve.
- *
+ *
* @return CharSequence[] for the attribute, or null if not defined.
*/
public CharSequence[] getTextArray(int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
final TypedValue value = mValue;
if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
if (false) {
@@ -633,43 +778,70 @@ public class TypedArray {
/**
* Retrieve the raw TypedValue for the attribute at <var>index</var>.
- *
+ *
* @param index Index of attribute to retrieve.
* @param outValue TypedValue object in which to place the attribute's
* data.
- *
- * @return Returns true if the value was retrieved, else false.
+ *
+ * @return Returns true if the value was retrieved, else false.
*/
public boolean getValue(int index, TypedValue outValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
return getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, outValue);
}
/**
+ * Returns the type of attribute at the specified index.
+ *
+ * @param index Index of attribute whose type to retrieve.
+ * @return Attribute type.
+ */
+ public int getType(int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ index *= AssetManager.STYLE_NUM_ENTRIES;
+ return mData[index + AssetManager.STYLE_TYPE];
+ }
+
+ /**
* Determines whether there is an attribute at <var>index</var>.
- *
+ *
* @param index Index of attribute to retrieve.
- *
+ *
* @return True if the attribute has a value, false otherwise.
*/
public boolean hasValue(int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
return type != TypedValue.TYPE_NULL;
}
-
+
/**
- * Retrieve the raw TypedValue for the attribute at <var>index</var>
- * and return a temporary object holding its data. This object is only
- * valid until the next call on to {@link TypedArray}.
- *
+ * Retrieve the raw TypedValue for the attribute at <var>index</var>
+ * and return a temporary object holding its data. This object is only
+ * valid until the next call on to {@link TypedArray}.
+ *
* @param index Index of attribute to retrieve.
- *
- * @return Returns a TypedValue object if the attribute is defined,
+ *
+ * @return Returns a TypedValue object if the attribute is defined,
* containing its data; otherwise returns null. (You will not
* receive a TypedValue whose type is TYPE_NULL.)
*/
public TypedValue peekValue(int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
final TypedValue value = mValue;
if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
return value;
@@ -681,20 +853,66 @@ public class TypedArray {
* Returns a message about the parser state suitable for printing error messages.
*/
public String getPositionDescription() {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
return mXml != null ? mXml.getPositionDescription() : "<internal>";
}
/**
- * Give back a previously retrieved array, for later re-use.
+ * Recycle the TypedArray, to be re-used by a later caller. After calling
+ * this function you must not ever touch the typed array again.
*/
public void recycle() {
- synchronized (mResources.mAccessLock) {
- TypedArray cached = mResources.mCachedStyledAttributes;
- if (cached == null || cached.mData.length < mData.length) {
- mXml = null;
- mResources.mCachedStyledAttributes = this;
+ if (mRecycled) {
+ throw new RuntimeException(toString() + " recycled twice!");
+ }
+
+ mRecycled = true;
+ mResources = null;
+ mMetrics = null;
+ mAssets = null;
+
+ // These may have been set by the client.
+ mXml = null;
+ mTheme = null;
+
+ synchronized (mPool) {
+ mPool.release(this);
+ }
+ }
+
+ /**
+ * Extracts theme attributes from a typed array for later resolution using
+ * {@link Theme#resolveAttributes(int[], int[], int, int)}.
+ *
+ * @param array An array to populate with theme attributes. If the array is
+ * null or not large enough, a new array will be returned.
+ * @return an array of length {@link #getIndexCount()} populated with theme
+ * attributes, or null if there are no theme attributes in the
+ * typed array
+ * @hide
+ */
+ public int[] extractThemeAttrs() {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ int[] attrs = null;
+
+ final int N = length();
+ for (int i = 0; i < N; i++) {
+ final int attrId = getThemeAttributeId(i, 0);
+ if (attrId != 0) {
+ if (attrs == null) {
+ attrs = new int[N];
+ }
+ attrs[i] = attrId;
}
}
+
+ return attrs;
}
private boolean getValueAt(int index, TypedValue outValue) {
@@ -723,18 +941,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.getPooledStringForCookie(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/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index 3ad357f..2f4d69b 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -375,7 +375,7 @@ final class XmlBlock {
boolean defaultValue) {
int t = nativeGetAttributeDataType(mParseState, idx);
// Note: don't attempt to convert any other types, because
- // we want to count on appt doing the conversion for us.
+ // we want to count on aapt doing the conversion for us.
if (t >= TypedValue.TYPE_FIRST_INT &&
t <= TypedValue.TYPE_LAST_INT) {
return nativeGetAttributeData(mParseState, idx) != 0;
@@ -385,7 +385,7 @@ final class XmlBlock {
public int getAttributeResourceValue(int idx, int defaultValue) {
int t = nativeGetAttributeDataType(mParseState, idx);
// Note: don't attempt to convert any other types, because
- // we want to count on appt doing the conversion for us.
+ // we want to count on aapt doing the conversion for us.
if (t == TypedValue.TYPE_REFERENCE) {
return nativeGetAttributeData(mParseState, idx);
}
@@ -394,7 +394,7 @@ final class XmlBlock {
public int getAttributeIntValue(int idx, int defaultValue) {
int t = nativeGetAttributeDataType(mParseState, idx);
// Note: don't attempt to convert any other types, because
- // we want to count on appt doing the conversion for us.
+ // we want to count on aapt doing the conversion for us.
if (t >= TypedValue.TYPE_FIRST_INT &&
t <= TypedValue.TYPE_LAST_INT) {
return nativeGetAttributeData(mParseState, idx);
@@ -404,7 +404,7 @@ final class XmlBlock {
public int getAttributeUnsignedIntValue(int idx, int defaultValue) {
int t = nativeGetAttributeDataType(mParseState, idx);
// Note: don't attempt to convert any other types, because
- // we want to count on appt doing the conversion for us.
+ // we want to count on aapt doing the conversion for us.
if (t >= TypedValue.TYPE_FIRST_INT &&
t <= TypedValue.TYPE_LAST_INT) {
return nativeGetAttributeData(mParseState, idx);
@@ -414,7 +414,7 @@ final class XmlBlock {
public float getAttributeFloatValue(int idx, float defaultValue) {
int t = nativeGetAttributeDataType(mParseState, idx);
// Note: don't attempt to convert any other types, because
- // we want to count on appt doing the conversion for us.
+ // we want to count on aapt doing the conversion for us.
if (t == TypedValue.TYPE_FLOAT) {
return Float.intBitsToFloat(
nativeGetAttributeData(mParseState, idx));
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 eae4a46..35c86e7 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -47,7 +47,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
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..9852776 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -126,206 +126,300 @@ 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 mode 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 mode 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<int[]> CONTROL_MAX_REGIONS =
+ new Key<int[]>("android.control.maxRegions", int[].class);
+
+ /**
+ * <p>The set of edge enhancement modes supported by this camera device.</p>
+ * <p>This tag lists the valid modes for {@link CaptureRequest#EDGE_MODE android.edge.mode}.</p>
+ * <p>Full-capability camera devices must always support OFF and FAST.</p>
+ *
+ * @see CaptureRequest#EDGE_MODE
+ */
+ public static final Key<byte[]> EDGE_AVAILABLE_EDGE_MODES =
+ new Key<byte[]>("android.edge.availableEdgeModes", byte[].class);
+
+ /**
+ * <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<Integer> CONTROL_MAX_REGIONS =
- new Key<Integer>("android.control.maxRegions", int.class);
+ public static final Key<Boolean> FLASH_INFO_AVAILABLE =
+ new Key<Boolean>("android.flash.info.available", boolean.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>The set of hot pixel correction modes that are supported by this
+ * camera device.</p>
+ * <p>This tag lists valid modes for {@link CaptureRequest#HOT_PIXEL_MODE android.hotPixel.mode}.</p>
+ * <p>FULL mode camera devices will always support FAST.</p>
+ *
+ * @see CaptureRequest#HOT_PIXEL_MODE
*/
- public static final Key<Byte> FLASH_INFO_AVAILABLE =
- new Key<Byte>("android.flash.info.available", byte.class);
+ public static final Key<byte[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES =
+ new Key<byte[]>("android.hotPixel.availableHotPixelModes", byte[].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 +427,819 @@ 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>The set of noise reduction modes supported by this camera device.</p>
+ * <p>This tag lists the valid modes for {@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}.</p>
+ * <p>Full-capability camera devices must laways support OFF and FAST.</p>
+ *
+ * @see CaptureRequest#NOISE_REDUCTION_MODE
+ */
+ public static final Key<byte[]> NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES =
+ new Key<byte[]>("android.noiseReduction.availableNoiseReductionModes", byte[].class);
+
+ /**
+ * <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>Arrangement of color filters on sensor;
+ * represents the colors in the top-left 2x2 section of
+ * the sensor, in reading order</p>
+ * @see #SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB
+ * @see #SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG
+ * @see #SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG
+ * @see #SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR
+ * @see #SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGB
+ */
+ public static final Key<Integer> SENSOR_INFO_COLOR_FILTER_ARRANGEMENT =
+ new Key<Integer>("android.sensor.info.colorFilterArrangement", int.class);
+
+ /**
+ * <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
+ * (8-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>
+ *
+ * @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>
*
- * <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<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<int[]> SENSOR_AVAILABLE_TEST_PATTERN_MODES =
+ new Key<int[]>("android.sensor.availableTestPatternModes", 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
* 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>The set of hot pixel map output modes supported by this camera device.</p>
+ * <p>This tag lists valid output modes for {@link CaptureRequest#STATISTICS_HOT_PIXEL_MAP_MODE android.statistics.hotPixelMapMode}.</p>
+ * <p>If no hotpixel map is available for this camera device, this will contain
+ * only OFF. If the hotpixel map is available, this should include both
+ * the ON and OFF options.</p>
+ *
+ * @see CaptureRequest#STATISTICS_HOT_PIXEL_MAP_MODE
+ */
+ public static final Key<boolean[]> STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES =
+ new Key<boolean[]>("android.statistics.info.availableHotPixelMapModes", boolean[].class);
+
+ /**
+ * <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>
- * @see #LED_AVAILABLE_LEDS_TRANSMIT
+ * <p>The set of tonemapping modes supported by this camera device.</p>
+ * <p>This tag lists the valid modes for {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}.</p>
+ * <p>Full-capability camera devices must always support CONTRAST_CURVE and
+ * FAST.</p>
*
+ * @see CaptureRequest#TONEMAP_MODE
+ */
+ public static final Key<byte[]> TONEMAP_AVAILABLE_TONE_MAP_MODES =
+ new Key<byte[]>("android.tonemap.availableToneMapModes", byte[].class);
+
+ /**
+ * <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..0fcd598 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,14 @@ public final class CameraManager {
mCameraService = CameraBinderDecorator.newInstance(cameraServiceRaw);
try {
+ 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..9b1bc53 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,209 @@ 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#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
+ //
+
+ /**
+ * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
+ */
+ public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB = 0;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
+ */
+ public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG = 1;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
+ */
+ public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG = 2;
+
+ /**
+ * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
+ */
+ public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR = 3;
+
+ /**
+ * <p>Sensor is not Bayer; output has 3 16-bit
+ * values for each pixel, instead of just 1 16-bit value
+ * per pixel.</p>
+ * @see CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT
+ */
+ public static final int SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGB = 4;
+
+ //
// 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 +482,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 +560,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 +594,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 +670,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 +688,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 +736,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 +759,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 +780,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 +803,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,108 +895,125 @@ 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;
+ /**
+ * <p>This request is for manual capture use case where
+ * the applications want to directly control the capture parameters
+ * (e.g. {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}, {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} etc.).</p>
+ *
+ * @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_SENSITIVITY
+ * @see CaptureRequest#CONTROL_CAPTURE_INTENT
+ */
+ public static final int CONTROL_CAPTURE_INTENT_MANUAL = 6;
+
//
// Enumeration values for CaptureRequest#CONTROL_EFFECT_MODE
//
/**
+ * <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 +1023,169 @@ 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>
+ * This setting can only be used if scene mode is supported
+ * (i.e. {@link CameraCharacteristics#CONTROL_AVAILABLE_SCENE_MODES android.control.availableSceneModes} contain some modes
+ * other than DISABLED).</p>
+ *
+ * @see CameraCharacteristics#CONTROL_AVAILABLE_SCENE_MODES
* @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 +1195,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 +1219,74 @@ 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.
+ * The hotpixel map may be returned in {@link CaptureResult#STATISTICS_HOT_PIXEL_MAP android.statistics.hotPixelMap}.</p>
+ *
+ * @see CaptureResult#STATISTICS_HOT_PIXEL_MAP
+ * @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.
+ * The hotpixel map may be returned in {@link CaptureResult#STATISTICS_HOT_PIXEL_MAP android.statistics.hotPixelMap}.</p>
+ *
+ * @see CaptureResult#STATISTICS_HOT_PIXEL_MAP
+ * @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.
+ * The hotpixel map may be returned in {@link CaptureResult#STATISTICS_HOT_PIXEL_MAP android.statistics.hotPixelMap}.</p>
+ *
+ * @see CaptureResult#STATISTICS_HOT_PIXEL_MAP
+ * @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 +1296,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 +1452,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 +1484,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 +1519,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 +1573,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 +1638,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,55 +1671,170 @@ 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>Flash is charging and cannot be fired.</p>
* @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>Flash is ready to fire.</p>
* @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>Flash fired for this capture.</p>
* @see CaptureResult#FLASH_STATE
*/
public static final int FLASH_STATE_FIRED = 3;
+ /**
+ * <p>Flash partially illuminated this frame. This is usually due to the next
+ * or previous frame having the flash fire, and the flash spilling into this capture
+ * due to hardware limitations.</p>
+ * @see CaptureResult#FLASH_STATE
+ */
+ public static final int FLASH_STATE_PARTIAL = 4;
+
//
// Enumeration values for CaptureResult#LENS_STATE
//
/**
+ * <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 +1853,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..c4e342c 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,58 +685,66 @@ 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 (except for MANUAL) is only effective if
+ * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p>
+ * <p>ZERO_SHUTTER_LAG must be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
+ * contains ZSL. MANUAL must be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
+ * contains MANUAL_SENSOR.</p>
+ *
+ * @see CaptureRequest#CONTROL_MODE
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
* @see #CONTROL_CAPTURE_INTENT_CUSTOM
* @see #CONTROL_CAPTURE_INTENT_PREVIEW
* @see #CONTROL_CAPTURE_INTENT_STILL_CAPTURE
* @see #CONTROL_CAPTURE_INTENT_VIDEO_RECORD
* @see #CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT
* @see #CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG
+ * @see #CONTROL_CAPTURE_INTENT_MANUAL
*/
public static final Key<Integer> CONTROL_CAPTURE_INTENT =
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 +759,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 +824,30 @@ 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>This must be set to one of the modes listed in {@link CameraCharacteristics#EDGE_AVAILABLE_EDGE_MODES android.edge.availableEdgeModes}.</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 CameraCharacteristics#EDGE_AVAILABLE_EDGE_MODES
* @see #EDGE_MODE_OFF
* @see #EDGE_MODE_FAST
* @see #EDGE_MODE_HIGH_QUALITY
@@ -725,9 +856,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 +883,171 @@ 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>Valid modes for this camera device are listed in
+ * {@link CameraCharacteristics#HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES android.hotPixel.availableHotPixelModes}.</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 CameraCharacteristics#HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES
+ * @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 +1055,19 @@ 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>This must be set to a valid mode in
+ * {@link CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES android.noiseReduction.availableNoiseReductionModes}.</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 CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES
* @see #NOISE_REDUCTION_MODE_OFF
* @see #NOISE_REDUCTION_MODE_FAST
* @see #NOISE_REDUCTION_MODE_HIGH_QUALITY
@@ -877,44 +1076,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 +1111,188 @@ 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>
*/
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 +1301,25 @@ 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>Operating mode for hotpixel map generation.</p>
+ * <p>If set to ON, a hotpixel map is returned in {@link CaptureResult#STATISTICS_HOT_PIXEL_MAP android.statistics.hotPixelMap}.
+ * If set to OFF, no hotpixel map should be returned.</p>
+ * <p>This must be set to a valid mode from {@link CameraCharacteristics#STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES android.statistics.info.availableHotPixelMapModes}.</p>
+ *
+ * @see CaptureResult#STATISTICS_HOT_PIXEL_MAP
+ * @see CameraCharacteristics#STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES
+ */
+ public static final Key<Boolean> STATISTICS_HOT_PIXEL_MAP_MODE =
+ new Key<Boolean>("android.statistics.hotPixelMapMode", boolean.class);
+
+ /**
+ * <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 +1327,110 @@ 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>This must be set to a valid mode in
+ * {@link CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES android.tonemap.availableToneMapModes}.</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 CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES
+ * @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 +1439,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..b3bce3b 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,46 @@ 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>This must be set to one of the modes listed in {@link CameraCharacteristics#EDGE_AVAILABLE_EDGE_MODES android.edge.availableEdgeModes}.</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 CameraCharacteristics#EDGE_AVAILABLE_EDGE_MODES
* @see #EDGE_MODE_OFF
* @see #EDGE_MODE_FAST
* @see #EDGE_MODE_HIGH_QUALITY
@@ -403,9 +1126,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,153 +1153,188 @@ 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
* @see #FLASH_STATE_FIRED
+ * @see #FLASH_STATE_PARTIAL
*/
public static final Key<Integer> FLASH_STATE =
new Key<Integer>("android.flash.state", int.class);
/**
- * <p>
- * GPS coordinates to include in output JPEG
- * EXIF
- * </p>
+ * <p>Set operational mode for hot pixel correction.</p>
+ * <p>Valid modes for this camera device are listed in
+ * {@link CameraCharacteristics#HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES android.hotPixel.availableHotPixelModes}.</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 CameraCharacteristics#HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES
+ * @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 +1342,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 +1378,19 @@ 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>This must be set to a valid mode in
+ * {@link CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES android.noiseReduction.availableNoiseReductionModes}.</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 CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES
* @see #NOISE_REDUCTION_MODE_OFF
* @see #NOISE_REDUCTION_MODE_FAST
* @see #NOISE_REDUCTION_MODE_HIGH_QUALITY
@@ -590,14 +1399,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 +1411,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 +1477,340 @@ 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>
*/
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 5x5 pixel (or larger) window W within the active sensor array is
+ * chosen. The term 'pixel' here is taken to mean a group of 4 Bayer
+ * mosaic channels (R, Gr, Gb, B). The location and size of the window
+ * chosen is implementation defined, and should be chosen to provide a
+ * green split estimate that is both representative of the entire image
+ * for this camera sensor, and can be calculated quickly.</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 +1819,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 +1972,137 @@ 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>Operating mode for hotpixel map generation.</p>
+ * <p>If set to ON, a hotpixel map is returned in {@link CaptureResult#STATISTICS_HOT_PIXEL_MAP android.statistics.hotPixelMap}.
+ * If set to OFF, no hotpixel map should be returned.</p>
+ * <p>This must be set to a valid mode from {@link CameraCharacteristics#STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES android.statistics.info.availableHotPixelMapModes}.</p>
+ *
+ * @see CaptureResult#STATISTICS_HOT_PIXEL_MAP
+ * @see CameraCharacteristics#STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES
+ */
+ public static final Key<Boolean> STATISTICS_HOT_PIXEL_MAP_MODE =
+ new Key<Boolean>("android.statistics.hotPixelMapMode", boolean.class);
+
+ /**
+ * <p>List of <code>(x, y)</code> coordinates of hot/defective pixels on the sensor.</p>
+ * <p>A coordinate <code>(x, y)</code> must lie between <code>(0, 0)</code>, and
+ * <code>(width - 1, height - 1)</code> (inclusive), which are the top-left and
+ * bottom-right of the pixel array, respectively. The width and
+ * height dimensions are 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[]> STATISTICS_HOT_PIXEL_MAP =
+ new Key<int[]>("android.statistics.hotPixelMap", int[].class);
+
+ /**
+ * <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>This must be set to a valid mode in
+ * {@link CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES android.tonemap.availableToneMapModes}.</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 CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES
+ * @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 +2111,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/CaptureResultExtras.aidl b/core/java/android/hardware/camera2/CaptureResultExtras.aidl
new file mode 100644
index 0000000..6587f02
--- /dev/null
+++ b/core/java/android/hardware/camera2/CaptureResultExtras.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.camera2;
+
+/** @hide */
+parcelable CaptureResultExtras;
diff --git a/core/java/android/hardware/camera2/CaptureResultExtras.java b/core/java/android/hardware/camera2/CaptureResultExtras.java
new file mode 100644
index 0000000..e5c2c1c
--- /dev/null
+++ b/core/java/android/hardware/camera2/CaptureResultExtras.java
@@ -0,0 +1,90 @@
+/*
+ * 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.camera2;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public class CaptureResultExtras implements Parcelable {
+ private int requestId;
+ private int subsequenceId;
+ private int afTriggerId;
+ private int precaptureTriggerId;
+ private long frameNumber;
+
+ public static final Parcelable.Creator<CaptureResultExtras> CREATOR =
+ new Parcelable.Creator<CaptureResultExtras>() {
+ @Override
+ public CaptureResultExtras createFromParcel(Parcel in) {
+ return new CaptureResultExtras(in);
+ }
+
+ @Override
+ public CaptureResultExtras[] newArray(int size) {
+ return new CaptureResultExtras[size];
+ }
+ };
+
+ private CaptureResultExtras(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(requestId);
+ dest.writeInt(subsequenceId);
+ dest.writeInt(afTriggerId);
+ dest.writeInt(precaptureTriggerId);
+ dest.writeLong(frameNumber);
+ }
+
+ public void readFromParcel(Parcel in) {
+ requestId = in.readInt();
+ subsequenceId = in.readInt();
+ afTriggerId = in.readInt();
+ precaptureTriggerId = in.readInt();
+ frameNumber = in.readLong();
+ }
+
+ public int getRequestId() {
+ return requestId;
+ }
+
+ public int getSubsequenceId() {
+ return subsequenceId;
+ }
+
+ public int getAfTriggerId() {
+ return afTriggerId;
+ }
+
+ public int getPrecaptureTriggerId() {
+ return precaptureTriggerId;
+ }
+
+ public long getFrameNumber() {
+ return frameNumber;
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
index 02a73d66..a14d38b 100644
--- a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
+++ b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
@@ -17,6 +17,7 @@
package android.hardware.camera2;
import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.CaptureResultExtras;
/** @hide */
interface ICameraDeviceCallbacks
@@ -25,8 +26,9 @@ interface ICameraDeviceCallbacks
* Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
*/
- oneway void onCameraError(int errorCode);
+ oneway void onCameraError(int errorCode, in CaptureResultExtras resultExtras);
oneway void onCameraIdle();
- oneway void onCaptureStarted(int requestId, long timestamp);
- oneway void onResultReceived(int requestId, in CameraMetadataNative result);
+ oneway void onCaptureStarted(in CaptureResultExtras resultExtras, long timestamp);
+ oneway void onResultReceived(in CameraMetadataNative result,
+ in CaptureResultExtras resultExtras);
}
diff --git a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
index 1936963..d77f3d1 100644
--- a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
+++ b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
@@ -20,6 +20,8 @@ import android.view.Surface;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.LongParcelable;
+
/** @hide */
interface ICameraDeviceUser
{
@@ -31,9 +33,13 @@ interface ICameraDeviceUser
// ints here are status_t
// non-negative value is the requestId. negative value is status_t
- int submitRequest(in CaptureRequest request, boolean streaming);
+ int submitRequest(in CaptureRequest request, boolean streaming,
+ out LongParcelable lastFrameNumber);
+
+ int submitRequestList(in List<CaptureRequest> requestList, boolean streaming,
+ out LongParcelable lastFrameNumber);
- int cancelRequest(int requestId);
+ int cancelRequest(int requestId, out LongParcelable lastFrameNumber);
int deleteStream(int streamId);
@@ -46,5 +52,5 @@ interface ICameraDeviceUser
int waitUntilIdle();
- int flush();
+ int flush(out LongParcelable lastFrameNumber);
}
diff --git a/core/java/android/hardware/camera2/LongParcelable.aidl b/core/java/android/hardware/camera2/LongParcelable.aidl
new file mode 100644
index 0000000..7d7e51b
--- /dev/null
+++ b/core/java/android/hardware/camera2/LongParcelable.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.camera2;
+
+/** @hide */
+parcelable LongParcelable; \ No newline at end of file
diff --git a/core/java/android/hardware/camera2/LongParcelable.java b/core/java/android/hardware/camera2/LongParcelable.java
new file mode 100644
index 0000000..97b0631
--- /dev/null
+++ b/core/java/android/hardware/camera2/LongParcelable.java
@@ -0,0 +1,74 @@
+/*
+ * 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.camera2;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public class LongParcelable implements Parcelable {
+ private long number;
+
+ public LongParcelable() {
+ this.number = 0;
+ }
+
+ public LongParcelable(long number) {
+ this.number = number;
+ }
+
+ public static final Parcelable.Creator<LongParcelable> CREATOR =
+ new Parcelable.Creator<LongParcelable>() {
+ @Override
+ public LongParcelable createFromParcel(Parcel in) {
+ return new LongParcelable(in);
+ }
+
+ @Override
+ public LongParcelable[] newArray(int size) {
+ return new LongParcelable[size];
+ }
+ };
+
+ private LongParcelable(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(number);
+ }
+
+ public void readFromParcel(Parcel in) {
+ number = in.readLong();
+ }
+
+ public long getNumber() {
+ return number;
+ }
+
+ public void setNumber(long number) {
+ this.number = number;
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index 40586f0..cd44b51 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -21,8 +21,10 @@ import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CaptureResultExtras;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.LongParcelable;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.os.Handler;
@@ -33,10 +35,12 @@ import android.util.Log;
import android.util.SparseArray;
import android.view.Surface;
+import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.TreeSet;
/**
* HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
@@ -69,10 +73,24 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
private final String mCameraId;
+ /**
+ * A list tracking request and its expected last frame.
+ * Updated when calling ICameraDeviceUser methods.
+ */
+ private final List<SimpleEntry</*frameNumber*/Long, /*requestId*/Integer>>
+ mFrameNumberRequestPairs = new ArrayList<SimpleEntry<Long, Integer>>();
+
+ /**
+ * An object tracking received frame numbers.
+ * Updated when receiving callbacks from ICameraDeviceCallbacks.
+ */
+ private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
+
// Runnables for all state transitions, except error, which needs the
// error code argument
private final Runnable mCallOnOpened = new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onOpened(CameraDevice.this);
@@ -81,6 +99,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
};
private final Runnable mCallOnUnconfigured = new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onUnconfigured(CameraDevice.this);
@@ -89,6 +108,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
};
private final Runnable mCallOnActive = new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onActive(CameraDevice.this);
@@ -97,6 +117,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
};
private final Runnable mCallOnBusy = new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onBusy(CameraDevice.this);
@@ -105,14 +126,14 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
};
private final Runnable mCallOnClosed = new Runnable() {
+ @Override
public void run() {
- if (!CameraDevice.this.isClosed()) {
- mDeviceListener.onClosed(CameraDevice.this);
- }
+ mDeviceListener.onClosed(CameraDevice.this);
}
};
private final Runnable mCallOnIdle = new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onIdle(CameraDevice.this);
@@ -121,6 +142,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
};
private final Runnable mCallOnDisconnected = new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onDisconnected(CameraDevice.this);
@@ -251,22 +273,26 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
@Override
public int capture(CaptureRequest request, CaptureListener listener, Handler handler)
throws CameraAccessException {
- return submitCaptureRequest(request, listener, handler, /*streaming*/false);
+ if (DEBUG) {
+ Log.d(TAG, "calling capture");
+ }
+ List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
+ requestList.add(request);
+ return submitCaptureRequest(requestList, listener, handler, /*streaming*/false);
}
@Override
public int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
Handler handler) throws CameraAccessException {
+ // TODO: remove this. Throw IAE if the request is null or empty. Need to update API doc.
if (requests.isEmpty()) {
Log.w(TAG, "Capture burst request list is empty, do nothing!");
return -1;
}
- // TODO
- throw new UnsupportedOperationException("Burst capture implemented yet");
-
+ return submitCaptureRequest(requests, listener, handler, /*streaming*/false);
}
- private int submitCaptureRequest(CaptureRequest request, CaptureListener listener,
+ private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureListener listener,
Handler handler, boolean repeating) throws CameraAccessException {
// Need a valid handler, or current thread needs to have a looper, if
@@ -283,8 +309,13 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
stopRepeating();
}
+ LongParcelable lastFrameNumberRef = new LongParcelable();
try {
- requestId = mRemoteDevice.submitRequest(request, repeating);
+ requestId = mRemoteDevice.submitRequestList(requestList, repeating,
+ /*out*/lastFrameNumberRef);
+ if (!repeating) {
+ Log.v(TAG, "last frame number " + lastFrameNumberRef.getNumber());
+ }
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
@@ -292,12 +323,29 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
return -1;
}
if (listener != null) {
- mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener, request,
- handler, repeating));
+ mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener,
+ requestList, handler, repeating));
}
+ long lastFrameNumber = lastFrameNumberRef.getNumber();
+ /**
+ * If it's the first repeating request, then returned lastFrameNumber can be
+ * negative. Otherwise, it should always be non-negative.
+ */
+ if (((lastFrameNumber < 0) && (requestId > 0))
+ || ((lastFrameNumber < 0) && (!repeating))) {
+ throw new AssertionError(String.format("returned bad frame number %d",
+ lastFrameNumber));
+ }
if (repeating) {
+ if (mRepeatingRequestId != REQUEST_ID_NONE) {
+ mFrameNumberRequestPairs.add(
+ new SimpleEntry<Long, Integer>(lastFrameNumber, mRepeatingRequestId));
+ }
mRepeatingRequestId = requestId;
+ } else {
+ mFrameNumberRequestPairs.add(
+ new SimpleEntry<Long, Integer>(lastFrameNumber, requestId));
}
if (mIdle) {
@@ -312,18 +360,20 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
@Override
public int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
Handler handler) throws CameraAccessException {
- return submitCaptureRequest(request, listener, handler, /*streaming*/true);
+ List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
+ requestList.add(request);
+ return submitCaptureRequest(requestList, listener, handler, /*streaming*/true);
}
@Override
public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
Handler handler) throws CameraAccessException {
+ // TODO: remove this. Throw IAE if the request is null or empty. Need to update API doc.
if (requests.isEmpty()) {
Log.w(TAG, "Set Repeating burst request list is empty, do nothing!");
return -1;
}
- // TODO
- throw new UnsupportedOperationException("Burst capture implemented yet");
+ return submitCaptureRequest(requests, listener, handler, /*streaming*/true);
}
@Override
@@ -337,10 +387,20 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
mRepeatingRequestId = REQUEST_ID_NONE;
// Queue for deletion after in-flight requests finish
- mRepeatingRequestIdDeletedList.add(requestId);
+ if (mCaptureListenerMap.get(requestId) != null) {
+ mRepeatingRequestIdDeletedList.add(requestId);
+ }
try {
- mRemoteDevice.cancelRequest(requestId);
+ LongParcelable lastFrameNumberRef = new LongParcelable();
+ mRemoteDevice.cancelRequest(requestId, /*out*/lastFrameNumberRef);
+ long lastFrameNumber = lastFrameNumberRef.getNumber();
+ if ((lastFrameNumber < 0) && (requestId > 0)) {
+ throw new AssertionError(String.format("returned bad frame number %d",
+ lastFrameNumber));
+ }
+ mFrameNumberRequestPairs.add(
+ new SimpleEntry<Long, Integer>(lastFrameNumber, requestId));
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
@@ -351,8 +411,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
}
- @Override
- public void waitUntilIdle() throws CameraAccessException {
+ private void waitUntilIdle() throws CameraAccessException {
synchronized (mLock) {
checkIfCameraClosed();
@@ -370,8 +429,6 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
mRepeatingRequestId = REQUEST_ID_NONE;
- mRepeatingRequestIdDeletedList.clear();
- mCaptureListenerMap.clear();
}
}
@@ -382,7 +439,17 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
mDeviceHandler.post(mCallOnBusy);
try {
- mRemoteDevice.flush();
+ LongParcelable lastFrameNumberRef = new LongParcelable();
+ mRemoteDevice.flush(/*out*/lastFrameNumberRef);
+ if (mRepeatingRequestId != REQUEST_ID_NONE) {
+ long lastFrameNumber = lastFrameNumberRef.getNumber();
+ if (lastFrameNumber < 0) {
+ Log.e(TAG, String.format("returned bad frame number %d", lastFrameNumber));
+ }
+ mFrameNumberRequestPairs.add(
+ new SimpleEntry<Long, Integer>(lastFrameNumber, mRepeatingRequestId));
+ mRepeatingRequestId = REQUEST_ID_NONE;
+ }
} catch (CameraRuntimeException e) {
throw e.asChecked();
} catch (RemoteException e) {
@@ -428,18 +495,18 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
private final boolean mRepeating;
private final CaptureListener mListener;
- private final CaptureRequest mRequest;
+ private final List<CaptureRequest> mRequestList;
private final Handler mHandler;
- CaptureListenerHolder(CaptureListener listener, CaptureRequest request, Handler handler,
- boolean repeating) {
+ CaptureListenerHolder(CaptureListener listener, List<CaptureRequest> requestList,
+ Handler handler, boolean repeating) {
if (listener == null || handler == null) {
throw new UnsupportedOperationException(
"Must have a valid handler and a valid listener");
}
mRepeating = repeating;
mHandler = handler;
- mRequest = request;
+ mRequestList = new ArrayList<CaptureRequest>(requestList);
mListener = listener;
}
@@ -451,8 +518,24 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
return mListener;
}
+ public CaptureRequest getRequest(int subsequenceId) {
+ if (subsequenceId >= mRequestList.size()) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Requested subsequenceId %d is larger than request list size %d.",
+ subsequenceId, mRequestList.size()));
+ } else {
+ if (subsequenceId < 0) {
+ throw new IllegalArgumentException(String.format(
+ "Requested subsequenceId %d is negative", subsequenceId));
+ } else {
+ return mRequestList.get(subsequenceId);
+ }
+ }
+ }
+
public CaptureRequest getRequest() {
- return mRequest;
+ return getRequest(0);
}
public Handler getHandler() {
@@ -461,6 +544,105 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
+ /**
+ * This class tracks the last frame number for submitted requests.
+ */
+ public class FrameNumberTracker {
+
+ private long mCompletedFrameNumber = -1;
+ private final TreeSet<Long> mFutureErrorSet = new TreeSet<Long>();
+
+ private void update() {
+ Iterator<Long> iter = mFutureErrorSet.iterator();
+ while (iter.hasNext()) {
+ long errorFrameNumber = iter.next();
+ if (errorFrameNumber == mCompletedFrameNumber + 1) {
+ mCompletedFrameNumber++;
+ iter.remove();
+ } else {
+ break;
+ }
+ }
+ }
+
+ /**
+ * This function is called every time when a result or an error is received.
+ * @param frameNumber: the frame number corresponding to the result or error
+ * @param isError: true if it is an error, false if it is not an error
+ */
+ public void updateTracker(long frameNumber, boolean isError) {
+ if (isError) {
+ mFutureErrorSet.add(frameNumber);
+ } else {
+ /**
+ * HAL cannot send an OnResultReceived for frame N unless it knows for
+ * sure that all frames prior to N have either errored out or completed.
+ * So if the current frame is not an error, then all previous frames
+ * should have arrived. The following line checks whether this holds.
+ */
+ if (frameNumber != mCompletedFrameNumber + 1) {
+ throw new AssertionError(String.format(
+ "result frame number %d comes out of order",
+ frameNumber));
+ }
+ mCompletedFrameNumber++;
+ }
+ update();
+ }
+
+ public long getCompletedFrameNumber() {
+ return mCompletedFrameNumber;
+ }
+
+ }
+
+ private void checkAndFireSequenceComplete() {
+ long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
+ Iterator<SimpleEntry<Long, Integer> > iter = mFrameNumberRequestPairs.iterator();
+ while (iter.hasNext()) {
+ final SimpleEntry<Long, Integer> frameNumberRequestPair = iter.next();
+ if (frameNumberRequestPair.getKey() <= completedFrameNumber) {
+
+ // remove request from mCaptureListenerMap
+ final int requestId = frameNumberRequestPair.getValue();
+ final CaptureListenerHolder holder;
+ synchronized (mLock) {
+ int index = CameraDevice.this.mCaptureListenerMap.indexOfKey(requestId);
+ holder = (index >= 0) ? CameraDevice.this.mCaptureListenerMap.valueAt(index)
+ : null;
+ if (holder != null) {
+ CameraDevice.this.mCaptureListenerMap.removeAt(index);
+ }
+ }
+ iter.remove();
+
+ // Call onCaptureSequenceCompleted
+ if (holder != null) {
+ Runnable resultDispatch = new Runnable() {
+ @Override
+ public void run() {
+ if (!CameraDevice.this.isClosed()){
+ if (DEBUG) {
+ Log.d(TAG, String.format(
+ "fire sequence complete for request %d",
+ requestId));
+ }
+
+ holder.getListener().onCaptureSequenceCompleted(
+ CameraDevice.this,
+ requestId,
+ // TODO: this is problematic, crop long to int
+ frameNumberRequestPair.getKey().intValue());
+ }
+ }
+ };
+ holder.getHandler().post(resultDispatch);
+ }
+
+ }
+ }
+ }
+
public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
//
@@ -495,7 +677,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
@Override
- public void onCameraError(final int errorCode) {
+ public void onCameraError(final int errorCode, CaptureResultExtras resultExtras) {
Runnable r = null;
if (isClosed()) return;
@@ -510,6 +692,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
case ERROR_CAMERA_DEVICE:
case ERROR_CAMERA_SERVICE:
r = new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
mDeviceListener.onError(CameraDevice.this, errorCode);
@@ -520,6 +703,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
CameraDevice.this.mDeviceHandler.post(r);
}
+
+ // Fire onCaptureSequenceCompleted
+ mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true);
+ checkAndFireSequenceComplete();
+
}
@Override
@@ -538,7 +726,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
@Override
- public void onCaptureStarted(int requestId, final long timestamp) {
+ public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
+ int requestId = resultExtras.getRequestId();
if (DEBUG) {
Log.d(TAG, "Capture started for id " + requestId);
}
@@ -558,11 +747,12 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
// Dispatch capture start notice
holder.getHandler().post(
new Runnable() {
+ @Override
public void run() {
if (!CameraDevice.this.isClosed()) {
holder.getListener().onCaptureStarted(
CameraDevice.this,
- holder.getRequest(),
+ holder.getRequest(resultExtras.getSubsequenceId()),
timestamp);
}
}
@@ -570,48 +760,18 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
@Override
- public void onResultReceived(int requestId, CameraMetadataNative result)
- throws RemoteException {
+ public void onResultReceived(CameraMetadataNative result,
+ CaptureResultExtras resultExtras) throws RemoteException {
+ int requestId = resultExtras.getRequestId();
if (DEBUG) {
Log.d(TAG, "Received result for id " + requestId);
}
- final CaptureListenerHolder holder;
+ final CaptureListenerHolder holder =
+ CameraDevice.this.mCaptureListenerMap.get(requestId);
Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
- synchronized (mLock) {
- // TODO: move this whole map into this class to make it more testable,
- // exposing the methods necessary like subscribeToRequest, unsubscribe..
- // TODO: make class static class
-
- holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
-
- // Clean up listener once we no longer expect to see it.
- if (holder != null && !holder.isRepeating() && !quirkIsPartialResult) {
- CameraDevice.this.mCaptureListenerMap.remove(requestId);
- }
-
- // TODO: add 'capture sequence completed' callback to the
- // service, and clean up repeating requests there instead.
-
- // If we received a result for a repeating request and have
- // prior repeating requests queued for deletion, remove those
- // requests from mCaptureListenerMap.
- if (holder != null && holder.isRepeating() && !quirkIsPartialResult
- && mRepeatingRequestIdDeletedList.size() > 0) {
- Iterator<Integer> iter = mRepeatingRequestIdDeletedList.iterator();
- while (iter.hasNext()) {
- int deletedRequestId = iter.next();
- if (deletedRequestId < requestId) {
- CameraDevice.this.mCaptureListenerMap.remove(deletedRequestId);
- iter.remove();
- }
- }
- }
-
- }
-
// Check if we have a listener for this
if (holder == null) {
return;
@@ -619,7 +779,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
if (isClosed()) return;
- final CaptureRequest request = holder.getRequest();
+ final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId);
Runnable resultDispatch = null;
@@ -654,6 +814,12 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
holder.getHandler().post(resultDispatch);
+
+ // Fire onCaptureSequenceCompleted
+ if (!quirkIsPartialResult) {
+ mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/false);
+ checkAndFireSequenceComplete();
+ }
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 072c5bb..c5e5753 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,11 @@ 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();
+ } else if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS)) {
+ return (T) getAvailableStreamConfigurations();
+ } else if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS)) {
+ return (T) getAvailableMinFrameDurations();
}
// For other keys, get() falls back to getBase()
@@ -457,15 +473,62 @@ 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;
}
+ private int[] getAvailableStreamConfigurations() {
+ final int NUM_ELEMENTS_IN_CONFIG = 4;
+ int[] availableConfigs =
+ getBase(CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS);
+ if (availableConfigs != null) {
+ if (availableConfigs.length % NUM_ELEMENTS_IN_CONFIG != 0) {
+ Log.w(TAG, "availableStreamConfigurations is malformed, length must be multiple"
+ + " of " + NUM_ELEMENTS_IN_CONFIG);
+ return availableConfigs;
+ }
+
+ for (int i = 0; i < availableConfigs.length; i += NUM_ELEMENTS_IN_CONFIG) {
+ // JPEG has different value between native and managed side, need override.
+ if (availableConfigs[i] == NATIVE_JPEG_FORMAT) {
+ availableConfigs[i] = ImageFormat.JPEG;
+ }
+ }
+ }
+
+ return availableConfigs;
+ }
+
+ private long[] getAvailableMinFrameDurations() {
+ final int NUM_ELEMENTS_IN_DURATION = 4;
+ long[] availableMinDurations =
+ getBase(CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS);
+ if (availableMinDurations != null) {
+ if (availableMinDurations.length % NUM_ELEMENTS_IN_DURATION != 0) {
+ Log.w(TAG, "availableStreamConfigurations is malformed, length must be multiple"
+ + " of " + NUM_ELEMENTS_IN_DURATION);
+ return availableMinDurations;
+ }
+
+ for (int i = 0; i < availableMinDurations.length; i += NUM_ELEMENTS_IN_DURATION) {
+ // JPEG has different value between native and managed side, need override.
+ if (availableMinDurations[i] == NATIVE_JPEG_FORMAT) {
+ availableMinDurations[i] = ImageFormat.JPEG;
+ }
+ }
+ }
+
+ return availableMinDurations;
+ }
+
private Face[] getFaces() {
final int FACE_LANDMARK_SIZE = 6;
@@ -550,7 +613,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,12 +653,58 @@ 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);
+ } else if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS)) {
+ return setAvailableStreamConfigurations((int[])value);
+ } else if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS)) {
+ return setAvailableMinFrameDurations((long[])value);
}
// For other keys, set() falls back to setBase().
return false;
}
+ private boolean setAvailableStreamConfigurations(int[] value) {
+ final int NUM_ELEMENTS_IN_CONFIG = 4;
+ int[] availableConfigs = value;
+ if (value == null) {
+ // Let setBase() to handle the null value case.
+ return false;
+ }
+
+ int[] newValues = new int[availableConfigs.length];
+ for (int i = 0; i < availableConfigs.length; i++) {
+ newValues[i] = availableConfigs[i];
+ if (i % NUM_ELEMENTS_IN_CONFIG == 0 && availableConfigs[i] == ImageFormat.JPEG) {
+ newValues[i] = NATIVE_JPEG_FORMAT;
+ }
+ }
+
+ setBase(CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS, newValues);
+ return true;
+ }
+
+ private boolean setAvailableMinFrameDurations(long[] value) {
+ final int NUM_ELEMENTS_IN_DURATION = 4;
+ long[] availableDurations = value;
+ if (value == null) {
+ // Let setBase() to handle the null value case.
+ return false;
+ }
+
+ long[] newValues = new long[availableDurations.length];
+ for (int i = 0; i < availableDurations.length; i++) {
+ newValues[i] = availableDurations[i];
+ if (i % NUM_ELEMENTS_IN_DURATION == 0 && availableDurations[i] == ImageFormat.JPEG) {
+ newValues[i] = NATIVE_JPEG_FORMAT;
+ }
+ }
+
+ setBase(CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS, newValues);
+ return true;
+ }
+
private boolean setAvailableFormats(int[] value) {
int[] availableFormat = value;
if (value == null) {
@@ -615,6 +724,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..b5efa8e 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
@@ -814,13 +853,21 @@ public final class InputManager {
return true;
}
+ /**
+ * @hide
+ */
@Override
- public void vibrate(long milliseconds) {
+ public void vibrate(int uid, String opPkg, long milliseconds,
+ int streamHint) {
vibrate(new long[] { 0, milliseconds}, -1);
}
+ /**
+ * @hide
+ */
@Override
- public void vibrate(long[] pattern, int repeat) {
+ public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
+ int streamHint) {
if (repeat >= pattern.length) {
throw new ArrayIndexOutOfBoundsException();
}
@@ -831,22 +878,6 @@ public final class InputManager {
}
}
- /**
- * @hide
- */
- @Override
- public void vibrate(int owningUid, String owningPackage, long milliseconds) {
- vibrate(milliseconds);
- }
-
- /**
- * @hide
- */
- @Override
- public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) {
- vibrate(pattern, repeat);
- }
-
@Override
public void cancel() {
try {
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..c51d1a7 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -249,6 +249,16 @@ public class InputMethodService extends AbstractInputMethodService {
*/
public static final int IME_VISIBLE = 0x2;
+ /**
+ * The IME does not require cursor/anchor position.
+ */
+ public static final int CURSOR_ANCHOR_MONITOR_MODE_NONE = 0x0;
+
+ /**
+ * The IME expects that {@link #onUpdateCursor(Rect)} is called back.
+ */
+ public static final int CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT = 0x1;
+
InputMethodManager mImm;
int mTheme = 0;
@@ -647,6 +657,7 @@ public class InputMethodService extends AbstractInputMethodService {
getApplicationInfo().targetSdkVersion,
android.R.style.Theme_InputMethod,
android.R.style.Theme_Holo_InputMethod,
+ android.R.style.Theme_DeviceDefault_InputMethod,
android.R.style.Theme_DeviceDefault_InputMethod);
super.setTheme(mTheme);
super.onCreate();
@@ -1701,6 +1712,13 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
+ * Update the cursor/anthor monitor mode.
+ */
+ public void setCursorAnchorMonitorMode(int monitorMode) {
+ mImm.setCursorAnchorMonitorMode(mToken, monitorMode);
+ }
+
+ /**
* Close this input method's soft input area, removing it from the display.
* The input method will continue running, but the user can no longer use
* it to generate input by touching the screen.
@@ -2322,6 +2340,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..3da00b1 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
*/
@@ -171,6 +175,11 @@ public class ConnectivityManager {
* {@hide}
*/
public static final String EXTRA_IS_ACTIVE = "isActive";
+ /**
+ * The lookup key for a long that contains the timestamp (nanos) of the radio state change.
+ * {@hide}
+ */
+ public static final String EXTRA_REALTIME_NS = "tsNanos";
/**
* Broadcast Action: The setting for background data usage has changed
@@ -403,6 +412,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 +814,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 +919,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 +1119,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 +1307,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 +1440,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 +1460,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..10b5d0b 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -106,6 +106,24 @@ public class EthernetDataTracker extends BaseNetworkStateTracker {
mLinkCapabilities = new LinkCapabilities();
}
+ private void interfaceUpdated() {
+ // we don't get link status indications unless the iface is up - bring it up
+ try {
+ mNMService.setInterfaceUp(mIface);
+ String hwAddr = null;
+ InterfaceConfiguration config = mNMService.getInterfaceConfig(mIface);
+ if (config != null) {
+ hwAddr = config.getHardwareAddress();
+ }
+ synchronized (this) {
+ mHwAddr = hwAddr;
+ mNetworkInfo.setExtraInfo(mHwAddr);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error upping interface " + mIface + ": " + e);
+ }
+ }
+
private void interfaceAdded(String iface) {
if (!iface.matches(sIfaceMatch))
return;
@@ -118,12 +136,7 @@ public class EthernetDataTracker extends BaseNetworkStateTracker {
mIface = iface;
}
- // we don't get link status indications unless the iface is up - bring it up
- try {
- mNMService.setInterfaceUp(iface);
- } catch (Exception e) {
- Log.e(TAG, "Error upping interface " + iface + ": " + e);
- }
+ interfaceUpdated();
mNetworkInfo.setIsAvailable(true);
Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
@@ -159,15 +172,21 @@ public class EthernetDataTracker extends BaseNetworkStateTracker {
Log.d(TAG, "Removing " + iface);
disconnect();
- mIface = "";
+ synchronized (this) {
+ mIface = "";
+ mHwAddr = null;
+ mNetworkInfo.setExtraInfo(null);
+ }
}
private void runDhcp() {
Thread dhcpThread = new Thread(new Runnable() {
public void run() {
DhcpResults dhcpResults = new DhcpResults();
+ mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddr);
if (!NetworkUtils.runDhcp(mIface, dhcpResults)) {
Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError());
+ mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
return;
}
mLinkProperties = dhcpResults.linkProperties;
@@ -220,15 +239,7 @@ public class EthernetDataTracker extends BaseNetworkStateTracker {
for (String iface : ifaces) {
if (iface.matches(sIfaceMatch)) {
mIface = iface;
- mNMService.setInterfaceUp(iface);
- InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
- mLinkUp = config.hasFlag("up");
- if (config != null && mHwAddr == null) {
- mHwAddr = config.getHardwareAddress();
- if (mHwAddr != null) {
- mNetworkInfo.setExtraInfo(mHwAddr);
- }
- }
+ interfaceUpdated();
// if a DHCP client had previously been started for this interface, then stop it
NetworkUtils.stopDhcp(mIface);
@@ -270,11 +281,6 @@ public class EthernetDataTracker extends BaseNetworkStateTracker {
}
@Override
- public void captivePortalCheckComplete() {
- // not implemented
- }
-
- @Override
public void captivePortalCheckCompleted(boolean isCaptivePortal) {
// not implemented
}
@@ -423,4 +429,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/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
index 5b16f8b..dd9c39f 100644
--- a/core/java/android/net/INetworkManagementEventObserver.aidl
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -86,8 +86,9 @@ interface INetworkManagementEventObserver {
*
* @param iface The interface.
* @param active True if the interface is actively transmitting data, false if it is idle.
+ * @param tsNanos Elapsed realtime in nanos when the state of the network interface changed.
*/
- void interfaceClassDataActivityChanged(String label, boolean active);
+ void interfaceClassDataActivityChanged(String label, boolean active, long tsNanos);
/**
* Information about available DNS servers has been received.
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 21352bf..a470e88 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/NetworkKey.aidl b/core/java/android/net/NetworkKey.aidl
new file mode 100644
index 0000000..637075f
--- /dev/null
+++ b/core/java/android/net/NetworkKey.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.net;
+
+parcelable NetworkKey;
diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java
new file mode 100644
index 0000000..cc3ad3e
--- /dev/null
+++ b/core/java/android/net/NetworkKey.java
@@ -0,0 +1,105 @@
+/*
+ * 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.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information which identifies a specific network.
+ *
+ * @hide
+ */
+public class NetworkKey implements Parcelable {
+
+ /** A wifi network, for which {@link #wifiKey} will be populated. */
+ public static final int TYPE_WIFI = 1;
+
+ /**
+ * The type of this network.
+ * @see #TYPE_WIFI
+ */
+ public final int type;
+
+ /**
+ * Information identifying a Wi-Fi network. Only set when {@link #type} equals
+ * {@link #TYPE_WIFI}.
+ */
+ public final WifiKey wifiKey;
+
+ /**
+ * Construct a new {@link NetworkKey} for a Wi-Fi network.
+ * @param wifiKey the {@link WifiKey} identifying this Wi-Fi network.
+ */
+ public NetworkKey(WifiKey wifiKey) {
+ this.type = TYPE_WIFI;
+ this.wifiKey = wifiKey;
+ }
+
+ private NetworkKey(Parcel in) {
+ type = in.readInt();
+ switch (type) {
+ case TYPE_WIFI:
+ wifiKey = WifiKey.CREATOR.createFromParcel(in);
+ break;
+ default:
+ throw new IllegalArgumentException("Parcel has unknown type: " + type);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(type);
+ switch (type) {
+ case TYPE_WIFI:
+ wifiKey.writeToParcel(out, flags);
+ break;
+ default:
+ throw new IllegalStateException("NetworkKey has unknown type " + type);
+ }
+ }
+
+ @Override
+ public String toString() {
+ switch (type) {
+ case TYPE_WIFI:
+ return wifiKey.toString();
+ default:
+ // Don't throw an exception here in case someone is logging this object in a catch
+ // block for debugging purposes.
+ return "InvalidKey";
+ }
+ }
+
+ public static final Parcelable.Creator<NetworkKey> CREATOR =
+ new Parcelable.Creator<NetworkKey>() {
+ @Override
+ public NetworkKey createFromParcel(Parcel in) {
+ return new NetworkKey(in);
+ }
+
+ @Override
+ public NetworkKey[] newArray(int size) {
+ return new NetworkKey[size];
+ }
+ };
+}
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
new file mode 100644
index 0000000..3430547
--- /dev/null
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -0,0 +1,118 @@
+/*
+ * 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.net;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.Context;
+
+/**
+ * Class that manages communication between network subsystems and a network scorer.
+ *
+ * <p>You can get an instance of this class by calling
+ * {@link android.content.Context#getSystemService(String)}:
+ *
+ * <pre>NetworkScoreManager manager =
+ * (NetworkScoreManager) getSystemService(Context.NETWORK_SCORE_SERVICE)</pre>
+ *
+ * <p>A network scorer is any application which:
+ * <ul>
+ * <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission.
+ * <li>Includes a receiver for {@link #ACTION_SCORE_NETWORKS} guarded by the
+ * {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission which scores networks
+ * and (eventually) calls {@link #updateScores} with the results.
+ * </ul>
+ *
+ * <p>The system keeps track of a default scorer application; at any time, only this application
+ * will receive {@link #ACTION_SCORE_NETWORKS} broadcasts and will be permitted to call
+ * {@link #updateScores}. Applications may determine the current default scorer with
+ * {@link #getDefaultScorerPackage()} and request to change the default scorer by sending an
+ * {@link #ACTION_CHANGE_DEFAULT} broadcast with another scorer.
+ *
+ * @hide
+ */
+public class NetworkScoreManager {
+ /**
+ * Activity action: ask the user to change the default network scorer. This will show a dialog
+ * that asks the user whether they want to replace the current default scorer with the one
+ * specified in {@link #EXTRA_PACKAGE_NAME}. The activity will finish with RESULT_OK if the
+ * default was changed or RESULT_CANCELED if it failed for any reason.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHANGE_DEFAULT = "android.net.scoring.CHANGE_DEFAULT";
+
+ /**
+ * Extra used with {@link #ACTION_CHANGE_DEFAULT} to specify the new scorer package. Set with
+ * {@link android.content.Intent#putExtra(String, String)}.
+ */
+ public static final String EXTRA_PACKAGE_NAME = "packageName";
+
+ /**
+ * Broadcast action: new network scores are being requested. This intent will only be delivered
+ * to the current default scorer app. That app is responsible for scoring the networks and
+ * calling {@link #updateScores} when complete. The networks to score are specified in
+ * {@link #EXTRA_NETWORKS_TO_SCORE}, and will generally consist of all networks which have been
+ * configured by the user as well as any open networks.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_SCORE_NETWORKS = "android.net.scoring.SCORE_NETWORKS";
+
+ /**
+ * Extra used with {@link #ACTION_SCORE_NETWORKS} to specify the networks to be scored, as an
+ * array of {@link NetworkKey}s. Can be obtained with
+ * {@link android.content.Intent#getParcelableArrayExtra(String)}}.
+ */
+ public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore";
+
+ private final Context mContext;
+
+ /** @hide */
+ public NetworkScoreManager(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Obtain the package name of the current default network scorer.
+ *
+ * At any time, only one scorer application will receive {@link #ACTION_SCORE_NETWORKS}
+ * broadcasts and be allowed to call {@link #updateScores}. Applications may use this method to
+ * determine the current scorer and offer the user the ability to select a different scorer via
+ * the {@link #ACTION_CHANGE_DEFAULT} intent.
+ * @return the full package name of the current default scorer, or null if there is no active
+ * scorer.
+ */
+ public String getDefaultScorerPackage() {
+ // TODO: Implement.
+ return null;
+ }
+
+ /**
+ * Update network scores.
+ *
+ * This may be called at any time to re-score active networks. Scores will generally be updated
+ * quickly, but if this method is called too frequently, the scores may be held and applied at
+ * a later time.
+ *
+ * @param networks the networks which have been scored by the scorer.
+ * @throws SecurityException if the caller is not the default scorer.
+ */
+ public void updateScores(ScoredNetwork[] networks) throws SecurityException {
+ // TODO: Implement.
+ }
+}
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/RssiCurve.java b/core/java/android/net/RssiCurve.java
new file mode 100644
index 0000000..7af7998
--- /dev/null
+++ b/core/java/android/net/RssiCurve.java
@@ -0,0 +1,129 @@
+/*
+ * 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.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A curve defining the network score over a range of RSSI values.
+ *
+ * <p>For each RSSI bucket, the score may be any byte. Scores have no absolute meaning and are only
+ * considered relative to other scores assigned by the same scorer. Networks with no score are all
+ * considered equivalent and ranked below any network with a score.
+ *
+ * <p>For example, consider a curve starting at -110 dBm with a bucket width of 10 and the
+ * following buckets: {@code [-20, -10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]}.
+ * This represents a linear curve between -110 dBm and 30 dBm. It scores progressively higher at
+ * stronger signal strengths.
+ *
+ * <p>A network can be assigned a fixed score independent of RSSI by setting
+ * {@link #rssiBuckets} to a one-byte array whose element is the fixed score. {@link #start}
+ * should be set to the lowest RSSI value at which this fixed score should apply, and
+ * {@link #bucketWidth} should be set such that {@code start + bucketWidth} is equal to the
+ * highest RSSI value at which this fixed score should apply.
+ *
+ * <p>Note that RSSI values below -110 dBm or above 30 dBm are unlikely to cause any difference
+ * in connectivity behavior from those endpoints. That is, the connectivity framework will treat
+ * a network with a -120 dBm signal exactly as it would treat one with a -110 dBm signal.
+ * Therefore, graphs which specify scores outside this range may be truncated to this range by
+ * the system.
+ *
+ * @see ScoredNetwork
+ * @hide
+ */
+public class RssiCurve implements Parcelable {
+
+ /** The starting dBm of the curve. */
+ public final int start;
+
+ /** The width of each RSSI bucket, in dBm. */
+ public final int bucketWidth;
+
+ /** The score for each RSSI bucket. */
+ public final byte[] rssiBuckets;
+
+ /**
+ * Construct a new {@link RssiCurve}.
+ *
+ * @param start the starting dBm of the curve.
+ * @param bucketWidth the width of each RSSI bucket, in dBm.
+ * @param rssiBuckets the score for each RSSI bucket.
+ */
+ public RssiCurve(int start, int bucketWidth, byte[] rssiBuckets) {
+ this.start = start;
+ this.bucketWidth = bucketWidth;
+ if (rssiBuckets == null || rssiBuckets.length == 0) {
+ throw new IllegalArgumentException("rssiBuckets must be at least one element large.");
+ }
+ this.rssiBuckets = rssiBuckets;
+ }
+
+ private RssiCurve(Parcel in) {
+ start = in.readInt();
+ bucketWidth = in.readInt();
+ int bucketCount = in.readInt();
+ rssiBuckets = new byte[bucketCount];
+ in.readByteArray(rssiBuckets);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(start);
+ out.writeInt(bucketWidth);
+ out.writeInt(rssiBuckets.length);
+ out.writeByteArray(rssiBuckets);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("RssiCurve[start=")
+ .append(start)
+ .append(",bucketWidth=")
+ .append(bucketWidth);
+
+ sb.append(",buckets=");
+ for (int i = 0; i < rssiBuckets.length; i++) {
+ sb.append(rssiBuckets[i]);
+ if (i < rssiBuckets.length - 1) {
+ sb.append(",");
+ }
+ }
+ sb.append("]");
+
+ return sb.toString();
+ }
+
+ public static final Creator<RssiCurve> CREATOR =
+ new Creator<RssiCurve>() {
+ @Override
+ public RssiCurve createFromParcel(Parcel in) {
+ return new RssiCurve(in);
+ }
+
+ @Override
+ public RssiCurve[] newArray(int size) {
+ return new RssiCurve[size];
+ }
+ };
+}
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/ScoredNetwork.aidl b/core/java/android/net/ScoredNetwork.aidl
new file mode 100644
index 0000000..f83db11
--- /dev/null
+++ b/core/java/android/net/ScoredNetwork.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.net;
+
+parcelable ScoredNetwork;
diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java
new file mode 100644
index 0000000..8af3c3c
--- /dev/null
+++ b/core/java/android/net/ScoredNetwork.java
@@ -0,0 +1,99 @@
+/*
+ * 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.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A network identifier along with a score for the quality of that network.
+ *
+ * @hide
+ */
+public class ScoredNetwork implements Parcelable {
+
+ /** A {@link NetworkKey} uniquely identifying this network. */
+ public final NetworkKey networkKey;
+
+ /**
+ * The {@link RssiCurve} representing the scores for this network based on the RSSI.
+ *
+ * <p>This field is optional and may be set to null to indicate that no score is available for
+ * this network at this time. Such networks, along with networks for which the scorer has not
+ * responded, are always prioritized below scored networks, regardless of the score.
+ */
+ public final RssiCurve rssiCurve;
+
+ /**
+ * Construct a new {@link ScoredNetwork}.
+ *
+ * @param networkKey the {@link NetworkKey} uniquely identifying this network.
+ * @param rssiCurve the {@link RssiCurve} representing the scores for this network based on the
+ * RSSI. This field is optional, and may be skipped to represent a network which the scorer
+ * has opted not to score at this time. Passing a null value here is strongly preferred to
+ * not returning any {@link ScoredNetwork} for a given {@link NetworkKey} because it
+ * indicates to the system not to request scores for this network in the future, although
+ * the scorer may choose to issue an out-of-band update at any time.
+ */
+ public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve) {
+ this.networkKey = networkKey;
+ this.rssiCurve = rssiCurve;
+ }
+
+ private ScoredNetwork(Parcel in) {
+ networkKey = NetworkKey.CREATOR.createFromParcel(in);
+ if (in.readByte() == 1) {
+ rssiCurve = RssiCurve.CREATOR.createFromParcel(in);
+ } else {
+ rssiCurve = null;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ networkKey.writeToParcel(out, flags);
+ if (rssiCurve != null) {
+ out.writeByte((byte) 1);
+ rssiCurve.writeToParcel(out, flags);
+ } else {
+ out.writeByte((byte) 0);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ScoredNetwork[key=" + networkKey + ",score=" + rssiCurve + "]";
+ }
+
+ public static final Parcelable.Creator<ScoredNetwork> CREATOR =
+ new Parcelable.Creator<ScoredNetwork>() {
+ @Override
+ public ScoredNetwork createFromParcel(Parcel in) {
+ return new ScoredNetwork(in);
+ }
+
+ @Override
+ public ScoredNetwork[] newArray(int size) {
+ return new ScoredNetwork[size];
+ }
+ };
+}
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/WifiKey.java b/core/java/android/net/WifiKey.java
new file mode 100644
index 0000000..ffcd85a
--- /dev/null
+++ b/core/java/android/net/WifiKey.java
@@ -0,0 +1,106 @@
+/*
+ * 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.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.regex.Pattern;
+
+/**
+ * Information identifying a Wi-Fi network.
+ * @see NetworkKey
+ *
+ * @hide
+ */
+public class WifiKey implements Parcelable {
+
+ // Patterns used for validation.
+ private static final Pattern SSID_PATTERN = Pattern.compile("(\".*\")|(0x[\\p{XDigit}]+)");
+ private static final Pattern BSSID_PATTERN =
+ Pattern.compile("([\\p{XDigit}]{2}:){5}[\\p{XDigit}]{2}");
+
+ /**
+ * The service set identifier (SSID) of an 802.11 network. If the SSID can be decoded as
+ * UTF-8, it will be surrounded by double quotation marks. Otherwise, it will be a string of
+ * hex digits starting with 0x.
+ */
+ public final String ssid;
+
+ /**
+ * The basic service set identifier (BSSID) of an access point for this network. This will
+ * be in the form of a six-byte MAC address: {@code XX:XX:XX:XX:XX:XX}, where each X is a
+ * hexadecimal digit.
+ */
+ public final String bssid;
+
+ /**
+ * Construct a new {@link WifiKey} for the given Wi-Fi SSID/BSSID pair.
+ *
+ * @param ssid the service set identifier (SSID) of an 802.11 network. If the SSID can be
+ * decoded as UTF-8, it should be surrounded by double quotation marks. Otherwise,
+ * it should be a string of hex digits starting with 0x.
+ * @param bssid the basic service set identifier (BSSID) of this network's access point.
+ * This should be in the form of a six-byte MAC address: {@code XX:XX:XX:XX:XX:XX},
+ * where each X is a hexadecimal digit.
+ * @throws IllegalArgumentException if either the SSID or BSSID is invalid.
+ */
+ public WifiKey(String ssid, String bssid) {
+ if (!SSID_PATTERN.matcher(ssid).matches()) {
+ throw new IllegalArgumentException("Invalid ssid: " + ssid);
+ }
+ if (!BSSID_PATTERN.matcher(bssid).matches()) {
+ throw new IllegalArgumentException("Invalid bssid: " + bssid);
+ }
+ this.ssid = ssid;
+ this.bssid = bssid;
+ }
+
+ private WifiKey(Parcel in) {
+ ssid = in.readString();
+ bssid = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(ssid);
+ out.writeString(bssid);
+ }
+
+ @Override
+ public String toString() {
+ return "WifiKey[SSID=" + ssid + ",BSSID=" + bssid + "]";
+ }
+
+ public static final Creator<WifiKey> CREATOR =
+ new Creator<WifiKey>() {
+ @Override
+ public WifiKey createFromParcel(Parcel in) {
+ return new WifiKey(in);
+ }
+
+ @Override
+ public WifiKey[] newArray(int size) {
+ return new WifiKey[size];
+ }
+ };
+}
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 d730a7b..830ddce 100644
--- a/core/java/android/net/http/X509TrustManagerExtensions.java
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -76,4 +76,18 @@ public class X509TrustManagerExtensions {
return mDelegate.checkServerTrusted(chain, authType,
new DelegatingSSLSession.HostnameWrap(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..377ed88 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;
@@ -213,6 +211,7 @@ public final class NsdManager {
private Context mContext;
private static final int INVALID_LISTENER_KEY = 0;
+ private static final int BUSY_LISTENER_KEY = -1;
private int mListenerKey = 1;
private final SparseArray mListenerMap = new SparseArray();
private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<NsdServiceInfo>();
@@ -319,71 +318,74 @@ public final class NsdManager {
Log.d(TAG, "Stale key " + message.arg2);
return;
}
- boolean listenerRemove = true;
NsdServiceInfo ns = getNsdService(message.arg2);
switch (message.what) {
case DISCOVER_SERVICES_STARTED:
String s = getNsdServiceInfoType((NsdServiceInfo) message.obj);
((DiscoveryListener) listener).onDiscoveryStarted(s);
- // Keep listener until stop discovery
- listenerRemove = false;
break;
case DISCOVER_SERVICES_FAILED:
+ removeListener(message.arg2);
((DiscoveryListener) listener).onStartDiscoveryFailed(getNsdServiceInfoType(ns),
message.arg1);
break;
case SERVICE_FOUND:
((DiscoveryListener) listener).onServiceFound((NsdServiceInfo) message.obj);
- // Keep listener until stop discovery
- listenerRemove = false;
break;
case SERVICE_LOST:
((DiscoveryListener) listener).onServiceLost((NsdServiceInfo) message.obj);
- // Keep listener until stop discovery
- listenerRemove = false;
break;
case STOP_DISCOVERY_FAILED:
+ removeListener(message.arg2);
((DiscoveryListener) listener).onStopDiscoveryFailed(getNsdServiceInfoType(ns),
message.arg1);
break;
case STOP_DISCOVERY_SUCCEEDED:
+ removeListener(message.arg2);
((DiscoveryListener) listener).onDiscoveryStopped(getNsdServiceInfoType(ns));
break;
case REGISTER_SERVICE_FAILED:
+ removeListener(message.arg2);
((RegistrationListener) listener).onRegistrationFailed(ns, message.arg1);
break;
case REGISTER_SERVICE_SUCCEEDED:
((RegistrationListener) listener).onServiceRegistered(
(NsdServiceInfo) message.obj);
- // Keep listener until unregister
- listenerRemove = false;
break;
case UNREGISTER_SERVICE_FAILED:
+ removeListener(message.arg2);
((RegistrationListener) listener).onUnregistrationFailed(ns, message.arg1);
break;
case UNREGISTER_SERVICE_SUCCEEDED:
+ removeListener(message.arg2);
((RegistrationListener) listener).onServiceUnregistered(ns);
break;
case RESOLVE_SERVICE_FAILED:
+ removeListener(message.arg2);
((ResolveListener) listener).onResolveFailed(ns, message.arg1);
break;
case RESOLVE_SERVICE_SUCCEEDED:
+ removeListener(message.arg2);
((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj);
break;
default:
Log.d(TAG, "Ignored " + message);
break;
}
- if (listenerRemove) {
- removeListener(message.arg2);
- }
}
}
+ // if the listener is already in the map, reject it. Otherwise, add it and
+ // return its key.
+
private int putListener(Object listener, NsdServiceInfo s) {
if (listener == null) return INVALID_LISTENER_KEY;
int key;
synchronized (mMapLock) {
+ int valueIndex = mListenerMap.indexOfValue(listener);
+ if (valueIndex != -1) {
+ return BUSY_LISTENER_KEY;
+ }
do {
key = mListenerKey++;
} while (key == INVALID_LISTENER_KEY);
@@ -424,7 +426,6 @@ public final class NsdManager {
return INVALID_LISTENER_KEY;
}
-
private String getNsdServiceInfoType(NsdServiceInfo s) {
if (s == null) return "?";
return s.getServiceType();
@@ -451,14 +452,18 @@ public final class NsdManager {
* Register a service to be discovered by other services.
*
* <p> The function call immediately returns after sending a request to register service
- * to the framework. The application is notified of a success to initiate
- * discovery through the callback {@link RegistrationListener#onServiceRegistered} or a failure
+ * to the framework. The application is notified of a successful registration
+ * through the callback {@link RegistrationListener#onServiceRegistered} or a failure
* through {@link RegistrationListener#onRegistrationFailed}.
*
+ * <p> The application should call {@link #unregisterService} when the service
+ * registration is no longer required, and/or whenever the application is stopped.
+ *
* @param serviceInfo The service being registered
* @param protocolType The service discovery protocol
* @param listener The listener notifies of a successful registration and is used to
* unregister this service through a call on {@link #unregisterService}. Cannot be null.
+ * Cannot be in use for an active service registration.
*/
public void registerService(NsdServiceInfo serviceInfo, int protocolType,
RegistrationListener listener) {
@@ -475,8 +480,11 @@ public final class NsdManager {
if (protocolType != PROTOCOL_DNS_SD) {
throw new IllegalArgumentException("Unsupported protocol");
}
- mAsyncChannel.sendMessage(REGISTER_SERVICE, 0, putListener(listener, serviceInfo),
- serviceInfo);
+ int key = putListener(listener, serviceInfo);
+ if (key == BUSY_LISTENER_KEY) {
+ throw new IllegalArgumentException("listener already in use");
+ }
+ mAsyncChannel.sendMessage(REGISTER_SERVICE, 0, key, serviceInfo);
}
/**
@@ -486,7 +494,11 @@ public final class NsdManager {
*
* @param listener This should be the listener object that was passed to
* {@link #registerService}. It identifies the service that should be unregistered
- * and notifies of a successful unregistration.
+ * and notifies of a successful or unsuccessful unregistration via the listener
+ * callbacks. In API versions 20 and above, the listener object may be used for
+ * another service registration once the callback has been called. In API versions <= 19,
+ * there is no entirely reliable way to know when a listener may be re-used, and a new
+ * listener should be created for each service registration request.
*/
public void unregisterService(RegistrationListener listener) {
int id = getListenerKey(listener);
@@ -516,12 +528,16 @@ public final class NsdManager {
* <p> Upon failure to start, service discovery is not active and application does
* not need to invoke {@link #stopServiceDiscovery}
*
+ * <p> The application should call {@link #stopServiceDiscovery} when discovery of this
+ * service type is no longer required, and/or whenever the application is paused or
+ * stopped.
+ *
* @param serviceType The service type being discovered. Examples include "_http._tcp" for
* http services or "_ipp._tcp" for printers
* @param protocolType The service discovery protocol
* @param listener The listener notifies of a successful discovery and is used
* to stop discovery on this serviceType through a call on {@link #stopServiceDiscovery}.
- * Cannot be null.
+ * Cannot be null. Cannot be in use for an active service discovery.
*/
public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
if (listener == null) {
@@ -537,11 +553,17 @@ public final class NsdManager {
NsdServiceInfo s = new NsdServiceInfo();
s.setServiceType(serviceType);
- mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, putListener(listener, s), s);
+
+ int key = putListener(listener, s);
+ if (key == BUSY_LISTENER_KEY) {
+ throw new IllegalArgumentException("listener already in use");
+ }
+
+ mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, key, s);
}
/**
- * Stop service discovery initiated with {@link #discoverServices}. An active service
+ * Stop service discovery initiated with {@link #discoverServices}. An active service
* discovery is notified to the application with {@link DiscoveryListener#onDiscoveryStarted}
* and it stays active until the application invokes a stop service discovery. A successful
* stop is notified to with a call to {@link DiscoveryListener#onDiscoveryStopped}.
@@ -550,7 +572,11 @@ public final class NsdManager {
* {@link DiscoveryListener#onStopDiscoveryFailed}.
*
* @param listener This should be the listener object that was passed to {@link #discoverServices}.
- * It identifies the discovery that should be stopped and notifies of a successful stop.
+ * It identifies the discovery that should be stopped and notifies of a successful or
+ * unsuccessful stop. In API versions 20 and above, the listener object may be used for
+ * another service discovery once the callback has been called. In API versions <= 19,
+ * there is no entirely reliable way to know when a listener may be re-used, and a new
+ * listener should be created for each service discovery request.
*/
public void stopServiceDiscovery(DiscoveryListener listener) {
int id = getListenerKey(listener);
@@ -570,6 +596,7 @@ public final class NsdManager {
*
* @param serviceInfo service to be resolved
* @param listener to receive callback upon success or failure. Cannot be null.
+ * Cannot be in use for an active service resolution.
*/
public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) {
if (TextUtils.isEmpty(serviceInfo.getServiceName()) ||
@@ -579,8 +606,13 @@ public final class NsdManager {
if (listener == null) {
throw new IllegalArgumentException("listener cannot be null");
}
- mAsyncChannel.sendMessage(RESOLVE_SERVICE, 0, putListener(listener, serviceInfo),
- serviceInfo);
+
+ int key = putListener(listener, serviceInfo);
+
+ if (key == BUSY_LISTENER_KEY) {
+ throw new IllegalArgumentException("listener already in use");
+ }
+ mAsyncChannel.sendMessage(RESOLVE_SERVICE, 0, key, serviceInfo);
}
/** Internal use only @hide */
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/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 2e38960..f339e52 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -16,9 +16,15 @@
package android.os;
+import android.os.BatteryProperty;
+import android.os.IBatteryPropertiesRegistrar;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
/**
* The BatteryManager class contains strings and constants used for values
- * in the {@link android.content.Intent#ACTION_BATTERY_CHANGED} Intent.
+ * in the {@link android.content.Intent#ACTION_BATTERY_CHANGED} Intent, and
+ * provides a method for querying battery and charging properties.
*/
public class BatteryManager {
/**
@@ -121,4 +127,30 @@ public class BatteryManager {
/** @hide */
public static final int BATTERY_PLUGGED_ANY =
BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS;
+
+ private IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
+
+ /**
+ * Return the requested battery property.
+ *
+ * @param id identifier from {@link BatteryProperty} of the requested property
+ * @return a {@link BatteryProperty} object that returns the property value, or null on error
+ */
+ public BatteryProperty getProperty(int id) throws RemoteException {
+ if (mBatteryPropertiesRegistrar == null) {
+ IBinder b = ServiceManager.getService("batteryproperties");
+ mBatteryPropertiesRegistrar =
+ IBatteryPropertiesRegistrar.Stub.asInterface(b);
+
+ if (mBatteryPropertiesRegistrar == null)
+ return null;
+ }
+
+ BatteryProperty prop = new BatteryProperty(Integer.MIN_VALUE);
+ if ((mBatteryPropertiesRegistrar.getProperty(id, prop) == 0) &&
+ (prop.getInt() != Integer.MIN_VALUE))
+ return prop;
+ else
+ return null;
+ }
}
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..ec73952
--- /dev/null
+++ b/core/java/android/os/BatteryProperty.java
@@ -0,0 +1,116 @@
+/* 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;
+
+/**
+ * Battery properties that may be queried using
+ * {@link BatteryManager#getProperty
+ * BatteryManager.getProperty()}
+ */
+public class BatteryProperty implements Parcelable {
+ /*
+ * Battery property identifiers. These must match the values in
+ * frameworks/native/include/batteryservice/BatteryService.h
+ */
+ /** Battery capacity in microampere-hours, as an integer. */
+ public static final int CHARGE_COUNTER = 1;
+
+ /**
+ * Instantaneous battery current in microamperes, as an integer. Positive
+ * values indicate net current entering the battery from a charge source,
+ * negative values indicate net current discharging from the battery.
+ */
+ public static final int CURRENT_NOW = 2;
+
+ /**
+ * Average battery current in microamperes, as an integer. Positive
+ * values indicate net current entering the battery from a charge source,
+ * negative values indicate net current discharging from the battery.
+ * The time period over which the average is computed may depend on the
+ * fuel gauge hardware and its configuration.
+ */
+ public static final int CURRENT_AVERAGE = 3;
+
+ /**
+ * Remaining battery capacity as an integer percentage of total capacity
+ * (with no fractional part).
+ */
+ public static final int CAPACITY = 4;
+
+ private int mValueInt;
+
+ /**
+ * @hide
+ */
+ public BatteryProperty(int value) {
+ mValueInt = value;
+ }
+
+ /**
+ * @hide
+ */
+ public BatteryProperty() {
+ mValueInt = Integer.MIN_VALUE;
+ }
+
+ /**
+ * Return the value of a property of integer type previously queried
+ * via {@link BatteryManager#getProperty
+ * BatteryManager.getProperty()}. If the platform does
+ * not provide the property queried, this value will be
+ * Integer.MIN_VALUE.
+ *
+ * @return The queried property value, or Integer.MIN_VALUE if not supported.
+ */
+ public int getInt() {
+ return mValueInt;
+ }
+
+ /*
+ * 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) {
+ mValueInt = p.readInt();
+ }
+
+ public void writeToParcel(Parcel p, int flags) {
+ p.writeInt(mValueInt);
+ }
+
+ 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..426f21e 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
@@ -116,19 +118,14 @@ public abstract class BatteryStats implements Parcelable {
public static final int STATS_SINCE_CHARGED = 0;
/**
- * Include only the last run in the stats.
- */
- public static final int STATS_LAST = 1;
-
- /**
* Include only the current run in the stats.
*/
- public static final int STATS_CURRENT = 2;
+ public static final int STATS_CURRENT = 1;
/**
* Include only the run since the last time the device was unplugged in the stats.
*/
- public static final int STATS_SINCE_UNPLUGGED = 3;
+ public static final int STATS_SINCE_UNPLUGGED = 2;
// NOTE: Update this list if you add/change any stats above.
// These characters are supposed to represent "total", "last", "current",
@@ -153,6 +150,7 @@ public abstract class BatteryStats implements Parcelable {
private static final String FOREGROUND_DATA = "fg";
private static final String WAKELOCK_DATA = "wl";
private static final String KERNEL_WAKELOCK_DATA = "kwl";
+ private static final String WAKEUP_REASON_DATA = "wr";
private static final String NETWORK_DATA = "nt";
private static final String USER_ACTIVITY_DATA = "ua";
private static final String BATTERY_DATA = "bt";
@@ -160,6 +158,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 +167,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);
@@ -191,6 +197,25 @@ public abstract class BatteryStats implements Parcelable {
}
/**
+ * State for keeping track of long counting information.
+ */
+ public static abstract class LongCounter {
+
+ /**
+ * Returns the count associated with this Counter for the
+ * selected type of statistics.
+ *
+ * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT
+ */
+ public abstract long getCountLocked(int which);
+
+ /**
+ * Temporary for debugging.
+ */
+ public abstract void logState(Printer pw, String prefix);
+ }
+
+ /**
* State for keeping track of timing information.
*/
public static abstract class Timer {
@@ -207,11 +232,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 +294,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 +338,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 +358,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 +378,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 +473,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,50 +552,101 @@ 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_CPU_RUNNING_FLAG = 1<<31;
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_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_SENSOR_ON_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_PHONE_SCANNING_FLAG = 1<<21;
public static final int STATE_SCREEN_ON_FLAG = 1<<20;
public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19;
public static final int STATE_PHONE_IN_CALL_FLAG = 1<<18;
public static final int STATE_WIFI_ON_FLAG = 1<<17;
public static final int STATE_BLUETOOTH_ON_FLAG = 1<<16;
-
+
public static final int MOST_INTERESTING_STATES =
STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG
| STATE_GPS_ON_FLAG | STATE_PHONE_IN_CALL_FLAG;
public int states;
+ public static final int STATE2_VIDEO_ON_FLAG = 1<<0;
+ public int states2;
+
+ // 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 +660,70 @@ 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);
+ dest.writeInt(states2);
+ 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;
+ states2 = src.readInt();
+ 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 +736,26 @@ public abstract class BatteryStats implements Parcelable {
batteryTemperature = 0;
batteryVoltage = 0;
states = 0;
+ states2 = 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 +763,70 @@ public abstract class BatteryStats implements Parcelable {
batteryTemperature = o.batteryTemperature;
batteryVoltage = o.batteryVoltage;
states = o.states;
+ states2 = o.states2;
+ 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
+ && states2 == o.states2
+ && 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 +834,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 +898,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 +917,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 +930,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 +940,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 +965,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 +974,46 @@ 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 is the difference between the mobile radio
+ * time we saw based on the elapsed timestamp when going down vs. the given time stamp
+ * from the radio.
+ *
+ * {@hide}
+ */
+ public abstract long getMobileRadioActiveAdjustedTime(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 +1047,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
@@ -881,36 +1056,53 @@ public abstract class BatteryStats implements Parcelable {
* {@hide}
*/
public abstract int getPhoneDataConnectionCount(int dataType, int which);
-
+
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_CPU_RUNNING_FLAG, "running", "r"),
+ 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_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 BitDescription[] HISTORY_STATE2_DESCRIPTIONS
+ = new BitDescription[] {
+ new BitDescription(HistoryItem.STATE2_VIDEO_ON_FLAG, "video", "v"),
+ };
+
+ 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 +1111,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 +1119,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 +1158,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_BLUETOOTH_STATES = BLUETOOTH_STATE_HIGH +1;
+
+ /**
+ * 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);
- public static final int NUM_NETWORK_ACTIVITY_TYPES = NETWORK_WIFI_TX_BYTES + 1;
+ /**
+ * 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 abstract long getNetworkActivityCount(int type, int which);
+ 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 +1222,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.
@@ -1008,6 +1253,11 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getHighDischargeAmountSinceCharge();
/**
+ * Retrieve the discharge amount over the selected discharge period <var>which</var>.
+ */
+ public abstract int getDischargeAmount(int which);
+
+ /**
* Get the amount the battery has discharged while the screen was on,
* since the last time power was unplugged.
*/
@@ -1048,6 +1298,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.
@@ -1063,11 +1329,15 @@ public abstract class BatteryStats implements Parcelable {
*/
public abstract long computeRealtime(long curTime, int which);
+ public abstract Map<String, ? extends LongCounter> getWakeupReasonStats();
+
public abstract Map<String, ? extends Timer> getKernelWakelockStats();
/** 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 +1366,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 +1397,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 +1414,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 +1428,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 +1462,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 +1511,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 +1539,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 +1558,97 @@ 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),
+ getMobileRadioActiveAdjustedTime(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());
}
@@ -1353,18 +1664,79 @@ public abstract class BatteryStats implements Parcelable {
}
if (reqUid < 0) {
- Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats();
+ Map<String, ? extends Timer> kernelWakelocks = getKernelWakelockStats();
if (kernelWakelocks.size() > 0) {
- for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) {
+ for (Map.Entry<String, ? extends Timer> ent : kernelWakelocks.entrySet()) {
sb.setLength(0);
- printWakeLockCheckin(sb, ent.getValue(), batteryRealtime, null, which, "");
-
- dumpLine(pw, 0 /* uid */, category, KERNEL_WAKELOCK_DATA, ent.getKey(),
+ printWakeLockCheckin(sb, ent.getValue(), rawRealtime, null, which, "");
+ dumpLine(pw, 0 /* uid */, category, KERNEL_WAKELOCK_DATA, ent.getKey(),
sb.toString());
}
}
+ Map<String, ? extends LongCounter> wakeupReasons = getWakeupReasonStats();
+ if (wakeupReasons.size() > 0) {
+ for (Map.Entry<String, ? extends LongCounter> ent : wakeupReasons.entrySet()) {
+ dumpLine(pw, 0 /* uid */, category, WAKEUP_REASON_DATA,
+ "\"" + ent.getKey() + "\"", ent.getValue().getCountLocked(which));
+ }
+ }
}
+ 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 +1744,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 +1795,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 +1821,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 +1833,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 +1843,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 +1911,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 +1947,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 +2005,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 +2026,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 +2048,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 +2099,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 +2129,112 @@ 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());
+ }
+
+ final long mobileActiveAdjustedTime = getMobileRadioActiveAdjustedTime(which);
+ if (mobileActiveAdjustedTime != 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Mobile radio active adjusted time: ");
+ formatTimeMs(sb, mobileActiveAdjustedTime / 1000);
+ sb.append("(");
+ sb.append(formatRatioLocked(mobileActiveAdjustedTime, whichBatteryRealtime));
+ sb.append(")");
+ 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,24 +2268,185 @@ public abstract class BatteryStats implements Parcelable {
pw.println();
}
- if (timers.size() > 0) {
- Collections.sort(timers, timerComparator);
- pw.print(prefix); pw.println(" All partial wake locks:");
- for (int i=0; i<timers.size(); i++) {
- TimerEntry timer = timers.get(i);
+ 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(" Wake lock ");
- UserHandle.formatUid(sb, timer.mId);
- sb.append(" ");
- sb.append(timer.mName);
- printWakeLock(sb, timer.mTimer, batteryRealtime, null, which, ": ");
- sb.append(" realtime");
+ 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;
}
- timers.clear();
+ 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:");
+ for (int i=0; i<timers.size(); i++) {
+ TimerEntry timer = timers.get(i);
+ sb.setLength(0);
+ sb.append(" Wake lock ");
+ UserHandle.formatUid(sb, timer.mId);
+ sb.append(" ");
+ sb.append(timer.mName);
+ printWakeLock(sb, timer.mTimer, rawRealtime, null, which, ": ");
+ sb.append(" realtime");
+ pw.println(sb.toString());
+ }
+ timers.clear();
+ pw.println();
+ }
+
+ Map<String, ? extends LongCounter> wakeupReasons = getWakeupReasonStats();
+ if (wakeupReasons.size() > 0) {
+ pw.print(prefix); pw.println(" All wakeup reasons:");
+ final ArrayList<TimerEntry> reasons = new ArrayList<TimerEntry>();
+ for (Map.Entry<String, ? extends LongCounter> ent : wakeupReasons.entrySet()) {
+ BatteryStats.LongCounter counter = ent.getValue();
+ reasons.add(new TimerEntry(ent.getKey(), 0, null,
+ ent.getValue().getCountLocked(which)));
+ }
+ Collections.sort(reasons, timerComparator);
+ for (int i=0; i<reasons.size(); i++) {
+ TimerEntry timer = reasons.get(i);
+ String linePrefix = ": ";
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Wakeup reason ");
+ sb.append(timer.mName);
+ sb.append(": ");
+ formatTimeMs(sb, timer.mTime);
+ sb.append("realtime");
+ pw.println(sb.toString());
+ }
+ pw.println();
+ }
+ }
+
for (int iu=0; iu<NU; iu++) {
final int uid = uidStats.keyAt(iu);
if (reqUid >= 0 && uid != reqUid && uid != Process.SYSTEM_UID) {
@@ -1840,24 +2460,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 (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) {
+
+ 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 +2547,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 +2560,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 +2574,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 +2634,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 +2658,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 +2677,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 +2799,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 +2853,103 @@ public abstract class BatteryStats implements Parcelable {
public static class HistoryPrinter {
int oldState = 0;
+ int oldState2 = 0;
+ int oldLevel = -1;
int oldStatus = -1;
int oldHealth = -1;
int oldPlug = -1;
int oldTemp = -1;
int oldVolt = -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(" ");
+ long lastTime = -1;
+
+ public void printNextItem(PrintWriter pw, HistoryItem rec, long now, boolean checkin,
+ boolean verbose) {
+ 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);
+ if (verbose) {
+ 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 +2958,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 +2988,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 +3009,252 @@ 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);
+ printBitDescriptions(pw, oldState2, rec.states2, null,
+ HISTORY_STATE2_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;
+ public static final int DUMP_VERBOSE = 1<<4;
+
/**
* 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,
+ (flags&DUMP_VERBOSE) != 0);
+ }
+ }
+ 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, (flags&DUMP_VERBOSE) != 0);
+ }
+ 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, false);
+ }
+ }
+ 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 +3282,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/Build.java b/core/java/android/os/Build.java
index fae2d52..63e15a9 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -479,6 +479,19 @@ public class Build {
* Android 4.5: KitKat for watches, snacks on the run.
*/
public static final int KITKAT_WATCH = CUR_DEVELOPMENT; // STOPSHIP: update API level
+
+ /**
+ * L!
+ *
+ * <p>Applications targeting this or a later release will get these
+ * new changes in behavior:</p>
+ * <ul>
+ * <li> {@link android.content.Context#bindService Context.bindService} now
+ * requires an explicit Intent, and will throw an exception if given an explicit
+ * Intent.</li>
+ * </ul>
+ */
+ public static final int L = CUR_DEVELOPMENT;
}
/** The type of build, like "user" or "eng". */
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..e96398a 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;
@@ -42,6 +41,7 @@ public class Environment {
private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE";
private static final String ENV_SECONDARY_STORAGE = "SECONDARY_STORAGE";
private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT";
+ private static final String ENV_OEM_ROOT = "OEM_ROOT";
/** {@hide} */
public static final String DIR_ANDROID = "Android";
@@ -56,6 +56,7 @@ public class Environment {
public static final String DIRECTORY_ANDROID = DIR_ANDROID;
private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");
+ private static final File DIR_OEM_ROOT = getDirectory(ENV_OEM_ROOT, "/oem");
private static final File DIR_MEDIA_STORAGE = getDirectory(ENV_MEDIA_STORAGE, "/data/media");
private static final String CANONCIAL_EMULATED_STORAGE_TARGET = getCanonicalPathOrNull(
@@ -66,33 +67,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 +75,6 @@ public class Environment {
public static void initForCurrentUser() {
final int userId = UserHandle.myUserId();
sCurrentUser = new UserEnvironment(userId);
-
- synchronized (sLock) {
- sPrimaryVolume = null;
- }
}
/** {@hide} */
@@ -237,13 +207,24 @@ public class Environment {
}
/**
- * Gets the Android root directory.
+ * Return root of the "system" partition holding the core Android OS.
+ * Always present and mounted read-only.
*/
public static File getRootDirectory() {
return DIR_ANDROID_ROOT;
}
/**
+ * Return root directory of the "oem" partition holding OEM customizations,
+ * if any. If present, the partition is mounted read-only.
+ *
+ * @hide
+ */
+ public static File getOemDirectory() {
+ return DIR_OEM_ROOT;
+ }
+
+ /**
* Gets the system directory available for secure storage.
* If Encrypted File system is enabled, it returns an encrypted directory (/data/secure/system).
* Otherwise, it returns the unencrypted /data/system directory.
@@ -603,28 +584,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 +613,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 +621,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 +629,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 +637,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 +652,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 +668,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 +689,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 +826,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 f65b6ba..f5ff185 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 92af1a5..6c7b08d 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -23,14 +23,17 @@ import android.os.WorkSource;
interface IPowerManager
{
- // WARNING: The first four methods must remain the first three methods because their
+ // WARNING: The first five methods must remain the first five 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);
+ oneway void powerHint(int hintId, int data);
- void updateWakeLockWorkSource(IBinder lock, in WorkSource ws);
+ void updateWakeLockWorkSource(IBinder lock, in WorkSource ws, String historyTag);
boolean isWakeLockLevelSupported(int level);
void userActivity(long time, int event, int flags);
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 3c9d0d9..1192a45 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 createProfileForUser(in String name, int flags, int userHandle);
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> getProfiles(int userHandle);
UserInfo getUserInfo(int userHandle);
boolean isRestricted();
void setGuestEnabled(boolean enable);
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 456ffb1..4d05e2d 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -20,8 +20,8 @@ package android.os;
interface IVibratorService
{
boolean hasVibrator();
- void vibrate(int uid, String packageName, long milliseconds, IBinder token);
- void vibratePattern(int uid, String packageName, in long[] pattern, int repeat, IBinder token);
+ void vibrate(int uid, String opPkg, long milliseconds, int streamHint, IBinder token);
+ void vibratePattern(int uid, String opPkg, in long[] pattern, int repeat, int streamHint, IBinder token);
void cancelVibrate(IBinder token);
}
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..7b870ac 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.
*
@@ -38,22 +36,11 @@ public class NullVibrator extends Vibrator {
return false;
}
- @Override
- public void vibrate(long milliseconds) {
- }
-
- @Override
- public void vibrate(long[] pattern, int repeat) {
- if (repeat >= pattern.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
- }
-
/**
* @hide
*/
@Override
- public void vibrate(int owningUid, String owningPackage, long milliseconds) {
+ public void vibrate(int uid, String opPkg, long milliseconds, int streamHint) {
vibrate(milliseconds);
}
@@ -61,8 +48,11 @@ public class NullVibrator extends Vibrator {
* @hide
*/
@Override
- public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) {
- vibrate(pattern, repeat);
+ public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
+ int streamHint) {
+ if (repeat >= pattern.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
}
@Override
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 646bfef..f8d7c3e 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;
@@ -678,14 +697,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() {
@@ -772,7 +792,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;
@@ -866,13 +887,29 @@ public final class PowerManager {
if (changed && mHeld) {
try {
- mService.updateWakeLockWorkSource(mToken, mWorkSource);
+ mService.updateWakeLockWorkSource(mToken, mWorkSource, mHistoryTag);
} catch (RemoteException e) {
}
}
}
}
+ /** @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/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/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 700f80d..8d9cf54 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -16,7 +16,6 @@
package android.os;
-import android.app.ActivityThread;
import android.content.Context;
import android.util.Log;
@@ -28,18 +27,16 @@ import android.util.Log;
public class SystemVibrator extends Vibrator {
private static final String TAG = "Vibrator";
- private final String mPackageName;
private final IVibratorService mService;
private final Binder mToken = new Binder();
public SystemVibrator() {
- mPackageName = ActivityThread.currentPackageName();
mService = IVibratorService.Stub.asInterface(
ServiceManager.getService("vibrator"));
}
public SystemVibrator(Context context) {
- mPackageName = context.getOpPackageName();
+ super(context);
mService = IVibratorService.Stub.asInterface(
ServiceManager.getService("vibrator"));
}
@@ -57,27 +54,17 @@ public class SystemVibrator extends Vibrator {
return false;
}
- @Override
- public void vibrate(long milliseconds) {
- vibrate(Process.myUid(), mPackageName, milliseconds);
- }
-
- @Override
- public void vibrate(long[] pattern, int repeat) {
- vibrate(Process.myUid(), mPackageName, pattern, repeat);
- }
-
/**
* @hide
*/
@Override
- public void vibrate(int owningUid, String owningPackage, long milliseconds) {
+ public void vibrate(int uid, String opPkg, long milliseconds, int streamHint) {
if (mService == null) {
Log.w(TAG, "Failed to vibrate; no vibrator service.");
return;
}
try {
- mService.vibrate(owningUid, owningPackage, milliseconds, mToken);
+ mService.vibrate(uid, opPkg, milliseconds, streamHint, mToken);
} catch (RemoteException e) {
Log.w(TAG, "Failed to vibrate.", e);
}
@@ -87,7 +74,8 @@ public class SystemVibrator extends Vibrator {
* @hide
*/
@Override
- public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) {
+ public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
+ int streamHint) {
if (mService == null) {
Log.w(TAG, "Failed to vibrate; no vibrator service.");
return;
@@ -97,7 +85,8 @@ public class SystemVibrator extends Vibrator {
// anyway
if (repeat < pattern.length) {
try {
- mService.vibratePattern(owningUid, owningPackage, pattern, repeat, mToken);
+ mService.vibratePattern(uid, opPkg, pattern, repeat, streamHint,
+ mToken);
} catch (RemoteException e) {
Log.w(TAG, "Failed to vibrate.", e);
}
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..8aef9bd 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -17,14 +17,19 @@ 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;
+import android.graphics.Canvas;
+import android.graphics.Bitmap.Config;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.util.Log;
import com.android.internal.R;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -165,11 +170,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 +416,27 @@ public class UserManager {
}
/**
+ * Creates a user with the specified name and options as a profile of another user.
+ * 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 userHandle new user will be a profile of this use.
+ *
+ * @return the UserInfo object for the created user, or null if the user could not be created.
+ * @hide
+ */
+ public UserInfo createProfileForUser(String name, int flags, int userHandle) {
+ try {
+ return mService.createProfileForUser(name, flags, userHandle);
+ } 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,9 +460,97 @@ public class UserManager {
}
/**
- * Returns information for all users on this device.
+ * Returns list of the profiles of userHandle including
+ * userHandle itself.
+ *
* Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
- * @param excludeDying specify if the list should exclude users being removed.
+ * @param userHandle profiles of this user will be returned.
+ * @return the list of profiles.
+ * @hide
+ */
+ public List<UserInfo> getProfiles(int userHandle) {
+ try {
+ return mService.getProfiles(userHandle);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get user list", re);
+ return null;
+ }
+ }
+
+ /**
+ * Returns a list of UserHandles for profiles associated with this user, including this user.
+ *
+ * @return A non-empty list of UserHandles associated with the calling user.
+ */
+ public List<UserHandle> getUserProfiles() {
+ ArrayList<UserHandle> profiles = new ArrayList<UserHandle>();
+ List<UserInfo> users = getProfiles(UserHandle.myUserId());
+ for (UserInfo info : users) {
+ UserHandle userHandle = new UserHandle(info.id);
+ profiles.add(userHandle);
+ }
+ return profiles;
+ }
+
+ /**
+ * If the target user is a managed profile of the calling user or the caller
+ * is itself a managed profile, then this returns a badged copy of the given
+ * icon to be able to distinguish it from the original icon.
+ * <P>
+ * If the original drawable is not a BitmapDrawable, then the original
+ * drawable is returned.
+ * </P>
+ *
+ * @param icon The icon to badge.
+ * @param user The target user.
+ * @return A drawable that combines the original icon and a badge as
+ * determined by the system.
+ */
+ public Drawable getBadgedDrawableForUser(Drawable icon, UserHandle user) {
+ int badgeResId = getBadgeResIdForUser(user.getIdentifier());
+ if (badgeResId == 0) {
+ return icon;
+ } else {
+ Drawable badgeIcon = mContext.getPackageManager()
+ .getDrawable("system", badgeResId, null);
+ return getMergedDrawable(icon, badgeIcon);
+ }
+ }
+
+ private int getBadgeResIdForUser(int userHandle) {
+ // Return the framework-provided badge.
+ List<UserInfo> userProfiles = getProfiles(UserHandle.myUserId());
+ for (UserInfo user : userProfiles) {
+ if (user.id == userHandle
+ && user.isManagedProfile()) {
+ return com.android.internal.R.drawable.ic_corp_badge;
+ }
+ }
+ return 0;
+ }
+
+ private Drawable getMergedDrawable(Drawable icon, Drawable badge) {
+ final int width = icon.getIntrinsicWidth();
+ final int height = icon.getIntrinsicHeight();
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ icon.setBounds(0, 0, width, height);
+ icon.draw(canvas);
+ badge.setBounds(0, 0, width, height);
+ badge.draw(canvas);
+ BitmapDrawable merged = new BitmapDrawable(bitmap);
+ if (icon instanceof BitmapDrawable) {
+ merged.setTargetDensity(((BitmapDrawable) icon).getBitmap().getDensity());
+ }
+ return merged;
+ }
+
+ /**
+ * 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.
* @return the list of users that were created.
* @hide
*/
@@ -565,6 +681,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/Vibrator.java b/core/java/android/os/Vibrator.java
index 5d55143..c1d4d4c 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -16,7 +16,9 @@
package android.os;
+import android.app.ActivityThread;
import android.content.Context;
+import android.media.AudioManager;
/**
* Class that operates the vibrator on the device.
@@ -28,10 +30,21 @@ import android.content.Context;
* {@link Context#getSystemService} with {@link Context#VIBRATOR_SERVICE} as the argument.
*/
public abstract class Vibrator {
+
+ private final String mPackageName;
+
/**
* @hide to prevent subclassing from outside of the framework
*/
public Vibrator() {
+ mPackageName = ActivityThread.currentPackageName();
+ }
+
+ /**
+ * @hide to prevent subclassing from outside of the framework
+ */
+ protected Vibrator(Context context) {
+ mPackageName = context.getOpPackageName();
}
/**
@@ -40,7 +53,7 @@ public abstract class Vibrator {
* @return True if the hardware has a vibrator, else false.
*/
public abstract boolean hasVibrator();
-
+
/**
* Vibrate constantly for the specified period of time.
* <p>This method requires the caller to hold the permission
@@ -48,7 +61,23 @@ public abstract class Vibrator {
*
* @param milliseconds The number of milliseconds to vibrate.
*/
- public abstract void vibrate(long milliseconds);
+ public void vibrate(long milliseconds) {
+ vibrate(milliseconds, AudioManager.USE_DEFAULT_STREAM_TYPE);
+ }
+
+ /**
+ * Vibrate constantly for the specified period of time.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#VIBRATE}.
+ *
+ * @param milliseconds The number of milliseconds to vibrate.
+ * @param streamHint An {@link AudioManager} stream type corresponding to the vibration type.
+ * For example, specify {@link AudioManager#STREAM_ALARM} for alarm vibrations or
+ * {@link AudioManager#STREAM_RING} for vibrations associated with incoming calls.
+ */
+ public void vibrate(long milliseconds, int streamHint) {
+ vibrate(Process.myUid(), mPackageName, milliseconds, streamHint);
+ }
/**
* Vibrate with a given pattern.
@@ -70,21 +99,52 @@ public abstract class Vibrator {
* @param repeat the index into pattern at which to repeat, or -1 if
* you don't want to repeat.
*/
- public abstract void vibrate(long[] pattern, int repeat);
+ public void vibrate(long[] pattern, int repeat) {
+ vibrate(pattern, repeat, AudioManager.USE_DEFAULT_STREAM_TYPE);
+ }
+
+ /**
+ * Vibrate with a given pattern.
+ *
+ * <p>
+ * Pass in an array of ints that are the durations for which to turn on or off
+ * the vibrator in milliseconds. The first value indicates the number of milliseconds
+ * to wait before turning the vibrator on. The next value indicates the number of milliseconds
+ * for which to keep the vibrator on before turning it off. Subsequent values alternate
+ * between durations in milliseconds to turn the vibrator off or to turn the vibrator on.
+ * </p><p>
+ * To cause the pattern to repeat, pass the index into the pattern array at which
+ * to start the repeat, or -1 to disable repeating.
+ * </p>
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#VIBRATE}.
+ *
+ * @param pattern an array of longs of times for which to turn the vibrator on or off.
+ * @param repeat the index into pattern at which to repeat, or -1 if
+ * you don't want to repeat.
+ * @param streamHint An {@link AudioManager} stream type corresponding to the vibration type.
+ * For example, specify {@link AudioManager#STREAM_ALARM} for alarm vibrations or
+ * {@link AudioManager#STREAM_RING} for vibrations associated with incoming calls.
+ */
+ public void vibrate(long[] pattern, int repeat, int streamHint) {
+ vibrate(Process.myUid(), mPackageName, pattern, repeat, streamHint);
+ }
/**
* @hide
- * Like {@link #vibrate(long)}, but allowing the caller to specify that
+ * Like {@link #vibrate(long, int)}, but allowing the caller to specify that
* the vibration is owned by someone else.
*/
- public abstract void vibrate(int owningUid, String owningPackage, long milliseconds);
+ public abstract void vibrate(int uid, String opPkg,
+ long milliseconds, int streamHint);
/**
* @hide
- * Like {@link #vibrate(long[], int)}, but allowing the caller to specify that
+ * Like {@link #vibrate(long[], int, int)}, but allowing the caller to specify that
* the vibration is owned by someone else.
*/
- public abstract void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat);
+ public abstract void vibrate(int uid, String opPkg,
+ long[] pattern, int repeat, int streamHint);
/**
* Turn the vibrator off.
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 51ba2f6..939cda9 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -625,12 +625,13 @@ public interface IMountService extends IInterface {
return _result;
}
- public int encryptStorage(String password) throws RemoteException {
+ public int encryptStorage(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_encryptStorage, _data, _reply, 0);
_reply.readException();
@@ -642,12 +643,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 +679,83 @@ 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 String getPassword() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ String _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_getPassword, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readString();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ public void clearPassword() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_clearPassword, _data, _reply, IBinder.FLAG_ONEWAY);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ public void setField(String field, String data) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(field);
+ _data.writeString(data);
+ mRemote.transact(Stub.TRANSACTION_setField, _data, _reply, IBinder.FLAG_ONEWAY);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ public String getField(String field) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ String _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(field);
+ mRemote.transact(Stub.TRANSACTION_getField, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readString();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
public StorageVolume[] getVolumeList() throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
@@ -829,6 +908,16 @@ public interface IMountService extends IInterface {
static final int TRANSACTION_mkdirs = IBinder.FIRST_CALL_TRANSACTION + 34;
+ static final int TRANSACTION_getPasswordType = IBinder.FIRST_CALL_TRANSACTION + 35;
+
+ static final int TRANSACTION_getPassword = IBinder.FIRST_CALL_TRANSACTION + 36;
+
+ static final int TRANSACTION_clearPassword = IBinder.FIRST_CALL_TRANSACTION + 37;
+
+ static final int TRANSACTION_setField = IBinder.FIRST_CALL_TRANSACTION + 38;
+
+ static final int TRANSACTION_getField = IBinder.FIRST_CALL_TRANSACTION + 39;
+
/**
* Cast an IBinder object into an IMountService interface, generating a
* proxy if needed.
@@ -1122,16 +1211,18 @@ public interface IMountService extends IInterface {
}
case TRANSACTION_encryptStorage: {
data.enforceInterface(DESCRIPTOR);
+ int type = data.readInt();
String password = data.readString();
- int result = encryptStorage(password);
+ int result = encryptStorage(type, password);
reply.writeNoException();
reply.writeInt(result);
return true;
}
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 +1272,42 @@ 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;
+ }
+ case TRANSACTION_getPassword: {
+ data.enforceInterface(DESCRIPTOR);
+ String result = getPassword();
+ reply.writeNoException();
+ reply.writeString(result);
+ return true;
+ }
+ case TRANSACTION_clearPassword: {
+ data.enforceInterface(DESCRIPTOR);
+ clearPassword();
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_setField: {
+ data.enforceInterface(DESCRIPTOR);
+ String field = data.readString();
+ String contents = data.readString();
+ setField(field, contents);
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_getField: {
+ data.enforceInterface(DESCRIPTOR);
+ String field = data.readString();
+ String contents = getField(field);
+ reply.writeNoException();
+ reply.writeString(contents);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
}
@@ -1370,12 +1497,13 @@ public interface IMountService extends IInterface {
/**
* Encrypts storage.
*/
- public int encryptStorage(String password) throws RemoteException;
+ public int encryptStorage(int type, String password) throws RemoteException;
/**
* 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 +1540,35 @@ 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;
+
+ /**
+ * Get password from vold
+ * @return password or empty string
+ */
+ public String getPassword() throws RemoteException;
+
+ /**
+ * Securely clear password from vold
+ */
+ public void clearPassword() throws RemoteException;
+
+ /**
+ * Set a field in the crypto header.
+ * @param field field to set
+ * @param contents contents to set in field
+ */
+ public void setField(String field, String contents) throws RemoteException;
+
+ /**
+ * Gets a field from the crypto header.
+ * @param field field to get
+ * @return contents of field
+ */
+ public String getField(String field) 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..56d5617 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
@@ -506,8 +533,7 @@ public class Preference implements Comparable<Preference> {
* @see #onCreateView(ViewGroup)
*/
protected void onBindView(View view) {
- final TextView titleView = (TextView) view.findViewById(
- com.android.internal.R.id.title);
+ final TextView titleView = (TextView) view.findViewById(com.android.internal.R.id.title);
if (titleView != null) {
final CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
@@ -530,11 +556,11 @@ public class Preference implements Comparable<Preference> {
}
}
- ImageView imageView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
+ final ImageView imageView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
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);
@@ -543,6 +569,11 @@ public class Preference implements Comparable<Preference> {
imageView.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
}
+ final View imageFrame = view.findViewById(com.android.internal.R.id.icon_frame);
+ if (imageFrame != null) {
+ imageFrame.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
+ }
+
if (mShouldDisableView) {
setEnabledStateOnViews(view, isEnabled());
}
@@ -667,7 +698,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/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java
index 23d0a19..c2e1f51 100644
--- a/core/java/android/preference/PreferenceGroupAdapter.java
+++ b/core/java/android/preference/PreferenceGroupAdapter.java
@@ -45,8 +45,11 @@ import android.widget.ListView;
* adapter, use {@link PreferenceCategoryAdapter} instead.
*
* @see PreferenceCategoryAdapter
+ *
+ * @hide
*/
-class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeInternalListener {
+public class PreferenceGroupAdapter extends BaseAdapter
+ implements OnPreferenceChangeInternalListener {
private static final String TAG = "PreferenceGroupAdapter";
@@ -88,6 +91,8 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
}
};
+ private int mActivatedPosition = -1;
+
private static class PreferenceLayout implements Comparable<PreferenceLayout> {
private int resId;
private int widgetResId;
@@ -207,6 +212,10 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
return this.getItem(position).getId();
}
+ public void setActivated(int position) {
+ mActivatedPosition = position;
+ }
+
public View getView(int position, View convertView, ViewGroup parent) {
final Preference preference = this.getItem(position);
// Build a PreferenceLayout to compare with known ones that are cacheable.
@@ -217,8 +226,9 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) {
convertView = null;
}
-
- return preference.getView(convertView, parent);
+ View result = preference.getView(convertView, parent);
+ result.setActivated(position == mActivatedPosition);
+ return result;
}
@Override
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/print/PrintManager.java b/core/java/android/print/PrintManager.java
index d1bb8fd..e4f73cb 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -359,6 +359,17 @@ public final class PrintManager {
* selected the hinted options in the print dialog, given the current printer
* supports them.
* </p>
+ * <p>
+ * <strong>Note:</strong> Calling this method will bring the print dialog and
+ * the system will connect to the provided {@link PrintDocumentAdapter}. If a
+ * configuration change occurs that you application does not handle, for example
+ * a rotation change, the system will drop the connection to the adapter as the
+ * activity has to be recreated and the old adapter may be invalid in this context,
+ * hence a new adapter instance is required. As a consequence, if your activity
+ * does not handle configuration changes (default behavior), you have to save the
+ * state that you were printing and call this method again when your activity
+ * is recreated.
+ * </p>
*
* @param printJobName A name for the new print job which is shown to the user.
* @param documentAdapter An adapter that emits the document to print.
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..ae24968 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -169,6 +169,18 @@ 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 playlist.
+ */
+ public static final String EXTRA_MEDIA_PLAYLIST = "android.intent.extra.playlist";
+ /**
+ * 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 +1401,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 +1876,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/SearchIndexableData.java b/core/java/android/provider/SearchIndexableData.java
new file mode 100644
index 0000000..60bcc40
--- /dev/null
+++ b/core/java/android/provider/SearchIndexableData.java
@@ -0,0 +1,171 @@
+/*
+ * 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.provider;
+
+import android.content.Context;
+
+import java.util.Locale;
+
+/**
+ * The Indexable data for Search.
+ *
+ * This abstract class defines the common parts for all search indexable data.
+ *
+ * @hide
+ */
+public abstract class SearchIndexableData {
+
+ /**
+ * The context for the data. Will usually allow retrieving some resources.
+ *
+ * @see Context
+ */
+ public Context context;
+
+ /**
+ * The locale for the data
+ */
+ public Locale locale;
+
+ /**
+ * Tells if the data will be included into the search results. This is application specific.
+ */
+ public boolean enabled;
+
+ /**
+ * The rank for the data. This is application specific.
+ */
+ public int rank;
+
+ /**
+ * The key for the data. This is application specific. Should be unique per data as the data
+ * should be able to be retrieved by the key.
+ */
+ public String key;
+
+ /**
+ * The class name associated with the data. Generally this is a Fragment class name for
+ * referring where the data is coming from and for launching the associated Fragment for
+ * displaying the data. This is used only when the data is provided "locally".
+ *
+ * If the data is provided "externally", the relevant information come from the
+ * {@link SearchIndexableData#intentAction} and {@link SearchIndexableData#intentTargetPackage}
+ * and {@link SearchIndexableData#intentTargetClass}.
+ *
+ * @see SearchIndexableData#intentAction
+ * @see SearchIndexableData#intentTargetPackage
+ * @see SearchIndexableData#intentTargetClass
+ */
+ public String className;
+
+ /**
+ * The package name for retrieving the icon associated with the data.
+ *
+ * @see SearchIndexableData#iconResId
+ */
+ public String packageName;
+
+ /**
+ * The icon resource ID associated with the data.
+ *
+ * @see SearchIndexableData#packageName
+ */
+ public int iconResId;
+
+ /**
+ * The Intent action associated with the data. This is used when the
+ * {@link SearchIndexableData#className} is not relevant.
+ *
+ * @see SearchIndexableData#intentTargetPackage
+ * @see SearchIndexableData#intentTargetClass
+ */
+ public String intentAction;
+
+ /**
+ * The Intent target package associated with the data.
+ *
+ * @see SearchIndexableData#intentAction
+ * @see SearchIndexableData#intentTargetClass
+ */
+ public String intentTargetPackage;
+
+ /**
+ * The Intent target class associated with the data.
+ *
+ * @see SearchIndexableData#intentAction
+ * @see SearchIndexableData#intentTargetPackage
+ */
+ public String intentTargetClass;
+
+ /**
+ * Default constructor.
+ */
+ public SearchIndexableData() {
+ locale = Locale.getDefault();
+ enabled = true;
+ }
+
+ /**
+ * Constructor with a {@link Context}.
+ *
+ * @param ctx the Context
+ */
+ public SearchIndexableData(Context ctx) {
+ this();
+ context = ctx;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("SearchIndexableData[context: ");
+ sb.append(context);
+ sb.append(", ");
+ sb.append("locale: ");
+ sb.append(locale);
+ sb.append(", ");
+ sb.append("enabled: ");
+ sb.append(enabled);
+ sb.append(", ");
+ sb.append("rank: ");
+ sb.append(rank);
+ sb.append(", ");
+ sb.append("key: ");
+ sb.append(key);
+ sb.append(", ");
+ sb.append("className: ");
+ sb.append(className);
+ sb.append(", ");
+ sb.append("packageName: ");
+ sb.append(packageName);
+ sb.append(", ");
+ sb.append("iconResId: ");
+ sb.append(iconResId);
+ sb.append(", ");
+ sb.append("intentAction: ");
+ sb.append(intentAction);
+ sb.append(", ");
+ sb.append("intentTargetPackage: ");
+ sb.append(intentTargetPackage);
+ sb.append(", ");
+ sb.append("intentTargetClass: ");
+ sb.append(intentTargetClass);
+ sb.append("]");
+
+ return sb.toString();
+ }
+}
diff --git a/core/java/android/provider/SearchIndexableResource.java b/core/java/android/provider/SearchIndexableResource.java
new file mode 100644
index 0000000..c807df2
--- /dev/null
+++ b/core/java/android/provider/SearchIndexableResource.java
@@ -0,0 +1,79 @@
+/*
+ * 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.provider;
+
+import android.content.Context;
+
+/**
+ * Search Indexable Resource.
+ *
+ * This class wraps a set of reference information representing data that can be indexed from a
+ * resource which would typically be a {@link android.preference.PreferenceScreen}.
+ *
+ * xmlResId: the resource ID of a {@link android.preference.PreferenceScreen} XML file.
+ *
+ * @see SearchIndexableData
+ * @see android.preference.PreferenceScreen
+ *
+ * @hide
+ */
+public class SearchIndexableResource extends SearchIndexableData {
+
+ /**
+ * Resource ID of the associated {@link android.preference.PreferenceScreen} XML file.
+ */
+ public int xmlResId;
+
+ /**
+ * Constructor.
+ *
+ * @param rank the rank of the data.
+ * @param xmlResId the resource ID of a {@link android.preference.PreferenceScreen} XML file.
+ * @param className the class name associated with the data (generally a
+ * {@link android.app.Fragment}).
+ * @param iconResId the resource ID associated with the data.
+ */
+ public SearchIndexableResource(int rank, int xmlResId, String className, int iconResId) {
+ super();
+ this.rank = rank;
+ this.xmlResId = xmlResId;
+ this.className = className;
+ this.iconResId = iconResId;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param context the Context associated with the data.
+ */
+ public SearchIndexableResource(Context context) {
+ super(context);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("SearchIndexableResource[");
+ sb.append(super.toString());
+ sb.append(", ");
+ sb.append("xmlResId: ");
+ sb.append(xmlResId);
+ sb.append("]");
+
+ return sb.toString();
+ }
+} \ No newline at end of file
diff --git a/core/java/android/provider/SearchIndexablesContract.java b/core/java/android/provider/SearchIndexablesContract.java
new file mode 100644
index 0000000..a8b4cfb
--- /dev/null
+++ b/core/java/android/provider/SearchIndexablesContract.java
@@ -0,0 +1,265 @@
+/*
+ * 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.provider;
+
+import android.content.ContentResolver;
+
+/**
+ * Describe the contract for an Indexable data.
+ *
+ * @hide
+ */
+public class SearchIndexablesContract {
+
+ /**
+ * Intent action used to identify {@link SearchIndexablesProvider}
+ * instances. This is used in the {@code <intent-filter>} of a {@code <provider>}.
+ */
+ public static final String PROVIDER_INTERFACE =
+ "android.content.action.SEARCH_INDEXABLES_PROVIDER";
+
+ private static final String SETTINGS = "settings";
+
+ /**
+ * Indexable reference names.
+ */
+ public static final String INDEXABLES_XML_RES = "indexables_xml_res";
+
+ /**
+ * ContentProvider path for indexable xml resources.
+ */
+ public static final String INDEXABLES_XML_RES_PATH = SETTINGS + "/" + INDEXABLES_XML_RES;
+
+ /**
+ * Indexable raw data names.
+ */
+ public static final String INDEXABLES_RAW = "indexables_raw";
+
+ /**
+ * ContentProvider path for indexable raw data.
+ */
+ public static final String INDEXABLES_RAW_PATH = SETTINGS + "/" + INDEXABLES_RAW;
+
+ /**
+ * Non indexable data keys.
+ */
+ public static final String NON_INDEXABLES_KEYS = "non_indexables_key";
+
+ /**
+ * ContentProvider path for non indexable data keys.
+ */
+ public static final String NON_INDEXABLES_KEYS_PATH = SETTINGS + "/" + NON_INDEXABLES_KEYS;
+
+ /**
+ * Indexable xml resources colums.
+ */
+ public static final String[] INDEXABLES_XML_RES_COLUMNS = new String[] {
+ XmlResource.COLUMN_RANK, // 0
+ XmlResource.COLUMN_XML_RESID, // 1
+ XmlResource.COLUMN_CLASS_NAME, // 2
+ XmlResource.COLUMN_ICON_RESID, // 3
+ XmlResource.COLUMN_INTENT_ACTION, // 4
+ XmlResource.COLUMN_INTENT_TARGET_PACKAGE, // 5
+ XmlResource.COLUMN_INTENT_TARGET_CLASS // 6
+ };
+
+ /**
+ * Indexable xml resources colums indices.
+ */
+ public static final int COLUMN_INDEX_XML_RES_RANK = 0;
+ public static final int COLUMN_INDEX_XML_RES_RESID = 1;
+ public static final int COLUMN_INDEX_XML_RES_CLASS_NAME = 2;
+ public static final int COLUMN_INDEX_XML_RES_ICON_RESID = 3;
+ public static final int COLUMN_INDEX_XML_RES_INTENT_ACTION = 4;
+ public static final int COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE = 5;
+ public static final int COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS = 6;
+
+ /**
+ * Indexable raw data colums.
+ */
+ public static final String[] INDEXABLES_RAW_COLUMNS = new String[] {
+ RawData.COLUMN_RANK, // 0
+ RawData.COLUMN_TITLE, // 1
+ RawData.COLUMN_SUMMARY_ON, // 2
+ RawData.COLUMN_SUMMARY_OFF, // 3
+ RawData.COLUMN_ENTRIES, // 4
+ RawData.COLUMN_KEYWORDS, // 5
+ RawData.COLUMN_SCREEN_TITLE, // 6
+ RawData.COLUMN_CLASS_NAME, // 7
+ RawData.COLUMN_ICON_RESID, // 8
+ RawData.COLUMN_INTENT_ACTION, // 9
+ RawData.COLUMN_INTENT_TARGET_PACKAGE, // 10
+ RawData.COLUMN_INTENT_TARGET_CLASS, // 11
+ RawData.COLUMN_KEY, // 12
+ };
+
+ /**
+ * Indexable raw data colums indices.
+ */
+ public static final int COLUMN_INDEX_RAW_RANK = 0;
+ public static final int COLUMN_INDEX_RAW_TITLE = 1;
+ public static final int COLUMN_INDEX_RAW_SUMMARY_ON = 2;
+ public static final int COLUMN_INDEX_RAW_SUMMARY_OFF = 3;
+ public static final int COLUMN_INDEX_RAW_ENTRIES = 4;
+ public static final int COLUMN_INDEX_RAW_KEYWORDS = 5;
+ public static final int COLUMN_INDEX_RAW_SCREEN_TITLE = 6;
+ public static final int COLUMN_INDEX_RAW_CLASS_NAME = 7;
+ public static final int COLUMN_INDEX_RAW_ICON_RESID = 8;
+ public static final int COLUMN_INDEX_RAW_INTENT_ACTION = 9;
+ public static final int COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE = 10;
+ public static final int COLUMN_INDEX_RAW_INTENT_TARGET_CLASS = 11;
+ public static final int COLUMN_INDEX_RAW_KEY = 12;
+
+ /**
+ * Indexable raw data colums.
+ */
+ public static final String[] NON_INDEXABLES_KEYS_COLUMNS = new String[] {
+ NonIndexableKey.COLUMN_KEY_VALUE // 0
+ };
+
+ /**
+ * Non indexable data keys colums indices.
+ */
+ public static final int COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE = 0;
+
+ /**
+ * Constants related to a {@link SearchIndexableResource}.
+ *
+ * This is a description of
+ */
+ public static final class XmlResource extends BaseColumns {
+ private XmlResource() {
+ }
+
+ public static final String MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE +
+ "/" + INDEXABLES_XML_RES;
+
+ /**
+ * XML resource ID for the {@link android.preference.PreferenceScreen} to load and index.
+ */
+ public static final String COLUMN_XML_RESID = "xmlResId";
+ }
+
+ /**
+ * Constants related to a {@link SearchIndexableData}.
+ *
+ * This is the raw data that is stored into an Index. This is related to
+ * {@link android.preference.Preference} and its attributes like
+ * {@link android.preference.Preference#getTitle()},
+ * {@link android.preference.Preference#getSummary()}, etc.
+ *
+ */
+ public static final class RawData extends BaseColumns {
+ private RawData() {
+ }
+
+ public static final String MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE +
+ "/" + INDEXABLES_RAW;
+
+ /**
+ * Title's raw data.
+ */
+ public static final String COLUMN_TITLE = "title";
+
+ /**
+ * Summary's raw data when the data is "ON".
+ */
+ public static final String COLUMN_SUMMARY_ON = "summaryOn";
+
+ /**
+ * Summary's raw data when the data is "OFF".
+ */
+ public static final String COLUMN_SUMMARY_OFF = "summaryOff";
+
+ /**
+ * Entries associated with the raw data (when the data can have several values).
+ */
+ public static final String COLUMN_ENTRIES = "entries";
+
+ /**
+ * Keywords' raw data.
+ */
+ public static final String COLUMN_KEYWORDS = "keywords";
+
+ /**
+ * Fragment or Activity title associated with the raw data.
+ */
+ public static final String COLUMN_SCREEN_TITLE = "screenTitle";
+
+ /**
+ * Key associated with the raw data. The key needs to be unique.
+ */
+ public static final String COLUMN_KEY = "key";
+ }
+
+ /**
+ * Constants related to a {@link SearchIndexableResource} and {@link SearchIndexableData}.
+ *
+ * This is a description of a data (thru its unique key) that cannot be indexed.
+ */
+ public static final class NonIndexableKey extends BaseColumns {
+ private NonIndexableKey() {
+ }
+
+ public static final String MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE +
+ "/" + NON_INDEXABLES_KEYS;
+
+ /**
+ * Key for the non indexable data.
+ */
+ public static final String COLUMN_KEY_VALUE = "key";
+ }
+
+ /**
+ * The base columns.
+ */
+ private static class BaseColumns {
+ private BaseColumns() {
+ }
+
+ /**
+ * Rank of the data. This is an integer used for ranking the search results. This is
+ * application specific.
+ */
+ public static final String COLUMN_RANK = "rank";
+
+ /**
+ * Class name associated with the data (usually a Fragment class name).
+ */
+ public static final String COLUMN_CLASS_NAME = "className";
+
+ /**
+ * Icon resource ID for the data.
+ */
+ public static final String COLUMN_ICON_RESID = "iconResId";
+
+ /**
+ * Intent action associated with the data.
+ */
+ public static final String COLUMN_INTENT_ACTION = "intentAction";
+
+ /**
+ * Intent target package associated with the data.
+ */
+ public static final String COLUMN_INTENT_TARGET_PACKAGE = "intentTargetPackage";
+
+ /**
+ * Intent target class associated with the data.
+ */
+ public static final String COLUMN_INTENT_TARGET_CLASS = "intentTargetClass";
+ }
+}
diff --git a/core/java/android/provider/SearchIndexablesProvider.java b/core/java/android/provider/SearchIndexablesProvider.java
new file mode 100644
index 0000000..9c8f6d0
--- /dev/null
+++ b/core/java/android/provider/SearchIndexablesProvider.java
@@ -0,0 +1,189 @@
+/*
+ * 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.provider;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Base class for a search indexable provider. Such provider offers data to be indexed either
+ * as a reference to an XML file (like a {@link android.preference.PreferenceScreen}) or either
+ * as some raw data.
+ *
+ * @see SearchIndexableResource
+ * @see SearchIndexableData
+ * @see SearchIndexablesContract
+ *
+ * To create a search indexables provider, extend this class, then implement the abstract methods,
+ * and add it to your manifest like this:
+ *
+ * <pre class="prettyprint">&lt;manifest&gt;
+ * ...
+ * &lt;application&gt;
+ * ...
+ * &lt;provider
+ * android:name="com.example.MyIndexablesProvider"
+ * android:authorities="com.example.myindexablesprovider"
+ * android:exported="true"
+ * android:grantUriPermissions="true"
+ * android:permission="android.permission.READ_SEARCH_INDEXABLES"
+ * &lt;intent-filter&gt;
+ * &lt;action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" /&gt;
+ * &lt;/intent-filter&gt;
+ * &lt;/provider&gt;
+ * ...
+ * &lt;/application&gt;
+ *&lt;/manifest&gt;</pre>
+ * <p>
+ * When defining your provider, you must protect it with
+ * {@link android.Manifest.permission#READ_SEARCH_INDEXABLES}, which is a permission only the system
+ * can obtain.
+ * </p>
+ *
+ * @hide
+ */
+public abstract class SearchIndexablesProvider extends ContentProvider {
+ private static final String TAG = "IndexablesProvider";
+
+ private String mAuthority;
+ private UriMatcher mMatcher;
+
+ private static final int MATCH_RES_CODE = 1;
+ private static final int MATCH_RAW_CODE = 2;
+ private static final int MATCH_NON_INDEXABLE_KEYS_CODE = 3;
+
+ /**
+ * Implementation is provided by the parent class.
+ */
+ @Override
+ public void attachInfo(Context context, ProviderInfo info) {
+ mAuthority = info.authority;
+
+ mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ mMatcher.addURI(mAuthority, SearchIndexablesContract.INDEXABLES_XML_RES_PATH,
+ MATCH_RES_CODE);
+ mMatcher.addURI(mAuthority, SearchIndexablesContract.INDEXABLES_RAW_PATH,
+ MATCH_RAW_CODE);
+ mMatcher.addURI(mAuthority, SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH,
+ MATCH_NON_INDEXABLE_KEYS_CODE);
+
+ // Sanity check our setup
+ if (!info.exported) {
+ throw new SecurityException("Provider must be exported");
+ }
+ if (!info.grantUriPermissions) {
+ throw new SecurityException("Provider must grantUriPermissions");
+ }
+ if (!android.Manifest.permission.READ_SEARCH_INDEXABLES.equals(info.readPermission)) {
+ throw new SecurityException("Provider must be protected by READ_SEARCH_INDEXABLES");
+ }
+
+ super.attachInfo(context, info);
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ switch (mMatcher.match(uri)) {
+ case MATCH_RES_CODE:
+ return queryXmlResources(null);
+ case MATCH_RAW_CODE:
+ return queryRawData(null);
+ case MATCH_NON_INDEXABLE_KEYS_CODE:
+ return queryNonIndexableKeys(null);
+ default:
+ throw new UnsupportedOperationException("Unknown Uri " + uri);
+ }
+ }
+
+ /**
+ * Returns all {@link android.provider.SearchIndexablesContract.XmlResource}.
+ *
+ * Those are Xml resource IDs to some {@link android.preference.PreferenceScreen}.
+ *
+ * @param projection list of {@link android.provider.SearchIndexablesContract.XmlResource}
+ * columns to put into the cursor. If {@code null} all supported columns
+ * should be included.
+ */
+ public abstract Cursor queryXmlResources(String[] projection);
+
+ /**
+ * Returns all {@link android.provider.SearchIndexablesContract.RawData}.
+ *
+ * Those are the raw indexable data.
+ *
+ * @param projection list of {@link android.provider.SearchIndexablesContract.RawData} columns
+ * to put into the cursor. If {@code null} all supported columns should be
+ * included.
+ */
+ public abstract Cursor queryRawData(String[] projection);
+
+ /**
+ * Returns all {@link android.provider.SearchIndexablesContract.NonIndexableKey}.
+ *
+ * Those are the non indexable data keys.
+ *
+ * @param projection list of {@link android.provider.SearchIndexablesContract.NonIndexableKey}
+ * columns to put into the cursor. If {@code null} all supported columns
+ * should be included.
+ */
+ public abstract Cursor queryNonIndexableKeys(String[] projection);
+
+ @Override
+ public String getType(Uri uri) {
+ switch (mMatcher.match(uri)) {
+ case MATCH_RES_CODE:
+ return SearchIndexablesContract.XmlResource.MIME_TYPE;
+ case MATCH_RAW_CODE:
+ return SearchIndexablesContract.RawData.MIME_TYPE;
+ case MATCH_NON_INDEXABLE_KEYS_CODE:
+ return SearchIndexablesContract.NonIndexableKey.MIME_TYPE;
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ }
+
+ /**
+ * Implementation is provided by the parent class. Throws by default, and cannot be overriden.
+ */
+ @Override
+ public final Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException("Insert not supported");
+ }
+
+ /**
+ * Implementation is provided by the parent class. Throws by default, and cannot be overriden.
+ */
+ @Override
+ public final int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("Delete not supported");
+ }
+
+ /**
+ * Implementation is provided by the parent class. Throws by default, and cannot be overriden.
+ */
+ @Override
+ public final int update(
+ Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("Update not supported");
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3810eb3..7062933 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
*/
@@ -5941,6 +6071,59 @@ 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. ZEN_MODE_OFF or ZEN_MODE_ON.
+ *
+ * @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_ON = 1;
+
+ /** @hide */ public static String zenModeToString(int mode) {
+ if (mode == ZEN_MODE_OFF) return "ZEN_MODE_OFF";
+ return "ZEN_MODE_ON";
+ }
+
+ /**
+ * 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/provider/TvContract.java b/core/java/android/provider/TvContract.java
new file mode 100644
index 0000000..233e0ca
--- /dev/null
+++ b/core/java/android/provider/TvContract.java
@@ -0,0 +1,449 @@
+/*
+ * 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.provider;
+
+import android.content.ContentUris;
+import android.net.Uri;
+
+/**
+ * <p>
+ * The contract between the TV provider and applications. Contains definitions for the supported
+ * URIs and columns.
+ * </p>
+ * <h3>Overview</h3>
+ * <p>
+ * TvContract defines a basic database of TV content metadata such as channel and program
+ * information. The information is stored in {@link Channels} and {@link Programs} tables.
+ * </p>
+ * <ul>
+ * <li>A row in the {@link Channels} table represents information about a TV channel. The data
+ * format can vary greatly from standard to standard or according to service provider, thus
+ * the columns here are mostly comprised of basic entities that are usually seen to users
+ * regardless of standard such as channel number and name.</li>
+ * <li>A row in the {@link Programs} table represents a set of data describing a TV program such
+ * as program title and start time.</li>
+ * </ul>
+ */
+public final class TvContract {
+ /** The authority for the TV provider. */
+ public static final String AUTHORITY = "com.android.tv";
+
+ /**
+ * Builds a URI that points to a specific channel.
+ *
+ * @param channelId The ID of the channel to point to.
+ */
+ public static final Uri buildChannelUri(long channelId) {
+ return ContentUris.withAppendedId(Channels.CONTENT_URI, channelId);
+ }
+
+ /**
+ * Builds a URI that points to a specific program.
+ *
+ * @param programId The ID of the program to point to.
+ */
+ public static final Uri buildProgramUri(long programId) {
+ return ContentUris.withAppendedId(Programs.CONTENT_URI, programId);
+ }
+
+ /**
+ * Builds a URI that points to a specific program the user watched.
+ *
+ * @param watchedProgramId The ID of the watched program to point to.
+ * @hide
+ */
+ public static final Uri buildWatchedProgramUri(long watchedProgramId) {
+ return ContentUris.withAppendedId(WatchedPrograms.CONTENT_URI, watchedProgramId);
+ }
+
+ private TvContract() {}
+
+ /**
+ * Common base for the tables of TV channels/programs.
+ */
+ public interface BaseTvColumns extends BaseColumns {
+ /**
+ * The name of the package that owns a row in each table.
+ * <p>
+ * The TV provider fills it in with the name of the package that provides the initial data
+ * of that row. If the package is later uninstalled, the rows it owns are automatically
+ * removed from the tables.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String PACKAGE_NAME = "package_name";
+ }
+
+ /** Column definitions for the TV channels table. */
+ public static final class Channels implements BaseTvColumns {
+
+ /** The content:// style URI for this table. */
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/channel");
+
+ /** The MIME type of a directory of TV channels. */
+ public static final String CONTENT_TYPE =
+ "vnd.android.cursor.dir/vnd.com.android.tv.channels";
+
+ /** The MIME type of a single TV channel. */
+ public static final String CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/vnd.com.android.tv.channels";
+
+ /** A generic channel type. */
+ public static final int TYPE_OTHER = 0x0;
+
+ /** The special channel type used for pass-through inputs such as HDMI. */
+ public static final int TYPE_PASSTHROUGH = 0x00010000;
+
+ /** The channel type for DVB-T (terrestrial). */
+ public static final int TYPE_DVB_T = 0x00020000;
+
+ /** The channel type for DVB-T2 (terrestrial). */
+ public static final int TYPE_DVB_T2 = 0x00020001;
+
+ /** The channel type for DVB-S (satellite). */
+ public static final int TYPE_DVB_S = 0x00020100;
+
+ /** The channel type for DVB-S2 (satellite). */
+ public static final int TYPE_DVB_S2 = 0x00020101;
+
+ /** The channel type for DVB-C (cable). */
+ public static final int TYPE_DVB_C = 0x00020200;
+
+ /** The channel type for DVB-C2 (cable). */
+ public static final int TYPE_DVB_C2 = 0x00020201;
+
+ /** The channel type for DVB-H (handheld). */
+ public static final int TYPE_DVB_H = 0x00020300;
+
+ /** The channel type for DVB-SH (satellite). */
+ public static final int TYPE_DVB_SH = 0x00020400;
+
+ /** The channel type for ATSC (terrestrial/cable). */
+ public static final int TYPE_ATSC = 0x00030000;
+
+ /** The channel type for ATSC 2.0. */
+ public static final int TYPE_ATSC_2_0 = 0x00030001;
+
+ /** The channel type for ATSC-M/H (mobile/handheld). */
+ public static final int TYPE_ATSC_M_H = 0x00030100;
+
+ /** The channel type for ISDB-T (terrestrial). */
+ public static final int TYPE_ISDB_T = 0x00040000;
+
+ /** The channel type for ISDB-Tb (Brazil). */
+ public static final int TYPE_ISDB_TB = 0x00040100;
+
+ /** The channel type for ISDB-S (satellite). */
+ public static final int TYPE_ISDB_S = 0x00040200;
+
+ /** The channel type for ISDB-C (cable). */
+ public static final int TYPE_ISDB_C = 0x00040300;
+
+ /** The channel type for 1seg (handheld). */
+ public static final int TYPE_1SEG = 0x00040400;
+
+ /** The channel type for DTMB (terrestrial). */
+ public static final int TYPE_DTMB = 0x00050000;
+
+ /** The channel type for CMMB (handheld). */
+ public static final int TYPE_CMMB = 0x00050100;
+
+ /** The channel type for T-DMB (terrestrial). */
+ public static final int TYPE_T_DMB = 0x00060000;
+
+ /** The channel type for S-DMB (satellite). */
+ public static final int TYPE_S_DMB = 0x00060100;
+
+ /**
+ * The name of the TV input service that provides this TV channel.
+ * <p>
+ * This is a required field.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String SERVICE_NAME = "service_name";
+
+ /**
+ * The predefined type of this TV channel.
+ * <p>
+ * This is used to indicate which broadcast standard (e.g. ATSC, DVB or ISDB) the current
+ * channel conforms to.
+ * </p><p>
+ * This is a required field.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * The transport stream ID as appeared in various broadcast standards.
+ * <p>
+ * This is not a required field but if provided, can significantly increase the accuracy of
+ * channel identification.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String TRANSPORT_STREAM_ID = "transport_stream_id";
+
+ /**
+ * The channel number that is displayed to the user.
+ * <p>
+ * The format can vary depending on broadcast standard and product specification.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String DISPLAY_NUMBER = "display_number";
+
+ /**
+ * The channel name that is displayed to the user.
+ * <p>
+ * A call sign is a good candidate to use for this purpose but any name that helps the user
+ * recognize the current channel will be enough. Can also be empty depending on broadcast
+ * standard.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String DISPLAY_NAME = "display_name";
+
+ /**
+ * The description of this TV channel.
+ * <p>
+ * Can be empty initially.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String DESCRIPTION = "description";
+
+ /**
+ * The flag indicating whether this TV channel is browsable or not.
+ * <p>
+ * A value of 1 indicates the channel is included in the channel list that applications use
+ * to browse channels, a value of 0 indicates the channel is not included in the list. If
+ * not specified, this value is set to 1 by default.
+ * </p><p>
+ * Type: INTEGER (boolean)
+ * </p>
+ */
+ public static final String BROWSABLE = "browsable";
+
+ /**
+ * Generic data used by individual TV input services.
+ * <p>
+ * Type: BLOB
+ * </p>
+ */
+ public static final String DATA = "data";
+
+
+ /**
+ * The version number of this row entry used by TV input services.
+ * <p>
+ * This is best used by sync adapters to identify the rows to update. The number can be
+ * defined by individual TV input services. One may assign the same value as
+ * {@code version_number} that appears in ETSI EN 300 468 or ATSC A/65, if the data are
+ * coming from a TV broadcast.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String VERSION_NUMBER = "version_number";
+
+ private Channels() {}
+ }
+
+ /** Column definitions for the TV programs table. */
+ public static final class Programs implements BaseTvColumns {
+
+ /** The content:// style URI for this table. */
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/program");
+
+ /** The MIME type of a directory of TV programs. */
+ public static final String CONTENT_TYPE =
+ "vnd.android.cursor.dir/vnd.com.android.tv.programs";
+
+ /** The MIME type of a single TV program. */
+ public static final String CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/vnd.com.android.tv.programs";
+
+ /**
+ * The ID of the TV channel that contains this TV program.
+ * <p>
+ * This is a part of the channel URI and matches to {@link BaseColumns#_ID}.
+ * </p><p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String CHANNEL_ID = "channel_id";
+
+ /**
+ * The title of this TV program.
+ * <p>
+ * Type: TEXT
+ * </p>
+ **/
+ public static final String TITLE = "title";
+
+ /**
+ * The start time of this TV program, in milliseconds since the epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String START_TIME_UTC_MILLIS = "start_time_utc_millis";
+
+ /**
+ * The end time of this TV program, in milliseconds since the epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String END_TIME_UTC_MILLIS = "end_time_utc_millis";
+
+ /**
+ * The description of this TV program that is displayed to the user by default.
+ * <p>
+ * The maximum length of this field is 256 characters.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String DESCRIPTION = "description";
+
+ /**
+ * The detailed, lengthy description of this TV program that is displayed only when the user
+ * wants to see more information.
+ * <p>
+ * TV input services should leave this field empty if they have no additional
+ * details beyond {@link #DESCRIPTION}.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String LONG_DESCRIPTION = "long_description";
+
+ /**
+ * Generic data used by TV input services.
+ * <p>
+ * Type: BLOB
+ * </p>
+ */
+ public static final String DATA = "data";
+
+ /**
+ * The version number of this row entry used by TV input services.
+ * <p>
+ * This is best used by sync adapters to identify the rows to update. The number can be
+ * defined by individual TV input services. One may assign the same value as
+ * {@code version_number} in ETSI EN 300 468 or ATSC A/65, if the data are coming from a TV
+ * broadcast.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String VERSION_NUMBER = "version_number";
+
+ private Programs() {}
+ }
+
+ /**
+ * Column definitions for the TV programs that the user watched. Applications do not have access
+ * to this table.
+ *
+ * @hide
+ */
+ public static final class WatchedPrograms implements BaseColumns {
+
+ /** The content:// style URI for this table. */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://" + AUTHORITY + "/watched_program");
+
+ /** The MIME type of a directory of watched programs. */
+ public static final String CONTENT_TYPE =
+ "vnd.android.cursor.dir/vnd.com.android.tv.watched_programs";
+
+ /** The MIME type of a single item in this table. */
+ public static final String CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/vnd.com.android.tv.watched_programs";
+
+ /**
+ * The UTC time that the user started watching this TV program, in milliseconds since the
+ * epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String WATCH_START_TIME_UTC_MILLIS = "watch_start_time_utc_millis";
+
+ /**
+ * The UTC time that the user stopped watching this TV program, in milliseconds since the
+ * epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String WATCH_END_TIME_UTC_MILLIS = "watch_end_time_utc_millis";
+
+ /**
+ * The channel ID that contains this TV program.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String CHANNEL_ID = "channel_id";
+
+ /**
+ * The title of this TV program.
+ * <p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String TITLE = "title";
+
+ /**
+ * The start time of this TV program, in milliseconds since the epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String START_TIME_UTC_MILLIS = "start_time_utc_millis";
+
+ /**
+ * The end time of this TV program, in milliseconds since the epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String END_TIME_UTC_MILLIS = "end_time_utc_millis";
+
+ /**
+ * The description of this TV program.
+ * <p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String DESCRIPTION = "description";
+
+ private WatchedPrograms() {}
+ }
+}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 8eaee29..3673f03 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.util.Log;
/**
@@ -121,11 +122,43 @@ public abstract class NotificationListenerService extends Service {
* {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
* @param id ID of the notification as specified by the notifying app in
* {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
+ * <p>
+ * @deprecated Use {@link #cancelNotification(String key)}
+ * instead. Beginning with {@link android.os.Build.VERSION_CODES#L} this method will no longer
+ * cancel the notification. It will continue to cancel the notification for applications
+ * whose {@code targetSdkVersion} is earlier than {@link android.os.Build.VERSION_CODES#L}.
*/
public final void cancelNotification(String pkg, String tag, int id) {
if (!isBound()) return;
try {
- getNotificationInterface().cancelNotificationFromListener(mWrapper, pkg, tag, id);
+ getNotificationInterface().cancelNotificationFromListener(
+ mWrapper, pkg, tag, id);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact notification manager", ex);
+ }
+ }
+
+ /**
+ * Inform the notification manager about dismissal of a single notification.
+ * <p>
+ * Use this if your listener has a user interface that allows the user to dismiss individual
+ * notifications, similar to the behavior of Android's status bar and notification panel.
+ * It should be called after the user dismisses a single notification using your UI;
+ * upon being informed, the notification manager will actually remove the notification
+ * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
+ * <P>
+ * <b>Note:</b> If your listener allows the user to fire a notification's
+ * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
+ * this method at that time <i>if</i> the Notification in question has the
+ * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
+ * <p>
+ * @param key Notification to dismiss from {@link StatusBarNotification#getKey()}.
+ */
+ public final void cancelNotification(String key) {
+ if (!isBound()) return;
+ try {
+ getNotificationInterface().cancelNotificationsFromListener(mWrapper,
+ new String[] {key});
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
}
@@ -192,6 +225,24 @@ public abstract class NotificationListenerService extends Service {
return null;
}
+ /**
+ * Request the list of outstanding notification keys(that is, those that are visible to the
+ * current user). You can use the notification keys for subsequent retrieval via
+ * {@link #getActiveNotifications(String[]) or dismissal via
+ * {@link #cancelNotifications(String[]).
+ *
+ * @return An array of active notification keys.
+ */
+ public String[] getActiveNotificationKeys() {
+ if (!isBound()) return null;
+ try {
+ return getNotificationInterface().getActiveNotificationKeysFromListener(mWrapper);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact notification manager", ex);
+ }
+ return null;
+ }
+
@Override
public IBinder onBind(Intent intent) {
if (mWrapper == null) {
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 96dd143d..2c0b76d 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -32,7 +32,7 @@ public class StatusBarNotification implements Parcelable {
private final String key;
private final int uid;
- private final String basePkg;
+ private final String opPkg;
private final int initialPid;
private final Notification notification;
private final UserHandle user;
@@ -41,26 +41,20 @@ public class StatusBarNotification implements Parcelable {
private final int score;
/** @hide */
- public StatusBarNotification(String pkg, int id, String tag, int uid, int initialPid, int score,
- Notification notification, UserHandle user) {
- this(pkg, null, id, tag, uid, initialPid, score, notification, user);
- }
-
- /** @hide */
- public StatusBarNotification(String pkg, String basePkg, int id, String tag, int uid,
+ public StatusBarNotification(String pkg, String opPkg, int id, String tag, int uid,
int initialPid, int score, Notification notification, UserHandle user) {
- this(pkg, basePkg, id, tag, uid, initialPid, score, notification, user,
+ this(pkg, opPkg, id, tag, uid, initialPid, score, notification, user,
System.currentTimeMillis());
}
- public StatusBarNotification(String pkg, String basePkg, int id, String tag, int uid,
+ public StatusBarNotification(String pkg, String opPkg, int id, String tag, int uid,
int initialPid, int score, Notification notification, UserHandle user,
long postTime) {
if (pkg == null) throw new NullPointerException();
if (notification == null) throw new NullPointerException();
this.pkg = pkg;
- this.basePkg = pkg;
+ this.opPkg = opPkg;
this.id = id;
this.tag = tag;
this.uid = uid;
@@ -69,14 +63,13 @@ public class StatusBarNotification implements Parcelable {
this.notification = notification;
this.user = user;
this.notification.setUser(user);
-
this.postTime = postTime;
this.key = key();
}
public StatusBarNotification(Parcel in) {
this.pkg = in.readString();
- this.basePkg = in.readString();
+ this.opPkg = in.readString();
this.id = in.readInt();
if (in.readInt() != 0) {
this.tag = in.readString();
@@ -94,12 +87,12 @@ public class StatusBarNotification implements Parcelable {
}
private String key() {
- return pkg + '|' + basePkg + '|' + id + '|' + tag + '|' + uid;
+ return pkg + '|' + opPkg + '|' + id + '|' + tag + '|' + uid;
}
public void writeToParcel(Parcel out, int flags) {
out.writeString(this.pkg);
- out.writeString(this.basePkg);
+ out.writeString(this.opPkg);
out.writeInt(this.id);
if (this.tag != null) {
out.writeInt(1);
@@ -140,14 +133,14 @@ public class StatusBarNotification implements Parcelable {
public StatusBarNotification cloneLight() {
final Notification no = new Notification();
this.notification.cloneInto(no, false); // light copy
- return new StatusBarNotification(this.pkg, this.basePkg,
+ return new StatusBarNotification(this.pkg, this.opPkg,
this.id, this.tag, this.uid, this.initialPid,
this.score, no, this.user, this.postTime);
}
@Override
public StatusBarNotification clone() {
- return new StatusBarNotification(this.pkg, this.basePkg,
+ return new StatusBarNotification(this.pkg, this.opPkg,
this.id, this.tag, this.uid, this.initialPid,
this.score, this.notification.clone(), this.user, this.postTime);
}
@@ -176,7 +169,11 @@ public class StatusBarNotification implements Parcelable {
&& ((notification.flags & Notification.FLAG_NO_CLEAR) == 0);
}
- /** Returns a userHandle for the instance of the app that posted this notification. */
+ /**
+ * Returns a userHandle for the instance of the app that posted this notification.
+ *
+ * @deprecated Use {@link #getUser()} instead.
+ */
public int getUserId() {
return this.user.getIdentifier();
}
@@ -202,9 +199,9 @@ public class StatusBarNotification implements Parcelable {
return uid;
}
- /** The notifying app's base package. @hide */
- public String getBasePkg() {
- return basePkg;
+ /** The package used for AppOps tracking. @hide */
+ public String getOpPkg() {
+ return opPkg;
}
/** @hide */
@@ -220,7 +217,6 @@ public class StatusBarNotification implements Parcelable {
/**
* The {@link android.os.UserHandle} for whom this notification is intended.
- * @hide
*/
public UserHandle getUser() {
return user;
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/trust/ITrustAgentService.aidl b/core/java/android/service/trust/ITrustAgentService.aidl
new file mode 100644
index 0000000..863a249
--- /dev/null
+++ b/core/java/android/service/trust/ITrustAgentService.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.service.trust;
+
+import android.os.Bundle;
+import android.service.trust.ITrustAgentServiceCallback;
+
+/**
+ * Communication channel from TrustManagerService to the TrustAgent.
+ * @hide
+ */
+oneway interface ITrustAgentService {
+ void onUnlockAttempt(boolean successful);
+ void setCallback(ITrustAgentServiceCallback callback);
+}
diff --git a/core/java/android/service/trust/ITrustAgentServiceCallback.aidl b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
new file mode 100644
index 0000000..c346771
--- /dev/null
+++ b/core/java/android/service/trust/ITrustAgentServiceCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.service.trust;
+
+import android.os.Bundle;
+import android.os.UserHandle;
+
+/**
+ * Communication channel from the TrustAgentService back to TrustManagerService.
+ * @hide
+ */
+oneway interface ITrustAgentServiceCallback {
+ void enableTrust(String message, long durationMs, boolean initiatedByUser);
+ void revokeTrust();
+}
diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java
new file mode 100644
index 0000000..d5ce429
--- /dev/null
+++ b/core/java/android/service/trust/TrustAgentService.java
@@ -0,0 +1,148 @@
+/**
+ * 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.service.trust;
+
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * A service that notifies the system about whether it believes the environment of the device
+ * to be trusted.
+ *
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#BIND_TRUST_AGENT_SERVICE} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
+ * <pre>
+ * &lt;service android:name=".TrustAgent"
+ * android:label="&#64;string/service_name"
+ * android:permission="android.permission.BIND_TRUST_AGENT_SERVICE">
+ * &lt;intent-filter>
+ * &lt;action android:name="android.service.trust.TrustAgentService" />
+ * &lt;/intent-filter>
+ * &lt;meta-data android:name="android.service.trust.trustagent"
+ * android:value="&#64;xml/trust_agent" />
+ * &lt;/service></pre>
+ *
+ * <p>The associated meta-data file can specify an activity that is accessible through Settings
+ * and should allow configuring the trust agent, as defined in
+ * {@link android.R.styleable#TrustAgent}. For example:</p>
+ *
+ * <pre>
+ * &lt;trust_agent xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:settingsActivity=".TrustAgentSettings" /></pre>
+ */
+public class TrustAgentService extends Service {
+ private final String TAG = TrustAgentService.class.getSimpleName() +
+ "[" + getClass().getSimpleName() + "]";
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE
+ = "android.service.trust.TrustAgentService";
+
+ /**
+ * The name of the {@code meta-data} tag pointing to additional configuration of the trust
+ * agent.
+ */
+ public static final String TRUST_AGENT_META_DATA = "android.service.trust.trustagent";
+
+ private static final int MSG_UNLOCK_ATTEMPT = 1;
+
+ private static final boolean DEBUG = false;
+
+ private ITrustAgentServiceCallback mCallback;
+
+ private Handler mHandler = new Handler() {
+ public void handleMessage(android.os.Message msg) {
+ switch (msg.what) {
+ case MSG_UNLOCK_ATTEMPT:
+ onUnlockAttempt(msg.arg1 != 0);
+ break;
+ }
+ };
+ };
+
+ /**
+ * Called when the user attempted to authenticate on the device.
+ *
+ * @param successful true if the attempt succeeded
+ */
+ protected void onUnlockAttempt(boolean successful) {
+ }
+
+ private void onError(String msg) {
+ Slog.v(TAG, "Remote exception while " + msg);
+ }
+
+ /**
+ * Call to enable trust on the device.
+ *
+ * @param message describes why the device is trusted, e.g. "Trusted by location".
+ * @param durationMs amount of time in milliseconds to keep the device in a trusted state. Trust
+ * for this agent will automatically be revoked when the timeout expires.
+ * @param initiatedByUser indicates that the user has explicitly initiated an action that proves
+ * the user is about to use the device.
+ */
+ protected final void enableTrust(String message, long durationMs, boolean initiatedByUser) {
+ if (mCallback != null) {
+ try {
+ mCallback.enableTrust(message, durationMs, initiatedByUser);
+ } catch (RemoteException e) {
+ onError("calling enableTrust()");
+ }
+ }
+ }
+
+ /**
+ * Call to revoke trust on the device.
+ */
+ protected final void revokeTrust() {
+ if (mCallback != null) {
+ try {
+ mCallback.revokeTrust();
+ } catch (RemoteException e) {
+ onError("calling revokeTrust()");
+ }
+ }
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ if (DEBUG) Slog.v(TAG, "onBind() intent = " + intent);
+ return new TrustAgentServiceWrapper();
+ }
+
+ private final class TrustAgentServiceWrapper extends ITrustAgentService.Stub {
+ @Override
+ public void onUnlockAttempt(boolean successful) {
+ mHandler.obtainMessage(MSG_UNLOCK_ATTEMPT, successful ? 1 : 0, 0)
+ .sendToTarget();
+ }
+
+ public void setCallback(ITrustAgentServiceCallback callback) {
+ mCallback = callback;
+ }
+ }
+
+}
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..c527acf 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");
}
@@ -988,6 +990,7 @@ public class TextToSpeech {
* must be behave as per {@link Boolean#parseBoolean(String)}.
*
* @param locale The locale to query features for.
+ * @return Set instance. May return {@code null} on error.
*/
public Set<String> getFeatures(final Locale locale) {
return runAction(new Action<Set<String>>() {
@@ -1443,8 +1446,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 +1464,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 +1478,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> {
@@ -1484,11 +1501,13 @@ public class TextToSpeech {
try {
mService.setCallback(getCallerIdentity(), mCallback);
- String[] defaultLanguage = mService.getClientDefaultLanguage();
- mParams.putString(Engine.KEY_PARAM_LANGUAGE, defaultLanguage[0]);
- mParams.putString(Engine.KEY_PARAM_COUNTRY, defaultLanguage[1]);
- mParams.putString(Engine.KEY_PARAM_VARIANT, defaultLanguage[2]);
+ if (mParams.getString(Engine.KEY_PARAM_LANGUAGE) == null) {
+ String[] defaultLanguage = mService.getClientDefaultLanguage();
+ mParams.putString(Engine.KEY_PARAM_LANGUAGE, defaultLanguage[0]);
+ mParams.putString(Engine.KEY_PARAM_COUNTRY, defaultLanguage[1]);
+ mParams.putString(Engine.KEY_PARAM_VARIANT, defaultLanguage[2]);
+ }
Log.i(TAG, "Set up connection to " + mName);
return SUCCESS;
diff --git a/core/java/android/speech/tts/TextToSpeechClient.java b/core/java/android/speech/tts/TextToSpeechClient.java
new file mode 100644
index 0000000..10e2073
--- /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 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/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 06935ae..77ef1da 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -21,6 +21,7 @@ import android.text.style.UpdateLayout;
import android.text.style.WrapTogetherSpan;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
import java.lang.ref.WeakReference;
@@ -401,7 +402,7 @@ public class DynamicLayout extends Layout
if (mBlockEndLines == null) {
// Initial creation of the array, no test on previous block ending line
- mBlockEndLines = new int[ArrayUtils.idealIntArraySize(1)];
+ mBlockEndLines = ArrayUtils.newUnpaddedIntArray(1);
mBlockEndLines[mNumberOfBlocks] = line;
mNumberOfBlocks++;
return;
@@ -409,13 +410,7 @@ public class DynamicLayout extends Layout
final int previousBlockEndLine = mBlockEndLines[mNumberOfBlocks - 1];
if (line > previousBlockEndLine) {
- if (mNumberOfBlocks == mBlockEndLines.length) {
- // Grow the array if needed
- int[] blockEndLines = new int[ArrayUtils.idealIntArraySize(mNumberOfBlocks + 1)];
- System.arraycopy(mBlockEndLines, 0, blockEndLines, 0, mNumberOfBlocks);
- mBlockEndLines = blockEndLines;
- }
- mBlockEndLines[mNumberOfBlocks] = line;
+ mBlockEndLines = GrowingArrayUtils.append(mBlockEndLines, mNumberOfBlocks, line);
mNumberOfBlocks++;
}
}
@@ -483,9 +478,9 @@ public class DynamicLayout extends Layout
}
if (newNumberOfBlocks > mBlockEndLines.length) {
- final int newSize = ArrayUtils.idealIntArraySize(newNumberOfBlocks);
- int[] blockEndLines = new int[newSize];
- int[] blockIndices = new int[newSize];
+ int[] blockEndLines = ArrayUtils.newUnpaddedIntArray(
+ Math.max(mBlockEndLines.length * 2, newNumberOfBlocks));
+ int[] blockIndices = new int[blockEndLines.length];
System.arraycopy(mBlockEndLines, 0, blockEndLines, 0, firstBlock);
System.arraycopy(mBlockIndices, 0, blockIndices, 0, firstBlock);
System.arraycopy(mBlockEndLines, lastBlock + 1,
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index f839d52..2fcc597 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.
@@ -214,7 +211,7 @@ public class Html {
private static String getOpenParaTagWithDirection(Spanned text, int start, int end) {
final int len = end - start;
- final byte[] levels = new byte[ArrayUtils.idealByteArraySize(len)];
+ final byte[] levels = ArrayUtils.newUnpaddedByteArray(len);
final char[] buffer = TextUtils.obtain(len);
TextUtils.getChars(text, start, end, buffer, 0);
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 9dfd383..4bfcaff 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -31,6 +31,7 @@ import android.text.style.ReplacementSpan;
import android.text.style.TabStopSpan;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
import java.util.Arrays;
@@ -403,14 +404,9 @@ public abstract class Layout {
// construction
if (mLineBackgroundSpans.spanStarts[j] >= end ||
mLineBackgroundSpans.spanEnds[j] <= start) continue;
- if (spansLength == spans.length) {
- // The spans array needs to be expanded
- int newSize = ArrayUtils.idealObjectArraySize(2 * spansLength);
- ParagraphStyle[] newSpans = new ParagraphStyle[newSize];
- System.arraycopy(spans, 0, newSpans, 0, spansLength);
- spans = newSpans;
- }
- spans[spansLength++] = mLineBackgroundSpans.spans[j];
+ spans = GrowingArrayUtils.append(
+ spans, spansLength, mLineBackgroundSpans.spans[j]);
+ spansLength++;
}
}
}
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 101d6a2..f8e3c83 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -98,10 +98,10 @@ class MeasuredText {
mPos = 0;
if (mWidths == null || mWidths.length < len) {
- mWidths = new float[ArrayUtils.idealFloatArraySize(len)];
+ mWidths = ArrayUtils.newUnpaddedFloatArray(len);
}
if (mChars == null || mChars.length < len) {
- mChars = new char[ArrayUtils.idealCharArraySize(len)];
+ mChars = ArrayUtils.newUnpaddedCharArray(len);
}
TextUtils.getChars(text, start, end, mChars, 0);
@@ -130,7 +130,7 @@ class MeasuredText {
mEasy = true;
} else {
if (mLevels == null || mLevels.length < len) {
- mLevels = new byte[ArrayUtils.idealByteArraySize(len)];
+ mLevels = ArrayUtils.newUnpaddedByteArray(len);
}
int bidiRequest;
if (textDir == TextDirectionHeuristics.LTR) {
diff --git a/core/java/android/text/PackedIntVector.java b/core/java/android/text/PackedIntVector.java
index d87f600..546ab44 100644
--- a/core/java/android/text/PackedIntVector.java
+++ b/core/java/android/text/PackedIntVector.java
@@ -17,6 +17,7 @@
package android.text;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
/**
@@ -252,9 +253,9 @@ class PackedIntVector {
*/
private final void growBuffer() {
final int columns = mColumns;
- int newsize = size() + 1;
- newsize = ArrayUtils.idealIntArraySize(newsize * columns) / columns;
- int[] newvalues = new int[newsize * columns];
+ int[] newvalues = ArrayUtils.newUnpaddedIntArray(
+ GrowingArrayUtils.growSize(size()) * columns);
+ int newsize = newvalues.length / columns;
final int[] valuegap = mValueGap;
final int rowgapstart = mRowGapStart;
diff --git a/core/java/android/text/PackedObjectVector.java b/core/java/android/text/PackedObjectVector.java
index a29df09..b777e16 100644
--- a/core/java/android/text/PackedObjectVector.java
+++ b/core/java/android/text/PackedObjectVector.java
@@ -17,6 +17,9 @@
package android.text;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
class PackedObjectVector<E>
{
@@ -32,12 +35,11 @@ class PackedObjectVector<E>
PackedObjectVector(int columns)
{
mColumns = columns;
- mRows = ArrayUtils.idealIntArraySize(0) / mColumns;
+ mValues = EmptyArray.OBJECT;
+ mRows = 0;
mRowGapStart = 0;
mRowGapLength = mRows;
-
- mValues = new Object[mRows * mColumns];
}
public E
@@ -109,10 +111,9 @@ class PackedObjectVector<E>
private void
growBuffer()
{
- int newsize = size() + 1;
- newsize = ArrayUtils.idealIntArraySize(newsize * mColumns) / mColumns;
- Object[] newvalues = new Object[newsize * mColumns];
-
+ Object[] newvalues = ArrayUtils.newUnpaddedObjectArray(
+ GrowingArrayUtils.growSize(size()) * mColumns);
+ int newsize = newvalues.length / mColumns;
int after = mRows - (mRowGapStart + mRowGapLength);
System.arraycopy(mValues, 0, newvalues, 0, mColumns * mRowGapStart);
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 34274a6..f440853 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -21,6 +21,9 @@ import android.graphics.Paint;
import android.util.Log;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
import java.lang.reflect.Array;
@@ -29,6 +32,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
*/
@@ -53,19 +57,17 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
if (srclen < 0) throw new StringIndexOutOfBoundsException();
- int len = ArrayUtils.idealCharArraySize(srclen + 1);
- mText = new char[len];
+ mText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(srclen));
mGapStart = srclen;
- mGapLength = len - srclen;
+ mGapLength = mText.length - srclen;
TextUtils.getChars(text, start, end, mText, 0);
mSpanCount = 0;
- int alloc = ArrayUtils.idealIntArraySize(0);
- mSpans = new Object[alloc];
- mSpanStarts = new int[alloc];
- mSpanEnds = new int[alloc];
- mSpanFlags = new int[alloc];
+ mSpans = EmptyArray.OBJECT;
+ mSpanStarts = EmptyArray.INT;
+ mSpanEnds = EmptyArray.INT;
+ mSpanFlags = EmptyArray.INT;
if (text instanceof Spanned) {
Spanned sp = (Spanned) text;
@@ -129,12 +131,14 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
private void resizeFor(int size) {
final int oldLength = mText.length;
- final int newLength = ArrayUtils.idealCharArraySize(size + 1);
- final int delta = newLength - oldLength;
- if (delta == 0) return;
+ if (size + 1 <= oldLength) {
+ return;
+ }
- char[] newText = new char[newLength];
+ char[] newText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(size));
System.arraycopy(mText, 0, newText, 0, mGapStart);
+ final int newLength = newText.length;
+ final int delta = newLength - oldLength;
final int after = oldLength - (mGapStart + mGapLength);
System.arraycopy(mText, oldLength - after, newText, newLength - after, after);
mText = newText;
@@ -436,10 +440,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 +633,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
@@ -661,28 +682,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
}
}
- if (mSpanCount + 1 >= mSpans.length) {
- int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1);
- Object[] newspans = new Object[newsize];
- int[] newspanstarts = new int[newsize];
- int[] newspanends = new int[newsize];
- int[] newspanflags = new int[newsize];
-
- System.arraycopy(mSpans, 0, newspans, 0, mSpanCount);
- System.arraycopy(mSpanStarts, 0, newspanstarts, 0, mSpanCount);
- System.arraycopy(mSpanEnds, 0, newspanends, 0, mSpanCount);
- System.arraycopy(mSpanFlags, 0, newspanflags, 0, mSpanCount);
-
- mSpans = newspans;
- mSpanStarts = newspanstarts;
- mSpanEnds = newspanends;
- mSpanFlags = newspanflags;
- }
-
- mSpans[mSpanCount] = what;
- mSpanStarts[mSpanCount] = start;
- mSpanEnds[mSpanCount] = end;
- mSpanFlags[mSpanCount] = flags;
+ mSpans = GrowingArrayUtils.append(mSpans, mSpanCount, what);
+ mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start);
+ mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end);
+ mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags);
mSpanCount++;
if (send) sendSpanAdded(what, nstart, nend);
diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java
index 456a3e5..d114d32 100644
--- a/core/java/android/text/SpannableStringInternal.java
+++ b/core/java/android/text/SpannableStringInternal.java
@@ -17,6 +17,9 @@
package android.text;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
import java.lang.reflect.Array;
@@ -29,9 +32,8 @@ import java.lang.reflect.Array;
else
mText = source.toString().substring(start, end);
- int initial = ArrayUtils.idealIntArraySize(0);
- mSpans = new Object[initial];
- mSpanData = new int[initial * 3];
+ mSpans = EmptyArray.OBJECT;
+ mSpanData = EmptyArray.INT;
if (source instanceof Spanned) {
Spanned sp = (Spanned) source;
@@ -115,9 +117,9 @@ import java.lang.reflect.Array;
}
if (mSpanCount + 1 >= mSpans.length) {
- int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1);
- Object[] newtags = new Object[newsize];
- int[] newdata = new int[newsize * 3];
+ Object[] newtags = ArrayUtils.newUnpaddedObjectArray(
+ GrowingArrayUtils.growSize(mSpanCount));
+ int[] newdata = new int[newtags.length * 3];
System.arraycopy(mSpans, 0, newtags, 0, mSpanCount);
System.arraycopy(mSpanData, 0, newdata, 0, mSpanCount * 3);
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index e7d6fda..638ef22 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -26,6 +26,7 @@ import android.text.style.TabStopSpan;
import android.util.Log;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
/**
* StaticLayout is a Layout for text that will not be edited after it
@@ -130,9 +131,8 @@ public class StaticLayout extends Layout {
mEllipsizedWidth = outerwidth;
}
- mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
- mLineDirections = new Directions[
- ArrayUtils.idealIntArraySize(2 * mColumns)];
+ mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
+ mLines = new int[mLineDirections.length];
mMaximumVisibleLineCount = maxLines;
mMeasured = MeasuredText.obtain();
@@ -149,8 +149,8 @@ public class StaticLayout extends Layout {
super(text, null, 0, null, 0, 0);
mColumns = COLUMNS_ELLIPSIZE;
- mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
- mLineDirections = new Directions[ArrayUtils.idealIntArraySize(2 * mColumns)];
+ mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
+ mLines = new int[mLineDirections.length];
// FIXME This is never recycled
mMeasured = MeasuredText.obtain();
}
@@ -215,8 +215,7 @@ public class StaticLayout extends Layout {
if (chooseHt.length != 0) {
if (chooseHtv == null ||
chooseHtv.length < chooseHt.length) {
- chooseHtv = new int[ArrayUtils.idealIntArraySize(
- chooseHt.length)];
+ chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
}
for (int i = 0; i < chooseHt.length; i++) {
@@ -434,7 +433,7 @@ public class StaticLayout extends Layout {
}
if (mLineCount >= mMaximumVisibleLineCount) {
- break;
+ return;
}
}
}
@@ -599,16 +598,16 @@ public class StaticLayout extends Layout {
int[] lines = mLines;
if (want >= lines.length) {
- int nlen = ArrayUtils.idealIntArraySize(want + 1);
- int[] grow = new int[nlen];
- System.arraycopy(lines, 0, grow, 0, lines.length);
- mLines = grow;
- lines = grow;
-
- Directions[] grow2 = new Directions[nlen];
+ Directions[] grow2 = ArrayUtils.newUnpaddedArray(
+ Directions.class, GrowingArrayUtils.growSize(want));
System.arraycopy(mLineDirections, 0, grow2, 0,
mLineDirections.length);
mLineDirections = grow2;
+
+ int[] grow = new int[grow2.length];
+ System.arraycopy(lines, 0, grow, 0, lines.length);
+ mLines = grow;
+ lines = grow;
}
if (chooseHt != null) {
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 1fecf81..d892f19 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -153,7 +153,7 @@ class TextLine {
if (mCharsValid) {
if (mChars == null || mChars.length < mLen) {
- mChars = new char[ArrayUtils.idealCharArraySize(mLen)];
+ mChars = ArrayUtils.newUnpaddedCharArray(mLen);
}
TextUtils.getChars(text, start, limit, mChars, 0);
if (hasReplacement) {
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 596ca8c..f06ae71 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1321,7 +1321,7 @@ public class TextUtils {
}
if (buf == null || buf.length < len)
- buf = new char[ArrayUtils.idealCharArraySize(len)];
+ buf = ArrayUtils.newUnpaddedCharArray(len);
return buf;
}
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/CircularPropagation.java b/core/java/android/transition/CircularPropagation.java
new file mode 100644
index 0000000..18a3d22
--- /dev/null
+++ b/core/java/android/transition/CircularPropagation.java
@@ -0,0 +1,103 @@
+/*
+ * 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.transition;
+
+import android.graphics.Rect;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A propagation that varies with the distance to the epicenter of the Transition
+ * or center of the scene if no epicenter exists. When a View is visible in the
+ * start of the transition, Views farther from the epicenter will transition
+ * sooner than Views closer to the epicenter. When a View is not in the start
+ * of the transition or is not visible at the start of the transition, it will
+ * transition sooner when closer to the epicenter and later when farther from
+ * the epicenter. This is the default TransitionPropagation used with
+ * {@link android.transition.Explode}.
+ */
+public class CircularPropagation extends VisibilityPropagation {
+ private static final String TAG = "CircularPropagation";
+
+ private float mPropagationSpeed = 4.0f;
+
+ /**
+ * Sets the speed at which transition propagation happens, relative to the duration of the
+ * Transition. A <code>propagationSpeed</code> of 1 means that a View centered farthest from
+ * the epicenter and View centered at the epicenter will have a difference
+ * in start delay of approximately the duration of the Transition. A speed of 2 means the
+ * start delay difference will be approximately half of the duration of the transition. A
+ * value of 0 is illegal, but negative values will invert the propagation.
+ *
+ * @param propagationSpeed The speed at which propagation occurs, relative to the duration
+ * of the transition. A speed of 4 means it works 4 times as fast
+ * as the duration of the transition. May not be 0.
+ */
+ public void setPropagationSpeed(float propagationSpeed) {
+ if (propagationSpeed == 0) {
+ throw new IllegalArgumentException("propagationSpeed may not be 0");
+ }
+ mPropagationSpeed = propagationSpeed;
+ }
+
+ @Override
+ public long getStartDelay(ViewGroup sceneRoot, Transition transition,
+ TransitionValues startValues, TransitionValues endValues) {
+ if (startValues == null && endValues == null) {
+ return 0;
+ }
+ int directionMultiplier = 1;
+ TransitionValues positionValues;
+ if (endValues == null || getViewVisibility(startValues) == View.VISIBLE) {
+ positionValues = startValues;
+ directionMultiplier = -1;
+ } else {
+ positionValues = endValues;
+ }
+
+ int viewCenterX = getViewX(positionValues);
+ int viewCenterY = getViewY(positionValues);
+
+ Rect epicenter = transition.getEpicenter();
+ int epicenterX;
+ int epicenterY;
+ if (epicenter != null) {
+ epicenterX = epicenter.centerX();
+ epicenterY = epicenter.centerY();
+ } else {
+ int[] loc = new int[2];
+ sceneRoot.getLocationOnScreen(loc);
+ epicenterX = Math.round(loc[0] + (sceneRoot.getWidth() / 2)
+ + sceneRoot.getTranslationX());
+ epicenterY = Math.round(loc[1] + (sceneRoot.getHeight() / 2)
+ + sceneRoot.getTranslationY());
+ }
+ float distance = distance(viewCenterX, viewCenterY, epicenterX, epicenterY);
+ float maxDistance = distance(0, 0, sceneRoot.getWidth(), sceneRoot.getHeight());
+ float distanceFraction = distance/maxDistance;
+
+ return Math.round(transition.getDuration() * directionMultiplier / mPropagationSpeed
+ * distanceFraction);
+ }
+
+ private static float distance(float x1, float y1, float x2, float y2) {
+ float x = x2 - x1;
+ float y = y2 - y1;
+ return FloatMath.sqrt((x * x) + (y * y));
+ }
+}
diff --git a/core/java/android/transition/Explode.java b/core/java/android/transition/Explode.java
new file mode 100644
index 0000000..fae527c
--- /dev/null
+++ b/core/java/android/transition/Explode.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.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * This transition tracks changes to the visibility of target views in the
+ * start and end scenes and moves views in or out from the edges of the
+ * scene. Visibility is determined by both the
+ * {@link View#setVisibility(int)} state of the view as well as whether it
+ * is parented in the current view hierarchy. Disappearing Views are
+ * limited as described in {@link Visibility#onDisappear(android.view.ViewGroup,
+ * TransitionValues, int, TransitionValues, int)}.
+ * <p>Views move away from the focal View or the center of the Scene if
+ * no epicenter was provided.</p>
+ */
+public class Explode extends Visibility {
+ private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
+ private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
+ private static final String TAG = "Explode";
+
+ private static final String PROPNAME_SCREEN_BOUNDS = "android:out:screenBounds";
+
+ private int[] mTempLoc = new int[2];
+
+ public Explode() {
+ setPropagation(new CircularPropagation());
+ }
+
+ private void captureValues(TransitionValues transitionValues) {
+ View view = transitionValues.view;
+ view.getLocationOnScreen(mTempLoc);
+ int left = mTempLoc[0] + Math.round(view.getTranslationX());
+ int top = mTempLoc[1] + Math.round(view.getTranslationY());
+ int right = left + view.getWidth();
+ int bottom = top + view.getHeight();
+ transitionValues.values.put(PROPNAME_SCREEN_BOUNDS, new Rect(left, top, right, bottom));
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ super.captureStartValues(transitionValues);
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ super.captureEndValues(transitionValues);
+ captureValues(transitionValues);
+ }
+
+ private Animator createAnimation(final View view, float startX, float startY, float endX,
+ float endY, float terminalX, float terminalY, TimeInterpolator interpolator) {
+ view.setTranslationX(startX);
+ view.setTranslationY(startY);
+ if (startY == endY && startX == endX) {
+ return null;
+ }
+ Path path = new Path();
+ path.moveTo(startX, startY);
+ path.lineTo(endX, endY);
+ ObjectAnimator pathAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
+ View.TRANSLATION_Y, path);
+ pathAnimator.setInterpolator(interpolator);
+ OutAnimatorListener listener = new OutAnimatorListener(view, terminalX, terminalY,
+ endX, endY);
+ pathAnimator.addListener(listener);
+ pathAnimator.addPauseListener(listener);
+
+ return pathAnimator;
+ }
+
+ @Override
+ public Animator onAppear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues, TransitionValues endValues) {
+ if (endValues == null) {
+ return null;
+ }
+ Rect bounds = (Rect) endValues.values.get(PROPNAME_SCREEN_BOUNDS);
+ calculateOut(sceneRoot, bounds, mTempLoc);
+
+ final float endX = view.getTranslationX();
+ final float startX = endX + mTempLoc[0];
+ final float endY = view.getTranslationY();
+ final float startY = endY + mTempLoc[1];
+
+ return createAnimation(view, startX, startY, endX, endY, endX, endY, sDecelerate);
+ }
+
+ @Override
+ public Animator onDisappear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues, TransitionValues endValues) {
+ Rect bounds = (Rect) startValues.values.get(PROPNAME_SCREEN_BOUNDS);
+ calculateOut(sceneRoot, bounds, mTempLoc);
+
+ final float startX = view.getTranslationX();
+ final float endX = startX + mTempLoc[0];
+ final float startY = view.getTranslationY();
+ final float endY = startY + mTempLoc[1];
+
+ return createAnimation(view, startX, startY, endX, endY, startX, startY,
+ sAccelerate);
+ }
+
+ private void calculateOut(View sceneRoot, Rect bounds, int[] outVector) {
+ sceneRoot.getLocationOnScreen(mTempLoc);
+ int sceneRootX = mTempLoc[0];
+ int sceneRootY = mTempLoc[1];
+ int focalX;
+ int focalY;
+
+ Rect epicenter = getEpicenter();
+ if (epicenter == null) {
+ focalX = sceneRootX + (sceneRoot.getWidth() / 2)
+ + Math.round(sceneRoot.getTranslationX());
+ focalY = sceneRootY + (sceneRoot.getHeight() / 2)
+ + Math.round(sceneRoot.getTranslationY());
+ } else {
+ focalX = epicenter.centerX();
+ focalY = epicenter.centerY();
+ }
+
+ int centerX = bounds.centerX();
+ int centerY = bounds.centerY();
+ float xVector = centerX - focalX;
+ float yVector = centerY - focalY;
+
+ if (xVector == 0 && yVector == 0) {
+ // Random direction when View is centered on focal View.
+ xVector = (float)(Math.random() * 2) - 1;
+ yVector = (float)(Math.random() * 2) - 1;
+ }
+ float vectorSize = calculateDistance(xVector, yVector);
+ xVector /= vectorSize;
+ yVector /= vectorSize;
+
+ float maxDistance =
+ calculateMaxDistance(sceneRoot, focalX - sceneRootX, focalY - sceneRootY);
+
+ outVector[0] = Math.round(maxDistance * xVector);
+ outVector[1] = Math.round(maxDistance * yVector);
+ }
+
+ private static float calculateMaxDistance(View sceneRoot, int focalX, int focalY) {
+ int maxX = Math.max(focalX, sceneRoot.getWidth() - focalX);
+ int maxY = Math.max(focalY, sceneRoot.getHeight() - focalY);
+ return calculateDistance(maxX, maxY);
+ }
+
+ private static float calculateDistance(float x, float y) {
+ return FloatMath.sqrt((x * x) + (y * y));
+ }
+
+ private static class OutAnimatorListener extends AnimatorListenerAdapter {
+ private final View mView;
+ private boolean mCanceled = false;
+ private float mPausedX;
+ private float mPausedY;
+ private final float mTerminalX;
+ private final float mTerminalY;
+ private final float mEndX;
+ private final float mEndY;
+
+ public OutAnimatorListener(View view, float terminalX, float terminalY,
+ float endX, float endY) {
+ mView = view;
+ mTerminalX = terminalX;
+ mTerminalY = terminalY;
+ mEndX = endX;
+ mEndY = endY;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ mView.setTranslationX(mTerminalX);
+ mView.setTranslationY(mTerminalY);
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (!mCanceled) {
+ mView.setTranslationX(mTerminalX);
+ mView.setTranslationY(mTerminalY);
+ }
+ }
+
+ @Override
+ public void onAnimationPause(Animator animator) {
+ mPausedX = mView.getTranslationX();
+ mPausedY = mView.getTranslationY();
+ mView.setTranslationY(mEndX);
+ mView.setTranslationY(mEndY);
+ }
+
+ @Override
+ public void onAnimationResume(Animator animator) {
+ mView.setTranslationX(mPausedX);
+ mView.setTranslationY(mPausedY);
+ }
+ }
+}
diff --git a/core/java/android/transition/Fade.java b/core/java/android/transition/Fade.java
index 8edb1ff..08e27d3 100644
--- a/core/java/android/transition/Fade.java
+++ b/core/java/android/transition/Fade.java
@@ -59,8 +59,6 @@ public class Fade extends Visibility {
private static boolean DBG = Transition.DBG && false;
private static final String LOG_TAG = "Fade";
- private static final String PROPNAME_SCREEN_X = "android:fade:screenX";
- private static final String PROPNAME_SCREEN_Y = "android:fade:screenY";
/**
* Fading mode used in {@link #Fade(int)} to make the transition
@@ -98,245 +96,81 @@ public class Fade extends Visibility {
/**
* Utility method to handle creating and running the Animator.
*/
- private Animator createAnimation(View view, float startAlpha, float endAlpha,
- AnimatorListenerAdapter listener) {
+ private Animator createAnimation(View view, float startAlpha, float endAlpha) {
if (startAlpha == endAlpha) {
- // run listener if we're noop'ing the animation, to get the end-state results now
- if (listener != null) {
- listener.onAnimationEnd(null);
- }
return null;
}
- final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "transitionAlpha", startAlpha,
- endAlpha);
+ view.setTransitionAlpha(startAlpha);
+ final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "transitionAlpha", endAlpha);
if (DBG) {
Log.d(LOG_TAG, "Created animator " + anim);
}
- if (listener != null) {
- anim.addListener(listener);
- anim.addPauseListener(listener);
- }
+ FadeAnimatorListener listener = new FadeAnimatorListener(view, endAlpha);
+ anim.addListener(listener);
+ anim.addPauseListener(listener);
return anim;
}
- private void captureValues(TransitionValues transitionValues) {
- int[] loc = new int[2];
- transitionValues.view.getLocationOnScreen(loc);
- transitionValues.values.put(PROPNAME_SCREEN_X, loc[0]);
- transitionValues.values.put(PROPNAME_SCREEN_Y, loc[1]);
- }
-
- @Override
- public void captureStartValues(TransitionValues transitionValues) {
- super.captureStartValues(transitionValues);
- captureValues(transitionValues);
- }
-
@Override
- public Animator onAppear(ViewGroup sceneRoot,
- TransitionValues startValues, int startVisibility,
- TransitionValues endValues, int endVisibility) {
+ public Animator onAppear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues,
+ TransitionValues endValues) {
if ((mFadingMode & IN) != IN || endValues == null) {
return null;
}
- final View endView = endValues.view;
if (DBG) {
View startView = (startValues != null) ? startValues.view : null;
Log.d(LOG_TAG, "Fade.onAppear: startView, startVis, endView, endVis = " +
- startView + ", " + startVisibility + ", " + endView + ", " + endVisibility);
+ startView + ", " + view);
}
- endView.setTransitionAlpha(0);
- TransitionListener transitionListener = new TransitionListenerAdapter() {
- boolean mCanceled = false;
- float mPausedAlpha;
-
- @Override
- public void onTransitionCancel(Transition transition) {
- endView.setTransitionAlpha(1);
- mCanceled = true;
- }
-
- @Override
- public void onTransitionEnd(Transition transition) {
- if (!mCanceled) {
- endView.setTransitionAlpha(1);
- }
- }
-
- @Override
- public void onTransitionPause(Transition transition) {
- mPausedAlpha = endView.getTransitionAlpha();
- endView.setTransitionAlpha(1);
- }
-
- @Override
- public void onTransitionResume(Transition transition) {
- endView.setTransitionAlpha(mPausedAlpha);
- }
- };
- addListener(transitionListener);
- return createAnimation(endView, 0, 1, null);
+ return createAnimation(view, 0, 1);
}
@Override
- public Animator onDisappear(ViewGroup sceneRoot,
- TransitionValues startValues, int startVisibility,
- TransitionValues endValues, int endVisibility) {
+ public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues,
+ TransitionValues endValues) {
if ((mFadingMode & OUT) != OUT) {
return null;
}
- View view = null;
- View startView = (startValues != null) ? startValues.view : null;
- View endView = (endValues != null) ? endValues.view : null;
- if (DBG) {
- Log.d(LOG_TAG, "Fade.onDisappear: startView, startVis, endView, endVis = " +
- startView + ", " + startVisibility + ", " + endView + ", " + endVisibility);
- }
- View overlayView = null;
- View viewToKeep = null;
- if (endView == null || endView.getParent() == null) {
- if (endView != null) {
- // endView was removed from its parent - add it to the overlay
- view = overlayView = endView;
- } else if (startView != null) {
- // endView does not exist. Use startView only under certain
- // conditions, because placing a view in an overlay necessitates
- // it being removed from its current parent
- if (startView.getParent() == null) {
- // no parent - safe to use
- view = overlayView = startView;
- } else if (startView.getParent() instanceof View &&
- startView.getParent().getParent() == null) {
- View startParent = (View) startView.getParent();
- int id = startParent.getId();
- if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) {
- // no parent, but its parent is unparented but the parent
- // hierarchy has been replaced by a new hierarchy with the same id
- // and it is safe to un-parent startView
- view = overlayView = startView;
- }
- }
- }
- } else {
- // visibility change
- if (endVisibility == View.INVISIBLE) {
- view = endView;
- viewToKeep = view;
- } else {
- // Becoming GONE
- if (startView == endView) {
- view = endView;
- viewToKeep = view;
- } else {
- view = startView;
- overlayView = view;
- }
- }
- }
- final int finalVisibility = endVisibility;
- // TODO: add automatic facility to Visibility superclass for keeping views around
- if (overlayView != null) {
- // TODO: Need to do this for general case of adding to overlay
- int screenX = (Integer) startValues.values.get(PROPNAME_SCREEN_X);
- int screenY = (Integer) startValues.values.get(PROPNAME_SCREEN_Y);
- int[] loc = new int[2];
- sceneRoot.getLocationOnScreen(loc);
- overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft());
- overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop());
- sceneRoot.getOverlay().add(overlayView);
- // TODO: add automatic facility to Visibility superclass for keeping views around
- final float startAlpha = 1;
- float endAlpha = 0;
- final View finalView = view;
- final View finalOverlayView = overlayView;
- final View finalViewToKeep = viewToKeep;
- final ViewGroup finalSceneRoot = sceneRoot;
- final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- finalView.setTransitionAlpha(startAlpha);
- // TODO: restore view offset from overlay repositioning
- if (finalViewToKeep != null) {
- finalViewToKeep.setVisibility(finalVisibility);
- }
- if (finalOverlayView != null) {
- finalSceneRoot.getOverlay().remove(finalOverlayView);
- }
- }
- @Override
- public void onAnimationPause(Animator animation) {
- if (finalOverlayView != null) {
- finalSceneRoot.getOverlay().remove(finalOverlayView);
- }
- }
+ return createAnimation(view, 1, 0);
+ }
+
+ private static class FadeAnimatorListener extends AnimatorListenerAdapter {
+ private final View mView;
+ private final float mEndAlpha;
+ private boolean mCanceled = false;
+ private float mPausedAlpha;
- @Override
- public void onAnimationResume(Animator animation) {
- if (finalOverlayView != null) {
- finalSceneRoot.getOverlay().add(finalOverlayView);
- }
- }
- };
- return createAnimation(view, startAlpha, endAlpha, endListener);
+ public FadeAnimatorListener(View view, float endAlpha) {
+ mView = view;
+ mEndAlpha = endAlpha;
}
- if (viewToKeep != null) {
- // TODO: find a different way to do this, like just changing the view to be
- // VISIBLE for the duration of the transition
- viewToKeep.setVisibility((View.VISIBLE));
- // TODO: add automatic facility to Visibility superclass for keeping views around
- final float startAlpha = 1;
- float endAlpha = 0;
- final View finalView = view;
- final View finalOverlayView = overlayView;
- final View finalViewToKeep = viewToKeep;
- final ViewGroup finalSceneRoot = sceneRoot;
- final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
- boolean mCanceled = false;
- float mPausedAlpha = -1;
- @Override
- public void onAnimationPause(Animator animation) {
- if (finalViewToKeep != null && !mCanceled) {
- finalViewToKeep.setVisibility(finalVisibility);
- }
- mPausedAlpha = finalView.getTransitionAlpha();
- finalView.setTransitionAlpha(startAlpha);
- }
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ mCanceled = true;
+ if (mPausedAlpha >= 0) {
+ mView.setTransitionAlpha(mPausedAlpha);
+ }
+ }
- @Override
- public void onAnimationResume(Animator animation) {
- if (finalViewToKeep != null && !mCanceled) {
- finalViewToKeep.setVisibility(View.VISIBLE);
- }
- finalView.setTransitionAlpha(mPausedAlpha);
- }
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (!mCanceled) {
+ mView.setTransitionAlpha(mEndAlpha);
+ }
+ }
- @Override
- public void onAnimationCancel(Animator animation) {
- mCanceled = true;
- if (mPausedAlpha >= 0) {
- finalView.setTransitionAlpha(mPausedAlpha);
- }
- }
+ @Override
+ public void onAnimationPause(Animator animator) {
+ mPausedAlpha = mView.getTransitionAlpha();
+ mView.setTransitionAlpha(mEndAlpha);
+ }
- @Override
- public void onAnimationEnd(Animator animation) {
- if (!mCanceled) {
- finalView.setTransitionAlpha(startAlpha);
- }
- // TODO: restore view offset from overlay repositioning
- if (finalViewToKeep != null && !mCanceled) {
- finalViewToKeep.setVisibility(finalVisibility);
- }
- if (finalOverlayView != null) {
- finalSceneRoot.getOverlay().remove(finalOverlayView);
- }
- }
- };
- return createAnimation(view, startAlpha, endAlpha, endListener);
+ @Override
+ public void onAnimationResume(Animator animator) {
+ mView.setTransitionAlpha(mPausedAlpha);
}
- return null;
}
-
-} \ No newline at end of file
+}
diff --git a/core/java/android/transition/MatrixClippedDrawable.java b/core/java/android/transition/MatrixClippedDrawable.java
new file mode 100644
index 0000000..ebaad59
--- /dev/null
+++ b/core/java/android/transition/MatrixClippedDrawable.java
@@ -0,0 +1,300 @@
+/*
+ * 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.transition;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.Property;
+
+/**
+ * Used in MoveImage to mock an ImageView as a Drawable to be scaled in the scene root Overlay.
+ * @hide
+ */
+class MatrixClippedDrawable extends Drawable implements Drawable.Callback {
+ private static final String TAG = "MatrixClippedDrawable";
+
+ private ClippedMatrixState mClippedMatrixState;
+
+ public static final Property<MatrixClippedDrawable, Rect> CLIP_PROPERTY
+ = new Property<MatrixClippedDrawable, Rect>(Rect.class, "clipRect") {
+
+ @Override
+ public Rect get(MatrixClippedDrawable object) {
+ return object.getClipRect();
+ }
+
+ @Override
+ public void set(MatrixClippedDrawable object, Rect value) {
+ object.setClipRect(value);
+ }
+ };
+
+ public static final Property<MatrixClippedDrawable, Matrix> MATRIX_PROPERTY
+ = new Property<MatrixClippedDrawable, Matrix>(Matrix.class, "matrix") {
+ @Override
+ public void set(MatrixClippedDrawable object, Matrix value) {
+ object.setMatrix(value);
+ }
+
+ @Override
+ public Matrix get(MatrixClippedDrawable object) {
+ return object.getMatrix();
+ }
+ };
+
+ public MatrixClippedDrawable(Drawable drawable) {
+ this(null, null);
+
+ mClippedMatrixState.mDrawable = drawable;
+
+ if (drawable != null) {
+ drawable.setCallback(this);
+ }
+ }
+
+ public void setMatrix(Matrix matrix) {
+ if (matrix == null) {
+ mClippedMatrixState.mMatrix = null;
+ } else {
+ if (mClippedMatrixState.mMatrix == null) {
+ mClippedMatrixState.mMatrix = new Matrix();
+ }
+ mClippedMatrixState.mMatrix.set(matrix);
+ }
+ invalidateSelf();
+ }
+
+ public Matrix getMatrix() {
+ return mClippedMatrixState.mMatrix;
+ }
+
+ public Rect getClipRect() {
+ return mClippedMatrixState.mClipRect;
+ }
+
+ public void setClipRect(Rect clipRect) {
+ if (clipRect == null) {
+ if (mClippedMatrixState.mClipRect != null) {
+ mClippedMatrixState.mClipRect = null;
+ invalidateSelf();
+ }
+ } else {
+ if (mClippedMatrixState.mClipRect == null) {
+ mClippedMatrixState.mClipRect = new Rect(clipRect);
+ } else {
+ mClippedMatrixState.mClipRect.set(clipRect);
+ }
+ invalidateSelf();
+ }
+ }
+
+ // overrides from Drawable.Callback
+
+ public void invalidateDrawable(Drawable who) {
+ final Drawable.Callback callback = getCallback();
+ if (callback != null) {
+ callback.invalidateDrawable(this);
+ }
+ }
+
+ public void scheduleDrawable(Drawable who, Runnable what, long when) {
+ final Drawable.Callback callback = getCallback();
+ if (callback != null) {
+ callback.scheduleDrawable(this, what, when);
+ }
+ }
+
+ public void unscheduleDrawable(Drawable who, Runnable what) {
+ final Drawable.Callback callback = getCallback();
+ if (callback != null) {
+ callback.unscheduleDrawable(this, what);
+ }
+ }
+
+ // overrides from Drawable
+
+ @Override
+ public int getChangingConfigurations() {
+ return super.getChangingConfigurations()
+ | mClippedMatrixState.mChangingConfigurations
+ | mClippedMatrixState.mDrawable.getChangingConfigurations();
+ }
+
+ @Override
+ public boolean getPadding(Rect padding) {
+ // XXX need to adjust padding!
+ return mClippedMatrixState.mDrawable.getPadding(padding);
+ }
+
+ @Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ mClippedMatrixState.mDrawable.setVisible(visible, restart);
+ return super.setVisible(visible, restart);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mClippedMatrixState.mDrawable.setAlpha(alpha);
+ }
+
+ @Override
+ public int getAlpha() {
+ return mClippedMatrixState.mDrawable.getAlpha();
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ mClippedMatrixState.mDrawable.setColorFilter(cf);
+ }
+
+ @Override
+ public int getOpacity() {
+ return mClippedMatrixState.mDrawable.getOpacity();
+ }
+
+ @Override
+ public boolean isStateful() {
+ return mClippedMatrixState.mDrawable.isStateful();
+ }
+
+ @Override
+ protected boolean onStateChange(int[] state) {
+ return mClippedMatrixState.mDrawable.setState(state);
+ }
+
+ @Override
+ protected boolean onLevelChange(int level) {
+ mClippedMatrixState.mDrawable.setLevel(level);
+ invalidateSelf();
+ return true;
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.setBounds(bounds);
+ if (mClippedMatrixState.mMatrix == null) {
+ mClippedMatrixState.mDrawable.setBounds(bounds);
+ } else {
+ int drawableWidth = mClippedMatrixState.mDrawable.getIntrinsicWidth();
+ int drawableHeight = mClippedMatrixState.mDrawable.getIntrinsicHeight();
+ mClippedMatrixState.mDrawable.setBounds(bounds.left, bounds.top,
+ drawableWidth + bounds.left, drawableHeight + bounds.top);
+ }
+ invalidateSelf();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ Rect bounds = getBounds();
+ int left = bounds.left;
+ int top = bounds.top;
+ int saveCount = canvas.getSaveCount();
+ canvas.save();
+ if (mClippedMatrixState.mClipRect != null) {
+ canvas.clipRect(mClippedMatrixState.mClipRect);
+ } else {
+ canvas.clipRect(bounds);
+ }
+
+ if (mClippedMatrixState != null && !mClippedMatrixState.mMatrix.isIdentity()) {
+ canvas.translate(left, top);
+ canvas.concat(mClippedMatrixState.mMatrix);
+ canvas.translate(-left, -top);
+ }
+ mClippedMatrixState.mDrawable.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mClippedMatrixState.mDrawable.getIntrinsicWidth();
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mClippedMatrixState.mDrawable.getIntrinsicHeight();
+ }
+
+ @Override
+ public Drawable.ConstantState getConstantState() {
+ if (mClippedMatrixState.canConstantState()) {
+ mClippedMatrixState.mChangingConfigurations = getChangingConfigurations();
+ return mClippedMatrixState;
+ }
+ return null;
+ }
+
+ final static class ClippedMatrixState extends Drawable.ConstantState {
+ Drawable mDrawable;
+ Matrix mMatrix;
+ Rect mClipRect;
+
+ private boolean mCheckedConstantState;
+ private boolean mCanConstantState;
+ int mChangingConfigurations;
+
+ ClippedMatrixState(ClippedMatrixState orig, MatrixClippedDrawable owner, Resources res) {
+ if (orig != null) {
+ if (res != null) {
+ mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
+ } else {
+ mDrawable = orig.mDrawable.getConstantState().newDrawable();
+ }
+ mDrawable.setCallback(owner);
+ mCheckedConstantState = mCanConstantState = true;
+ if (orig.mMatrix != null) {
+ mMatrix = new Matrix(orig.mMatrix);
+ }
+ if (orig.mClipRect != null) {
+ mClipRect = new Rect(orig.mClipRect);
+ }
+ }
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return new MatrixClippedDrawable(this, null);
+ }
+
+ @Override
+ public Drawable newDrawable(Resources res) {
+ return new MatrixClippedDrawable(this, res);
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ boolean canConstantState() {
+ if (!mCheckedConstantState) {
+ mCanConstantState = mDrawable.getConstantState() != null;
+ mCheckedConstantState = true;
+ }
+
+ return mCanConstantState;
+ }
+ }
+
+ private MatrixClippedDrawable(ClippedMatrixState state, Resources res) {
+ mClippedMatrixState = new ClippedMatrixState(state, this, res);
+ }
+
+}
diff --git a/core/java/android/transition/MoveImage.java b/core/java/android/transition/MoveImage.java
new file mode 100644
index 0000000..d68e971
--- /dev/null
+++ b/core/java/android/transition/MoveImage.java
@@ -0,0 +1,326 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.RectEvaluator;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroupOverlay;
+import android.view.ViewParent;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+/**
+ * Transitions ImageViews, including size, scaleType, and matrix. The ImageView drawable
+ * must remain the same between both start and end states, but the
+ * {@link ImageView#setScaleType(android.widget.ImageView.ScaleType)} may
+ * differ.
+ */
+public class MoveImage extends Transition {
+ private static final String TAG = "MoveImage";
+ private static final String PROPNAME_MATRIX = "android:moveImage:matrix";
+ private static final String PROPNAME_BOUNDS = "android:moveImage:bounds";
+ private static final String PROPNAME_CLIP = "android:moveImage:clip";
+ private static final String PROPNAME_DRAWABLE = "android:moveImage:drawable";
+
+ private int[] mTempLoc = new int[2];
+
+ private static final String[] sTransitionProperties = {
+ PROPNAME_MATRIX,
+ PROPNAME_BOUNDS,
+ PROPNAME_CLIP,
+ PROPNAME_DRAWABLE,
+ };
+
+ private void captureValues(TransitionValues transitionValues) {
+ View view = transitionValues.view;
+ if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) {
+ return;
+ }
+ Map<String, Object> values = transitionValues.values;
+
+ ViewGroup parent = (ViewGroup) view.getParent();
+ parent.getLocationInWindow(mTempLoc);
+ int paddingLeft = view.getPaddingLeft();
+ int paddingTop = view.getPaddingTop();
+ int paddingRight = view.getPaddingRight();
+ int paddingBottom = view.getPaddingBottom();
+ int left = mTempLoc[0] + paddingLeft + view.getLeft() + Math.round(view.getTranslationX());
+ int top = mTempLoc[1] + paddingTop + view.getTop() + Math.round(view.getTranslationY());
+ int right = left + view.getWidth() - paddingRight - paddingLeft;
+ int bottom = top + view.getHeight() - paddingTop - paddingBottom;
+
+ Rect bounds = new Rect(left, top, right, bottom);
+ values.put(PROPNAME_BOUNDS, bounds);
+ ImageView imageView = (ImageView) view;
+ Matrix matrix = getMatrix(imageView);
+ values.put(PROPNAME_MATRIX, matrix);
+ values.put(PROPNAME_CLIP, findClip(imageView));
+ values.put(PROPNAME_DRAWABLE, imageView.getDrawable());
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public String[] getTransitionProperties() {
+ return sTransitionProperties;
+ }
+
+ /**
+ * Creates an Animator for ImageViews moving, changing dimensions, and/or changing
+ * {@link android.widget.ImageView.ScaleType}.
+ * @param sceneRoot The root of the transition hierarchy.
+ * @param startValues The values for a specific target in the start scene.
+ * @param endValues The values for the target in the end scene.
+ * @return An Animator to move an ImageView or null if the View is not an ImageView,
+ * the Drawable changed, the View is not VISIBLE, or there was no change.
+ */
+ @Override
+ public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
+ TransitionValues endValues) {
+ if (startValues == null || endValues == null
+ || startValues.values.get(PROPNAME_BOUNDS) == null
+ || endValues.values.get(PROPNAME_BOUNDS) == null
+ || startValues.values.get(PROPNAME_DRAWABLE)
+ != endValues.values.get(PROPNAME_DRAWABLE)) {
+ return null;
+ }
+ ArrayList<PropertyValuesHolder> changes = new ArrayList<PropertyValuesHolder>();
+
+ Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX);
+ Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX);
+
+ if (!startMatrix.equals(endMatrix)) {
+ changes.add(PropertyValuesHolder.ofObject(MatrixClippedDrawable.MATRIX_PROPERTY,
+ new MatrixEvaluator(), startMatrix, endMatrix));
+ }
+
+ sceneRoot.getLocationInWindow(mTempLoc);
+ int rootX = mTempLoc[0];
+ int rootY = mTempLoc[1];
+ final ImageView imageView = (ImageView) endValues.view;
+
+ Drawable drawable = imageView.getDrawable();
+
+ Rect startBounds = new Rect((Rect) startValues.values.get(PROPNAME_BOUNDS));
+ Rect endBounds = new Rect((Rect) endValues.values.get(PROPNAME_BOUNDS));
+ startBounds.offset(-rootX, -rootY);
+ endBounds.offset(-rootX, -rootY);
+
+ if (!startBounds.equals(endBounds)) {
+ changes.add(PropertyValuesHolder.ofObject("bounds", new RectEvaluator(new Rect()),
+ startBounds, endBounds));
+ }
+
+ Rect startClip = (Rect) startValues.values.get(PROPNAME_CLIP);
+ Rect endClip = (Rect) endValues.values.get(PROPNAME_CLIP);
+ if (startClip != null || endClip != null) {
+ startClip = nonNullClip(startClip, sceneRoot, rootX, rootY);
+ endClip = nonNullClip(endClip, sceneRoot, rootX, rootY);
+
+ expandClip(startBounds, startMatrix, startClip, endClip);
+ expandClip(endBounds, endMatrix, endClip, startClip);
+ boolean clipped = !startClip.contains(startBounds) || !endClip.contains(endBounds);
+ if (!clipped) {
+ startClip = null;
+ } else if (!startClip.equals(endClip)) {
+ changes.add(PropertyValuesHolder.ofObject(MatrixClippedDrawable.CLIP_PROPERTY,
+ new RectEvaluator(), startClip, endClip));
+ }
+ }
+
+ if (changes.isEmpty()) {
+ return null;
+ }
+
+ drawable = drawable.getConstantState().newDrawable();
+ final MatrixClippedDrawable matrixClippedDrawable = new MatrixClippedDrawable(drawable);
+ matrixClippedDrawable.setMatrix(startMatrix);
+ matrixClippedDrawable.setBounds(startBounds);
+ matrixClippedDrawable.setClipRect(startClip);
+
+ imageView.setVisibility(View.INVISIBLE);
+ final ViewGroupOverlay overlay = sceneRoot.getOverlay();
+ overlay.add(matrixClippedDrawable);
+ ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(matrixClippedDrawable,
+ changes.toArray(new PropertyValuesHolder[changes.size()]));
+
+ AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ imageView.setVisibility(View.VISIBLE);
+ overlay.remove(matrixClippedDrawable);
+ }
+
+ @Override
+ public void onAnimationPause(Animator animation) {
+ imageView.setVisibility(View.VISIBLE);
+ overlay.remove(matrixClippedDrawable);
+ }
+
+ @Override
+ public void onAnimationResume(Animator animation) {
+ imageView.setVisibility(View.INVISIBLE);
+ overlay.add(matrixClippedDrawable);
+ }
+ };
+
+ animator.addListener(listener);
+ animator.addPauseListener(listener);
+
+ return animator;
+ }
+
+ private static Rect nonNullClip(Rect clip, ViewGroup sceneRoot, int rootX, int rootY) {
+ if (clip != null) {
+ clip = new Rect(clip);
+ clip.offset(-rootX, -rootY);
+ } else {
+ clip = new Rect(0, 0, sceneRoot.getWidth(), sceneRoot.getHeight());
+ }
+ return clip;
+ }
+
+ private static void expandClip(Rect bounds, Matrix matrix, Rect clip, Rect otherClip) {
+ RectF boundsF = new RectF(bounds);
+ matrix.mapRect(boundsF);
+ clip.left = expandMinDimension(boundsF.left, clip.left, otherClip.left);
+ clip.top = expandMinDimension(boundsF.top, clip.top, otherClip.top);
+ clip.right = expandMaxDimension(boundsF.right, clip.right, otherClip.right);
+ clip.bottom = expandMaxDimension(boundsF.bottom, clip.bottom, otherClip.bottom);
+ }
+
+ private static int expandMinDimension(float boundsDimension, int clipDimension,
+ int otherClipDimension) {
+ if (clipDimension > boundsDimension) {
+ // Already clipped in that dimension, return the clipped value
+ return clipDimension;
+ }
+ return Math.min(clipDimension, otherClipDimension);
+ }
+
+ private static int expandMaxDimension(float boundsDimension, int clipDimension,
+ int otherClipDimension) {
+ return -expandMinDimension(-boundsDimension, -clipDimension, -otherClipDimension);
+ }
+
+ private static Matrix getMatrix(ImageView imageView) {
+ Drawable drawable = imageView.getDrawable();
+ int drawableWidth = drawable.getIntrinsicWidth();
+ int drawableHeight = drawable.getIntrinsicHeight();
+ ImageView.ScaleType scaleType = imageView.getScaleType();
+ if (drawableWidth <= 0 || drawableHeight <= 0 || scaleType == ImageView.ScaleType.FIT_XY) {
+ return null;
+ }
+ return new Matrix(imageView.getImageMatrix());
+ }
+
+ private Rect findClip(ImageView imageView) {
+ if (imageView.getCropToPadding()) {
+ Rect clip = getClip(imageView);
+ clip.left += imageView.getPaddingLeft();
+ clip.right -= imageView.getPaddingRight();
+ clip.top += imageView.getPaddingTop();
+ clip.bottom -= imageView.getPaddingBottom();
+ return clip;
+ } else {
+ View view = imageView;
+ ViewParent viewParent;
+ while ((viewParent = view.getParent()) instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) viewParent;
+ if (viewGroup.getClipChildren()) {
+ Rect clip = getClip(view);
+ return clip;
+ }
+ view = viewGroup;
+ }
+ }
+ return null;
+ }
+
+ private Rect getClip(View clipView) {
+ Rect clipBounds = clipView.getClipBounds();
+ if (clipBounds == null) {
+ clipBounds = new Rect(clipView.getLeft(), clipView.getTop(),
+ clipView.getRight(), clipView.getBottom());
+ }
+
+ ViewParent parent = clipView.getParent();
+ if (parent instanceof ViewGroup) {
+ ViewGroup parentViewGroup = (ViewGroup) parent;
+ parentViewGroup.getLocationInWindow(mTempLoc);
+ clipBounds.offset(mTempLoc[0], mTempLoc[1]);
+ }
+
+ return clipBounds;
+ }
+
+ @Override
+ public Transition clone() {
+ MoveImage clone = (MoveImage) super.clone();
+ clone.mTempLoc = new int[2];
+ return clone;
+ }
+
+ private static class MatrixEvaluator implements TypeEvaluator<Matrix> {
+ static final Matrix sIdentity = new Matrix();
+ float[] mTempStartValues = new float[9];
+ float[] mTempEndValues = new float[9];
+ Matrix mTempMatrix = new Matrix();
+
+ @Override
+ public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
+ if (startValue == null && endValue == null) {
+ return null;
+ }
+ if (startValue == null) {
+ startValue = sIdentity;
+ } else if (endValue == null) {
+ endValue = sIdentity;
+ }
+ startValue.getValues(mTempStartValues);
+ endValue.getValues(mTempEndValues);
+ for (int i = 0; i < 9; i++) {
+ float diff = mTempEndValues[i] - mTempStartValues[i];
+ mTempEndValues[i] = mTempStartValues[i] + (fraction * diff);
+ }
+ mTempMatrix.setValues(mTempEndValues);
+ return mTempMatrix;
+ }
+ }
+}
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/SidePropagation.java b/core/java/android/transition/SidePropagation.java
new file mode 100644
index 0000000..c331945
--- /dev/null
+++ b/core/java/android/transition/SidePropagation.java
@@ -0,0 +1,165 @@
+/*
+ * 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.transition;
+
+import android.graphics.Rect;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A <code>TransitionPropagation</code> that propagates based on the distance to the side
+ * and, orthogonally, the distance to epicenter. If the transitioning View is visible in
+ * the start of the transition, then it will transition sooner when closer to the side and
+ * later when farther. If the view is not visible in the start of the transition, then
+ * it will transition later when closer to the side and sooner when farther from the edge.
+ * This is the default TransitionPropagation used with {@link android.transition.Slide}.
+ */
+public class SidePropagation extends VisibilityPropagation {
+ private static final String TAG = "SlidePropagation";
+
+ /**
+ * Transition propagates relative to the distance of the left side of the scene.
+ */
+ public static final int LEFT = Slide.LEFT;
+
+ /**
+ * Transition propagates relative to the distance of the top of the scene.
+ */
+ public static final int TOP = Slide.TOP;
+
+ /**
+ * Transition propagates relative to the distance of the right side of the scene.
+ */
+ public static final int RIGHT = Slide.RIGHT;
+
+ /**
+ * Transition propagates relative to the distance of the bottom of the scene.
+ */
+ public static final int BOTTOM = Slide.BOTTOM;
+
+ private float mPropagationSpeed = 4.0f;
+ private int mSide = BOTTOM;
+
+ /**
+ * Sets the side that is used to calculate the transition propagation. If the transitioning
+ * View is visible in the start of the transition, then it will transition sooner when
+ * closer to the side and later when farther. If the view is not visible in the start of
+ * the transition, then it will transition later when closer to the side and sooner when
+ * farther from the edge. The default is {@link #BOTTOM}.
+ *
+ * @param side The side that is used to calculate the transition propagation. Must be one of
+ * {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, or {@link #BOTTOM}.
+ */
+ public void setSide(int side) {
+ mSide = side;
+ }
+
+ /**
+ * Sets the speed at which transition propagation happens, relative to the duration of the
+ * Transition. A <code>propagationSpeed</code> of 1 means that a View centered at the side
+ * set in {@link #setSide(int)} and View centered at the opposite edge will have a difference
+ * in start delay of approximately the duration of the Transition. A speed of 2 means the
+ * start delay difference will be approximately half of the duration of the transition. A
+ * value of 0 is illegal, but negative values will invert the propagation.
+ *
+ * @param propagationSpeed The speed at which propagation occurs, relative to the duration
+ * of the transition. A speed of 4 means it works 4 times as fast
+ * as the duration of the transition. May not be 0.
+ */
+ public void setPropagationSpeed(float propagationSpeed) {
+ if (propagationSpeed == 0) {
+ throw new IllegalArgumentException("propagationSpeed may not be 0");
+ }
+ mPropagationSpeed = propagationSpeed;
+ }
+
+ @Override
+ public long getStartDelay(ViewGroup sceneRoot, Transition transition,
+ TransitionValues startValues, TransitionValues endValues) {
+ if (startValues == null && endValues == null) {
+ return 0;
+ }
+ int directionMultiplier = 1;
+ Rect epicenter = transition.getEpicenter();
+ TransitionValues positionValues;
+ if (endValues == null || getViewVisibility(startValues) == View.VISIBLE) {
+ positionValues = startValues;
+ directionMultiplier = -1;
+ } else {
+ positionValues = endValues;
+ }
+
+ int viewCenterX = getViewX(positionValues);
+ int viewCenterY = getViewY(positionValues);
+
+ int[] loc = new int[2];
+ sceneRoot.getLocationOnScreen(loc);
+ int left = loc[0] + Math.round(sceneRoot.getTranslationX());
+ int top = loc[1] + Math.round(sceneRoot.getTranslationY());
+ int right = left + sceneRoot.getWidth();
+ int bottom = top + sceneRoot.getHeight();
+
+ int epicenterX;
+ int epicenterY;
+ if (epicenter != null) {
+ epicenterX = epicenter.centerX();
+ epicenterY = epicenter.centerY();
+ } else {
+ epicenterX = (left + right) / 2;
+ epicenterY = (top + bottom) / 2;
+ }
+
+ float distance = distance(viewCenterX, viewCenterY, epicenterX, epicenterY,
+ left, top, right, bottom);
+ float maxDistance = getMaxDistance(sceneRoot);
+ float distanceFraction = distance/maxDistance;
+
+ return Math.round(transition.getDuration() * directionMultiplier / mPropagationSpeed
+ * distanceFraction);
+ }
+
+ private int distance(int viewX, int viewY, int epicenterX, int epicenterY,
+ int left, int top, int right, int bottom) {
+ int distance = 0;
+ switch (mSide) {
+ case LEFT:
+ distance = right - viewX + Math.abs(epicenterY - viewY);
+ break;
+ case TOP:
+ distance = bottom - viewY + Math.abs(epicenterX - viewX);
+ break;
+ case RIGHT:
+ distance = viewX - left + Math.abs(epicenterY - viewY);
+ break;
+ case BOTTOM:
+ distance = viewY - top + Math.abs(epicenterX - viewX);
+ break;
+ }
+ return distance;
+ }
+
+ private int getMaxDistance(ViewGroup sceneRoot) {
+ switch (mSide) {
+ case LEFT:
+ case RIGHT:
+ return sceneRoot.getWidth();
+ default:
+ return sceneRoot.getHeight();
+ }
+ }
+}
diff --git a/core/java/android/transition/Slide.java b/core/java/android/transition/Slide.java
index b38973c..0ff8ddd 100644
--- a/core/java/android/transition/Slide.java
+++ b/core/java/android/transition/Slide.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -13,53 +13,240 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.transition;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.Property;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
/**
- * This transition captures the visibility of target objects before and
- * after a scene change and animates any changes by sliding the target
- * objects into or out of place.
- *
- * @hide
+ * This transition tracks changes to the visibility of target views in the
+ * start and end scenes and moves views in or out from one of the edges of the
+ * scene. Visibility is determined by both the
+ * {@link View#setVisibility(int)} state of the view as well as whether it
+ * is parented in the current view hierarchy. Disappearing Views are
+ * limited as described in {@link Visibility#onDisappear(android.view.ViewGroup,
+ * TransitionValues, int, TransitionValues, int)}.
*/
public class Slide extends Visibility {
+ private static final String TAG = "Slide";
- // TODO: Add parameter for sliding factor - it's hard-coded below
+ /**
+ * Move Views in or out of the left edge of the scene.
+ * @see #setSlideEdge(int)
+ */
+ public static final int LEFT = 0;
- private static final TimeInterpolator sAccelerator = new AccelerateInterpolator();
- private static final TimeInterpolator sDecelerator = new DecelerateInterpolator();
+ /**
+ * Move Views in or out of the top edge of the scene.
+ * @see #setSlideEdge(int)
+ */
+ public static final int TOP = 1;
- @Override
- public Animator onAppear(ViewGroup sceneRoot,
- TransitionValues startValues, int startVisibility,
- TransitionValues endValues, int endVisibility) {
- View endView = (endValues != null) ? endValues.view : null;
- endView.setTranslationY(-2 * endView.getHeight());
- ObjectAnimator anim = ObjectAnimator.ofFloat(endView, View.TRANSLATION_Y,
- -2 * endView.getHeight(), 0);
- anim.setInterpolator(sDecelerator);
+ /**
+ * Move Views in or out of the right edge of the scene.
+ * @see #setSlideEdge(int)
+ */
+ public static final int RIGHT = 2;
+
+ /**
+ * Move Views in or out of the bottom edge of the scene. This is the
+ * default slide direction.
+ * @see #setSlideEdge(int)
+ */
+ public static final int BOTTOM = 3;
+
+ private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
+ private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
+
+ private int[] mTempLoc = new int[2];
+ private CalculateSlide mSlideCalculator = sCalculateBottom;
+
+ private interface CalculateSlide {
+ /** Returns the translation value for view when it out of the scene */
+ float getGone(ViewGroup sceneRoot, View view);
+
+ /** Returns the translation value for view when it is in the scene */
+ float getHere(View view);
+
+ /** Returns the property to animate translation */
+ Property<View, Float> getProperty();
+ }
+
+ private static abstract class CalculateSlideHorizontal implements CalculateSlide {
+ @Override
+ public float getHere(View view) {
+ return view.getTranslationX();
+ }
+
+ @Override
+ public Property<View, Float> getProperty() {
+ return View.TRANSLATION_X;
+ }
+ }
+
+ private static abstract class CalculateSlideVertical implements CalculateSlide {
+ @Override
+ public float getHere(View view) {
+ return view.getTranslationY();
+ }
+
+ @Override
+ public Property<View, Float> getProperty() {
+ return View.TRANSLATION_Y;
+ }
+ }
+
+ private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
+ @Override
+ public float getGone(ViewGroup sceneRoot, View view) {
+ return view.getTranslationX() - sceneRoot.getWidth();
+ }
+ };
+
+ private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
+ @Override
+ public float getGone(ViewGroup sceneRoot, View view) {
+ return view.getTranslationY() - sceneRoot.getHeight();
+ }
+ };
+
+ private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
+ @Override
+ public float getGone(ViewGroup sceneRoot, View view) {
+ return view.getTranslationX() + sceneRoot.getWidth();
+ }
+ };
+
+ private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
+ @Override
+ public float getGone(ViewGroup sceneRoot, View view) {
+ return view.getTranslationY() + sceneRoot.getHeight();
+ }
+ };
+
+ /**
+ * Constructor using the default {@link android.transition.Slide#BOTTOM}
+ * slide edge direction.
+ */
+ public Slide() {
+ setSlideEdge(BOTTOM);
+ }
+
+ /**
+ * Constructor using the provided slide edge direction.
+ */
+ public Slide(int slideEdge) {
+ setSlideEdge(slideEdge);
+ }
+
+ /**
+ * Change the edge that Views appear and disappear from.
+ * @param slideEdge The edge of the scene to use for Views appearing and disappearing.
+ */
+ public void setSlideEdge(int slideEdge) {
+ switch (slideEdge) {
+ case LEFT:
+ mSlideCalculator = sCalculateLeft;
+ break;
+ case TOP:
+ mSlideCalculator = sCalculateTop;
+ break;
+ case RIGHT:
+ mSlideCalculator = sCalculateRight;
+ break;
+ case BOTTOM:
+ mSlideCalculator = sCalculateBottom;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid slide direction");
+ }
+ SidePropagation propagation = new SidePropagation();
+ propagation.setSide(slideEdge);
+ setPropagation(propagation);
+ }
+
+ private Animator createAnimation(final View view, Property<View, Float> property,
+ float start, float end, float terminalValue, TimeInterpolator interpolator) {
+ view.setTranslationY(start);
+ if (start == end) {
+ return null;
+ }
+ final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end);
+
+ SlideAnimatorListener listener = new SlideAnimatorListener(view, terminalValue, end);
+ anim.addListener(listener);
+ anim.addPauseListener(listener);
+ anim.setInterpolator(interpolator);
return anim;
}
@Override
- public Animator onDisappear(ViewGroup sceneRoot,
- TransitionValues startValues, int startVisibility,
- TransitionValues endValues, int endVisibility) {
- View startView = (startValues != null) ? startValues.view : null;
- startView.setTranslationY(0);
- ObjectAnimator anim = ObjectAnimator.ofFloat(startView, View.TRANSLATION_Y, 0,
- -2 * startView.getHeight());
- anim.setInterpolator(sAccelerator);
- return anim;
+ public Animator onAppear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues, TransitionValues endValues) {
+ if (endValues == null) {
+ return null;
+ }
+ float end = mSlideCalculator.getHere(view);
+ float start = mSlideCalculator.getGone(sceneRoot, view);
+ return createAnimation(view, mSlideCalculator.getProperty(), start, end, end, sDecelerate);
}
+ @Override
+ public Animator onDisappear(ViewGroup sceneRoot, View view,
+ TransitionValues startValues, TransitionValues endValues) {
+ float start = mSlideCalculator.getHere(view);
+ float end = mSlideCalculator.getGone(sceneRoot, view);
+
+ return createAnimation(view, mSlideCalculator.getProperty(), start, end, start,
+ sAccelerate);
+ }
+
+ private static class SlideAnimatorListener extends AnimatorListenerAdapter {
+ private boolean mCanceled = false;
+ private float mPausedY;
+ private final View mView;
+ private final float mEndY;
+ private final float mTerminalY;
+
+ public SlideAnimatorListener(View view, float terminalY, float endY) {
+ mView = view;
+ mTerminalY = terminalY;
+ mEndY = endY;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ mView.setTranslationY(mTerminalY);
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (!mCanceled) {
+ mView.setTranslationY(mTerminalY);
+ }
+ }
+
+ @Override
+ public void onAnimationPause(Animator animator) {
+ mPausedY = mView.getTranslationY();
+ mView.setTranslationY(mEndY);
+ }
+
+ @Override
+ public void onAnimationResume(Animator animator) {
+ mView.setTranslationY(mPausedY);
+ }
+ }
}
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index da9ba5a..b7ae31e 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -19,15 +19,18 @@ package android.transition;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
+import android.graphics.Rect;
import android.util.ArrayMap;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;
+import android.util.SparseLongArray;
import android.view.SurfaceView;
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;
@@ -59,10 +62,18 @@ import java.util.List;
* <p>Transitions can be declared in XML resource files inside the <code>res/transition</code>
* directory. Transition resources consist of a tag name for one of the Transition
* subclasses along with attributes to define some of the attributes of that transition.
- * For example, here is a minimal resource file that declares a {@link ChangeBounds} transition:</p>
+ * For example, here is a minimal resource file that declares a {@link ChangeBounds} transition:
*
* {@sample development/samples/ApiDemos/res/transition/changebounds.xml ChangeBounds}
*
+ * <p>{@link android.transition.Explode} transition:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/transition/explode.xml Explode}
+ *
+ * <p>{@link android.transition.MoveImage} transition:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/transition/move_image.xml MoveImage}
+ *
* <p>Note that attributes for the transition are not required, just as they are
* optional when declared in code; Transitions created from XML resources will use
* the same defaults as their code-created equivalents. Here is a slightly more
@@ -86,7 +97,8 @@ import java.util.List;
*
* Further information on XML resource descriptions for transitions can be found for
* {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
- * {@link android.R.styleable#TransitionTarget}, and {@link android.R.styleable#Fade}.
+ * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade}, and
+ * {@link android.R.styleable#Slide}.
*
*/
public abstract class Transition implements Cloneable {
@@ -148,6 +160,13 @@ public abstract class Transition implements Cloneable {
// to be run in runAnimators()
ArrayList<Animator> mAnimators = new ArrayList<Animator>();
+ // The function for calculating the Animation start delay.
+ TransitionPropagation mPropagation;
+
+ // The rectangular region for Transitions like Explode and TransitionPropagations
+ // like CircularPropagation
+ EpicenterCallback mEpicenterCallback;
+
/**
* Constructs a Transition object with no target objects. A transition with
* no targets defaults to running on all target objects in the scene hierarchy
@@ -434,6 +453,9 @@ public abstract class Transition implements Cloneable {
endValuesList.add(end);
}
ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+ long minStartDelay = Long.MAX_VALUE;
+ int minAnimator = mAnimators.size();
+ SparseLongArray startDelays = new SparseLongArray();
for (int i = 0; i < startValuesList.size(); ++i) {
TransitionValues start = startValuesList.get(i);
TransitionValues end = endValuesList.get(i);
@@ -496,7 +518,14 @@ public abstract class Transition implements Cloneable {
view = (start != null) ? start.view : null;
}
if (animator != null) {
- AnimationInfo info = new AnimationInfo(view, getName(), infoValues);
+ if (mPropagation != null) {
+ long delay = mPropagation
+ .getStartDelay(sceneRoot, this, start, end);
+ startDelays.put(mAnimators.size(), delay);
+ minStartDelay = Math.min(delay, minStartDelay);
+ }
+ AnimationInfo info = new AnimationInfo(view, getName(),
+ sceneRoot.getWindowId(), infoValues);
runningAnimators.put(animator, info);
mAnimators.add(animator);
}
@@ -504,6 +533,14 @@ public abstract class Transition implements Cloneable {
}
}
}
+ if (minStartDelay != 0) {
+ for (int i = 0; i < startDelays.size(); i++) {
+ int index = startDelays.keyAt(i);
+ Animator animator = mAnimators.get(index);
+ long delay = startDelays.valueAt(i) - minStartDelay + animator.getStartDelay();
+ animator.setStartDelay(delay);
+ }
+ }
}
/**
@@ -563,7 +600,7 @@ public abstract class Transition implements Cloneable {
/**
* This is called internally once all animations have been set up by the
- * transition hierarchy. \
+ * transition hierarchy.
*
* @hide
*/
@@ -1008,6 +1045,7 @@ public abstract class Transition implements Cloneable {
} else {
captureEndValues(values);
}
+ capturePropagationValues(values);
if (start) {
mStartValues.viewValues.put(view, values);
if (id >= 0) {
@@ -1033,6 +1071,7 @@ public abstract class Transition implements Cloneable {
} else {
captureEndValues(values);
}
+ capturePropagationValues(values);
if (start) {
mStartValues.viewValues.put(view, values);
} else {
@@ -1077,6 +1116,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 +1151,33 @@ 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);
+ capturePropagationValues(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 +1239,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 +1270,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 =
@@ -1325,7 +1378,7 @@ public abstract class Transition implements Cloneable {
animator.setDuration(getDuration());
}
if (getStartDelay() >= 0) {
- animator.setStartDelay(getStartDelay());
+ animator.setStartDelay(getStartDelay() + animator.getStartDelay());
}
if (getInterpolator() != null) {
animator.setInterpolator(getInterpolator());
@@ -1458,6 +1511,98 @@ public abstract class Transition implements Cloneable {
return this;
}
+ /**
+ * Sets the callback to use to find the epicenter of a Transition. A null value indicates
+ * that there is no epicenter in the Transition and getEpicenter() will return null.
+ * Transitions like {@link android.transition.Explode} use a point or Rect to orient
+ * the direction of travel. This is called the epicenter of the Transition and is
+ * typically centered on a touched View. The
+ * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
+ * dynamically retrieve the epicenter during a Transition.
+ * @param epicenterCallback The callback to use to find the epicenter of the Transition.
+ */
+ public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
+ mEpicenterCallback = epicenterCallback;
+ }
+
+ /**
+ * Returns the callback used to find the epicenter of the Transition.
+ * Transitions like {@link android.transition.Explode} use a point or Rect to orient
+ * the direction of travel. This is called the epicenter of the Transition and is
+ * typically centered on a touched View. The
+ * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
+ * dynamically retrieve the epicenter during a Transition.
+ * @return the callback used to find the epicenter of the Transition.
+ */
+ public EpicenterCallback getEpicenterCallback() {
+ return mEpicenterCallback;
+ }
+
+ /**
+ * Returns the epicenter as specified by the
+ * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
+ * @return the epicenter as specified by the
+ * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
+ * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback)
+ */
+ public Rect getEpicenter() {
+ if (mEpicenterCallback == null) {
+ return null;
+ }
+ return mEpicenterCallback.getEpicenter(this);
+ }
+
+ /**
+ * Sets the method for determining Animator start delays.
+ * When a Transition affects several Views like {@link android.transition.Explode} or
+ * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
+ * such that the Animator start delay depends on position of the View. The
+ * TransitionPropagation specifies how the start delays are calculated.
+ * @param transitionPropagation The class used to determine the start delay of
+ * Animators created by this Transition. A null value
+ * indicates that no delay should be used.
+ */
+ public void setPropagation(TransitionPropagation transitionPropagation) {
+ mPropagation = transitionPropagation;
+ }
+
+ /**
+ * Returns the {@link android.transition.TransitionPropagation} used to calculate Animator start
+ * delays.
+ * When a Transition affects several Views like {@link android.transition.Explode} or
+ * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
+ * such that the Animator start delay depends on position of the View. The
+ * TransitionPropagation specifies how the start delays are calculated.
+ * @return the {@link android.transition.TransitionPropagation} used to calculate Animator start
+ * delays. This is null by default.
+ */
+ public TransitionPropagation getPropagation() {
+ return mPropagation;
+ }
+
+ /**
+ * Captures TransitionPropagation values for the given view and the
+ * hierarchy underneath it.
+ */
+ void capturePropagationValues(TransitionValues transitionValues) {
+ if (mPropagation != null) {
+ String[] propertyNames = mPropagation.getPropagationProperties();
+ if (propertyNames == null) {
+ return;
+ }
+ boolean containsAll = true;
+ for (int i = 0; i < propertyNames.length; i++) {
+ if (!transitionValues.values.containsKey(propertyNames[i])) {
+ containsAll = false;
+ break;
+ }
+ }
+ if (!containsAll) {
+ mPropagation.captureValues(transitionValues);
+ }
+ }
+ }
+
Transition setSceneRoot(ViewGroup sceneRoot) {
mSceneRoot = sceneRoot;
return this;
@@ -1467,6 +1612,10 @@ public abstract class Transition implements Cloneable {
mCanRemoveViews = canRemoveViews;
}
+ public boolean canRemoveViews() {
+ return mCanRemoveViews;
+ }
+
@Override
public String toString() {
return toString("");
@@ -1629,16 +1778,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;
}
}
@@ -1688,4 +1840,28 @@ public abstract class Transition implements Cloneable {
}
}
+ /**
+ * Class to get the epicenter of Transition. Use
+ * {@link #setEpicenterCallback(android.transition.Transition.EpicenterCallback)} to
+ * set the callback used to calculate the epicenter of the Transition. Override
+ * {@link #getEpicenter()} to return the rectangular region in screen coordinates of
+ * the epicenter of the transition.
+ * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback)
+ */
+ public static abstract class EpicenterCallback {
+
+ /**
+ * Implementers must override to return the epicenter of the Transition in screen
+ * coordinates. Transitions like {@link android.transition.Explode} depend upon
+ * an epicenter for the Transition. In Explode, Views move toward or away from the
+ * center of the epicenter Rect along the vector between the epicenter and the center
+ * of the View appearing and disappearing. Some Transitions, such as
+ * {@link android.transition.Fade} pay no attention to the epicenter.
+ *
+ * @param transition The transition for which the epicenter applies.
+ * @return The Rect region of the epicenter of <code>transition</code> or null if
+ * there is no epicenter.
+ */
+ public abstract Rect getEpicenter(Transition transition);
+ }
}
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index 9f77d5e..f675c6a 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -145,7 +145,13 @@ public class TransitionInflater {
transition = new ChangeBounds();
newTransition = true;
} else if ("slide".equals(name)) {
- transition = new Slide();
+ transition = createSlideTransition(attrs);
+ newTransition = true;
+ } else if ("explode".equals(name)) {
+ transition = new Explode();
+ newTransition = true;
+ } else if ("moveImage".equals(name)) {
+ transition = new MoveImage();
newTransition = true;
} else if ("autoTransition".equals(name)) {
transition = new AutoTransition();
@@ -188,6 +194,15 @@ public class TransitionInflater {
return transition;
}
+ private Slide createSlideTransition(AttributeSet attrs) {
+ TypedArray a = mContext.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.Slide);
+ int edge = a.getInt(com.android.internal.R.styleable.Slide_slideEdge, Slide.BOTTOM);
+ Slide slide = new Slide(edge);
+ a.recycle();
+ return slide;
+ }
+
private void getTargetIds(XmlPullParser parser,
AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException {
@@ -284,25 +299,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/TransitionPropagation.java b/core/java/android/transition/TransitionPropagation.java
new file mode 100644
index 0000000..9a481c2
--- /dev/null
+++ b/core/java/android/transition/TransitionPropagation.java
@@ -0,0 +1,88 @@
+/*
+ * 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.transition;
+
+import android.graphics.Rect;
+import android.view.ViewGroup;
+
+/**
+ * Extend <code>TransitionPropagation</code> to customize start delays for Animators created
+ * in {@link android.transition.Transition#createAnimator(ViewGroup,
+ * TransitionValues, TransitionValues)}. A Transition such as {@link android.transition.Explode}
+ * defaults to using {@link android.transition.CircularPropagation} and Views closer to the
+ * epicenter will move out of the scene later and into the scene sooner than Views farther
+ * from the epicenter, giving the appearance of inertia. With no TransitionPropagation, all
+ * Views will react simultaneously to the start of the transition.
+ *
+ * @see Transition#setPropagation(TransitionPropagation)
+ * @see Transition#getEpicenter()
+ */
+public abstract class TransitionPropagation {
+ /**
+ * Called by Transition to alter the Animator start delay. All start delays will be adjusted
+ * such that the minimum becomes zero.
+ * @param sceneRoot The root of the View hierarchy running the transition.
+ * @param transition The transition that created the Animator
+ * @param startValues The values for a specific target in the start scene.
+ * @param endValues The values for the target in the end scene.
+ * @return A start delay to use with the Animator created by <code>transition</code>. The
+ * delay will be offset by the minimum delay of all <code>TransitionPropagation</code>s
+ * used in the Transition so that the smallest delay will be 0. Returned values may be
+ * negative.
+ */
+ public abstract long getStartDelay(ViewGroup sceneRoot, Transition transition,
+ TransitionValues startValues, TransitionValues endValues);
+
+ /**
+ * Captures the values in the start or end scene for the properties that this
+ * transition propagation monitors. These values are then passed as the startValues
+ * or endValues structure in a later call to
+ * {@link #getStartDelay(ViewGroup, Transition, TransitionValues, TransitionValues)}.
+ * The main concern for an implementation is what the
+ * properties are that the transition cares about and what the values are
+ * for all of those properties. The start and end values will be compared
+ * later during the
+ * {@link #getStartDelay(ViewGroup, Transition, TransitionValues, TransitionValues)}.
+ * method to determine the start delay.
+ *
+ * <p>Subclasses must implement this method. The method should only be called by the
+ * transition system; it is not intended to be called from external classes.</p>
+ *
+ * @param transitionValues The holder for any values that the Transition
+ * wishes to store. Values are stored in the <code>values</code> field
+ * of this TransitionValues object and are keyed from
+ * a String value. For example, to store a view's rotation value,
+ * a transition might call
+ * <code>transitionValues.values.put("appname:transitionname:rotation",
+ * view.getRotation())</code>. The target view will already be stored in
+ * the transitionValues structure when this method is called.
+ */
+ public abstract void captureValues(TransitionValues transitionValues);
+
+ /**
+ * Returns the set of property names stored in the {@link TransitionValues}
+ * object passed into {@link #captureValues(TransitionValues)} that
+ * this transition propagation cares about for the purposes of preventing
+ * duplicate capturing of property values.
+
+ * <p>A <code>TransitionPropagation</code> must override this method to prevent
+ * duplicate capturing of values and must contain at least one </p>
+ *
+ * @return An array of property names as described in the class documentation for
+ * {@link TransitionValues}.
+ */
+ public abstract String[] getPropagationProperties() ;
+}
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index 4545e3b..966b24d 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -17,6 +17,7 @@
package android.transition;
import android.animation.TimeInterpolator;
+import android.graphics.Rect;
import android.util.AndroidRuntimeException;
import android.view.View;
import android.view.ViewGroup;
@@ -315,23 +316,32 @@ public class TransitionSet extends Transition {
}
}
+ @Override
+ void capturePropagationValues(TransitionValues transitionValues) {
+ super.capturePropagationValues(transitionValues);
+ int numTransitions = mTransitions.size();
+ for (int i = 0; i < numTransitions; ++i) {
+ mTransitions.get(i).capturePropagationValues(transitionValues);
+ }
+ }
+
/** @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);
}
}
@@ -365,6 +375,24 @@ public class TransitionSet extends Transition {
}
@Override
+ public void setPropagation(TransitionPropagation propagation) {
+ super.setPropagation(propagation);
+ int numTransitions = mTransitions.size();
+ for (int i = 0; i < numTransitions; ++i) {
+ mTransitions.get(i).setPropagation(propagation);
+ }
+ }
+
+ @Override
+ public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
+ super.setEpicenterCallback(epicenterCallback);
+ int numTransitions = mTransitions.size();
+ for (int i = 0; i < numTransitions; ++i) {
+ mTransitions.get(i).setEpicenterCallback(epicenterCallback);
+ }
+ }
+
+ @Override
String toString(String indent) {
String result = super.toString(indent);
for (int i = 0; i < mTransitions.size(); ++i) {
@@ -383,5 +411,4 @@ public class TransitionSet extends Transition {
}
return clone;
}
-
}
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index 44f92cd..7783b6f 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -17,6 +17,7 @@
package android.transition;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.view.View;
import android.view.ViewGroup;
@@ -29,15 +30,20 @@ import android.view.ViewGroup;
* information to determine the specific animations to run when visibility
* changes occur. Subclasses should implement one or both of the methods
* {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)},
- * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)},
+ * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)} or
+ * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)},
+ * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}.
*/
public abstract class Visibility extends Transition {
private static final String PROPNAME_VISIBILITY = "android:visibility:visibility";
private static final String PROPNAME_PARENT = "android:visibility:parent";
+ private static final String PROPNAME_SCREEN_LOCATION = "android:visibility:screenLocation";
+
private static final String[] sTransitionProperties = {
PROPNAME_VISIBILITY,
PROPNAME_PARENT,
+ PROPNAME_SCREEN_LOCATION,
};
private static class VisibilityInfo {
@@ -58,6 +64,9 @@ public abstract class Visibility extends Transition {
int visibility = transitionValues.view.getVisibility();
transitionValues.values.put(PROPNAME_VISIBILITY, visibility);
transitionValues.values.put(PROPNAME_PARENT, transitionValues.view.getParent());
+ int[] loc = new int[2];
+ transitionValues.view.getLocationOnScreen(loc);
+ transitionValues.values.put(PROPNAME_SCREEN_LOCATION, loc);
}
@Override
@@ -179,8 +188,11 @@ public abstract class Visibility extends Transition {
}
/**
- * The default implementation of this method does nothing. Subclasses
- * should override if they need to create an Animator when targets appear.
+ * The default implementation of this method calls
+ * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)}.
+ * Subclasses should override this method or
+ * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)}.
+ * if they need to create an Animator when targets appear.
* The method should only be called by the Visibility class; it is
* not intended to be called from external classes.
*
@@ -196,15 +208,53 @@ public abstract class Visibility extends Transition {
public Animator onAppear(ViewGroup sceneRoot,
TransitionValues startValues, int startVisibility,
TransitionValues endValues, int endVisibility) {
+ return onAppear(sceneRoot, endValues.view, startValues, endValues);
+ }
+
+ /**
+ * The default implementation of this method returns a null Animator. Subclasses should
+ * override this method to make targets appear with the desired transition. The
+ * method should only be called from
+ * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)}.
+ *
+ * @param sceneRoot The root of the transition hierarchy
+ * @param view The View to make appear. This will be in the target scene's View hierarchy and
+ * will be VISIBLE.
+ * @param startValues The target values in the start scene
+ * @param endValues The target values in the end scene
+ * @return An Animator to be started at the appropriate time in the
+ * overall transition for this scene change. A null value means no animation
+ * should be run.
+ */
+ public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+ TransitionValues endValues) {
return null;
}
/**
- * The default implementation of this method does nothing. Subclasses
- * should override if they need to create an Animator when targets disappear.
+ * Subclasses should override this method or
+ * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}
+ * if they need to create an Animator when targets disappear.
* The method should only be called by the Visibility class; it is
* not intended to be called from external classes.
- *
+ * <p>
+ * The default implementation of this method attempts to find a View to use to call
+ * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)},
+ * based on the situation of the View in the View hierarchy. For example,
+ * if a View was simply removed from its parent, then the View will be added
+ * into a {@link android.view.ViewGroupOverlay} and passed as the <code>view</code>
+ * parameter in {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}.
+ * If a visible View is changed to be {@link View#GONE} or {@link View#INVISIBLE},
+ * then it can be used as the <code>view</code> and the visibility will be changed
+ * to {@link View#VISIBLE} for the duration of the animation. However, if a View
+ * is in a hierarchy which is also altering its visibility, the situation can be
+ * more complicated. In general, if a view that is no longer in the hierarchy in
+ * the end scene still has a parent (so its parent hierarchy was removed, but it
+ * was not removed from its parent), then it will be left alone to avoid side-effects from
+ * improperly removing it from its parent. The only exception to this is if
+ * the previous {@link Scene} was {@link Scene#getSceneForLayout(ViewGroup, int,
+ * android.content.Context) created from a layout resource file}, then it is considered
+ * safe to un-parent the starting scene view in order to make it disappear.</p>
*
* @param sceneRoot The root of the transition hierarchy
* @param startValues The target values in the start scene
@@ -218,6 +268,144 @@ public abstract class Visibility extends Transition {
public Animator onDisappear(ViewGroup sceneRoot,
TransitionValues startValues, int startVisibility,
TransitionValues endValues, int endVisibility) {
+ View startView = (startValues != null) ? startValues.view : null;
+ View endView = (endValues != null) ? endValues.view : null;
+ View overlayView = null;
+ View viewToKeep = null;
+ if (endView == null || endView.getParent() == null) {
+ if (endView != null) {
+ // endView was removed from its parent - add it to the overlay
+ overlayView = endView;
+ } else if (startView != null) {
+ // endView does not exist. Use startView only under certain
+ // conditions, because placing a view in an overlay necessitates
+ // it being removed from its current parent
+ if (startView.getParent() == null) {
+ // no parent - safe to use
+ overlayView = startView;
+ } else if (startView.getParent() instanceof View &&
+ startView.getParent().getParent() == null) {
+ View startParent = (View) startView.getParent();
+ int id = startParent.getId();
+ if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) {
+ // no parent, but its parent is unparented but the parent
+ // hierarchy has been replaced by a new hierarchy with the same id
+ // and it is safe to un-parent startView
+ overlayView = startView;
+ }
+ }
+ }
+ } else {
+ // visibility change
+ if (endVisibility == View.INVISIBLE) {
+ viewToKeep = endView;
+ } else {
+ // Becoming GONE
+ if (startView == endView) {
+ viewToKeep = endView;
+ } else {
+ overlayView = startView;
+ }
+ }
+ }
+ final int finalVisibility = endVisibility;
+ final ViewGroup finalSceneRoot = sceneRoot;
+
+ if (overlayView != null) {
+ // TODO: Need to do this for general case of adding to overlay
+ int[] screenLoc = (int[]) startValues.values.get(PROPNAME_SCREEN_LOCATION);
+ int screenX = screenLoc[0];
+ int screenY = screenLoc[1];
+ int[] loc = new int[2];
+ sceneRoot.getLocationOnScreen(loc);
+ overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft());
+ overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop());
+ sceneRoot.getOverlay().add(overlayView);
+ Animator animator = onDisappear(sceneRoot, overlayView, startValues, endValues);
+ if (animator == null) {
+ sceneRoot.getOverlay().remove(overlayView);
+ } else {
+ final View finalOverlayView = overlayView;
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finalSceneRoot.getOverlay().remove(finalOverlayView);
+ }
+
+ @Override
+ public void onAnimationPause(Animator animation) {
+ finalSceneRoot.getOverlay().remove(finalOverlayView);
+ }
+
+ @Override
+ public void onAnimationResume(Animator animation) {
+ finalSceneRoot.getOverlay().add(finalOverlayView);
+ }
+ });
+ }
+ return animator;
+ }
+
+ if (viewToKeep != null) {
+ viewToKeep.setVisibility(View.VISIBLE);
+ Animator animator = onDisappear(sceneRoot, viewToKeep, startValues, endValues);
+ if (animator == null) {
+ viewToKeep.setVisibility(finalVisibility);
+ } else {
+ final View finalViewToKeep = viewToKeep;
+ animator.addListener(new AnimatorListenerAdapter() {
+ boolean mCanceled = false;
+
+ @Override
+ public void onAnimationPause(Animator animation) {
+ if (!mCanceled) {
+ finalViewToKeep.setVisibility(finalVisibility);
+ }
+ }
+
+ @Override
+ public void onAnimationResume(Animator animation) {
+ if (!mCanceled) {
+ finalViewToKeep.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mCanceled) {
+ finalViewToKeep.setVisibility(finalVisibility);
+ }
+ }
+ });
+ }
+ return animator;
+ }
+ return null;
+ }
+
+ /**
+ * The default implementation of this method returns a null Animator. Subclasses should
+ * override this method to make targets disappear with the desired transition. The
+ * method should only be called from
+ * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)}.
+ *
+ * @param sceneRoot The root of the transition hierarchy
+ * @param view The View to make disappear. This will be in the target scene's View
+ * hierarchy or in an {@link android.view.ViewGroupOverlay} and will be
+ * VISIBLE.
+ * @param startValues The target values in the start scene
+ * @param endValues The target values in the end scene
+ * @return An Animator to be started at the appropriate time in the
+ * overall transition for this scene change. A null value means no animation
+ * should be run.
+ */
+ public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+ TransitionValues endValues) {
return null;
}
}
diff --git a/core/java/android/transition/VisibilityPropagation.java b/core/java/android/transition/VisibilityPropagation.java
new file mode 100644
index 0000000..0326d47
--- /dev/null
+++ b/core/java/android/transition/VisibilityPropagation.java
@@ -0,0 +1,112 @@
+/*
+ * 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.transition;
+
+import android.view.View;
+
+/**
+ * Base class for <code>TransitionPropagation</code>s that care about
+ * View Visibility and the center position of the View.
+ */
+public abstract class VisibilityPropagation extends TransitionPropagation {
+
+ /**
+ * The property key used for {@link android.view.View#getVisibility()}.
+ */
+ private static final String PROPNAME_VISIBILITY = "android:visibilityPropagation:visibility";
+
+ /**
+ * The property key used for the center of the View in screen coordinates. This is an
+ * int[2] with the index 0 taking the x coordinate and index 1 taking the y coordinate.
+ */
+ private static final String PROPNAME_VIEW_CENTER = "android:visibilityPropagation:center";
+
+ private static final String[] VISIBILITY_PROPAGATION_VALUES = {
+ PROPNAME_VISIBILITY,
+ PROPNAME_VIEW_CENTER,
+ };
+
+ @Override
+ public void captureValues(TransitionValues values) {
+ View view = values.view;
+ values.values.put(PROPNAME_VISIBILITY, view.getVisibility());
+ int[] loc = new int[2];
+ view.getLocationOnScreen(loc);
+ loc[0] += Math.round(view.getTranslationX());
+ loc[0] += view.getWidth() / 2;
+ loc[1] += Math.round(view.getTranslationY());
+ loc[1] += view.getHeight() / 2;
+ values.values.put(PROPNAME_VIEW_CENTER, loc);
+ }
+
+ @Override
+ public String[] getPropagationProperties() {
+ return VISIBILITY_PROPAGATION_VALUES;
+ }
+
+ /**
+ * Returns {@link android.view.View#getVisibility()} for the View at the time the values
+ * were captured.
+ * @param values The TransitionValues captured at the start or end of the Transition.
+ * @return {@link android.view.View#getVisibility()} for the View at the time the values
+ * were captured.
+ */
+ public int getViewVisibility(TransitionValues values) {
+ if (values == null) {
+ return View.GONE;
+ }
+ Integer visibility = (Integer) values.values.get(PROPNAME_VISIBILITY);
+ if (visibility == null) {
+ return View.GONE;
+ }
+ return visibility;
+ }
+
+ /**
+ * Returns the View's center x coordinate, relative to the screen, at the time the values
+ * were captured.
+ * @param values The TransitionValues captured at the start or end of the Transition.
+ * @return the View's center x coordinate, relative to the screen, at the time the values
+ * were captured.
+ */
+ public int getViewX(TransitionValues values) {
+ return getViewCoordinate(values, 0);
+ }
+
+ /**
+ * Returns the View's center y coordinate, relative to the screen, at the time the values
+ * were captured.
+ * @param values The TransitionValues captured at the start or end of the Transition.
+ * @return the View's center y coordinate, relative to the screen, at the time the values
+ * were captured.
+ */
+ public int getViewY(TransitionValues values) {
+ return getViewCoordinate(values, 1);
+ }
+
+ private static int getViewCoordinate(TransitionValues values, int coordinateIndex) {
+ if (values == null) {
+ return -1;
+ }
+
+ int[] coordinates = (int[]) values.values.get(PROPNAME_VIEW_CENTER);
+ if (coordinates == null) {
+ return -1;
+ }
+
+ return coordinates[coordinateIndex];
+ }
+}
diff --git a/core/java/android/tv/ITvInputClient.aidl b/core/java/android/tv/ITvInputClient.aidl
new file mode 100644
index 0000000..43be6f0
--- /dev/null
+++ b/core/java/android/tv/ITvInputClient.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+import android.tv.ITvInputSession;
+
+/**
+ * Interface a client of the ITvInputManager implements, to identify itself and receive information
+ * about changes to the state of each TV input service.
+ * @hide
+ */
+oneway interface ITvInputClient {
+ void onSessionCreated(in ComponentName name, IBinder token, int seq);
+ void onAvailabilityChanged(in ComponentName name, boolean isAvailable);
+}
diff --git a/core/java/android/tv/ITvInputManager.aidl b/core/java/android/tv/ITvInputManager.aidl
new file mode 100644
index 0000000..a927dc9
--- /dev/null
+++ b/core/java/android/tv/ITvInputManager.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.tv.ITvInputClient;
+import android.tv.TvInputInfo;
+import android.view.Surface;
+
+/**
+ * Interface to the TV input manager service.
+ * @hide
+ */
+interface ITvInputManager {
+ List<TvInputInfo> getTvInputList(int userId);
+
+ boolean getAvailability(in ITvInputClient client, in ComponentName name, int userId);
+
+ void registerCallback(in ITvInputClient client, in ComponentName name, int userId);
+ void unregisterCallback(in ITvInputClient client, in ComponentName name, int userId);
+
+ void createSession(in ITvInputClient client, in ComponentName name, int seq, int userId);
+ void releaseSession(in IBinder sessionToken, int userId);
+
+ void setSurface(in IBinder sessionToken, in Surface surface, int userId);
+ void setVolume(in IBinder sessionToken, float volume, int userId);
+ void tune(in IBinder sessionToken, in Uri channelUri, int userId);
+}
diff --git a/core/java/android/tv/ITvInputService.aidl b/core/java/android/tv/ITvInputService.aidl
new file mode 100644
index 0000000..d80f286
--- /dev/null
+++ b/core/java/android/tv/ITvInputService.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.tv;
+
+import android.tv.ITvInputServiceCallback;
+import android.tv.ITvInputSession;
+import android.tv.ITvInputSessionCallback;
+
+/**
+ * Top-level interface to a TV input component (implemented in a Service).
+ * @hide
+ */
+oneway interface ITvInputService {
+ void registerCallback(ITvInputServiceCallback callback);
+ void unregisterCallback(in ITvInputServiceCallback callback);
+ void createSession(ITvInputSessionCallback callback);
+}
diff --git a/core/java/android/tv/ITvInputServiceCallback.aidl b/core/java/android/tv/ITvInputServiceCallback.aidl
new file mode 100644
index 0000000..e535c81
--- /dev/null
+++ b/core/java/android/tv/ITvInputServiceCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+
+/**
+ * Helper interface for ITvInputService to allow the TV input to notify the client when its status
+ * has been changed.
+ * @hide
+ */
+oneway interface ITvInputServiceCallback {
+ void onAvailabilityChanged(in ComponentName name, boolean isAvailable);
+}
diff --git a/core/java/android/tv/ITvInputSession.aidl b/core/java/android/tv/ITvInputSession.aidl
new file mode 100644
index 0000000..d379d2d
--- /dev/null
+++ b/core/java/android/tv/ITvInputSession.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.tv;
+
+import android.net.Uri;
+import android.view.Surface;
+
+/**
+ * Sub-interface of ITvInputService which is created per session and has its own context.
+ * @hide
+ */
+oneway interface ITvInputSession {
+ void release();
+
+ void setSurface(in Surface surface);
+ // TODO: Remove this once it becomes irrelevant for applications to handle audio focus. The plan
+ // is to introduce some new concepts that will solve a number of problems in audio policy today.
+ void setVolume(float volume);
+ void tune(in Uri channelUri);
+}
diff --git a/core/java/android/tv/ITvInputSessionCallback.aidl b/core/java/android/tv/ITvInputSessionCallback.aidl
new file mode 100644
index 0000000..a2bd0d7
--- /dev/null
+++ b/core/java/android/tv/ITvInputSessionCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.tv;
+
+import android.tv.ITvInputSession;
+
+/**
+ * Helper interface for ITvInputSession to allow the TV input to notify the system service when a
+ * new session has been created.
+ * @hide
+ */
+oneway interface ITvInputSessionCallback {
+ void onSessionCreated(ITvInputSession session);
+}
diff --git a/core/java/android/tv/ITvInputSessionWrapper.java b/core/java/android/tv/ITvInputSessionWrapper.java
new file mode 100644
index 0000000..66fe5e1
--- /dev/null
+++ b/core/java/android/tv/ITvInputSessionWrapper.java
@@ -0,0 +1,100 @@
+/*
+ * 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.tv;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Message;
+import android.tv.TvInputService.TvInputSessionImpl;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.internal.os.HandlerCaller;
+
+/**
+ * Implements the internal ITvInputSession interface to convert incoming calls on to it back to
+ * calls on the public TvInputSession interface, scheduling them on the main thread of the process.
+ *
+ * @hide
+ */
+public class ITvInputSessionWrapper extends ITvInputSession.Stub implements HandlerCaller.Callback {
+ private static final String TAG = "TvInputSessionWrapper";
+
+ private static final int DO_RELEASE = 1;
+ private static final int DO_SET_SURFACE = 2;
+ private static final int DO_SET_VOLUME = 3;
+ private static final int DO_TUNE = 4;
+
+ private TvInputSessionImpl mTvInputSession;
+ private final HandlerCaller mCaller;
+
+ public ITvInputSessionWrapper(Context context, TvInputSessionImpl session) {
+ mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
+ mTvInputSession = session;
+ }
+
+ @Override
+ public void executeMessage(Message msg) {
+ if (mTvInputSession == null) {
+ return;
+ }
+
+ switch (msg.what) {
+ case DO_RELEASE: {
+ mTvInputSession.release();
+ mTvInputSession = null;
+ return;
+ }
+ case DO_SET_SURFACE: {
+ mTvInputSession.setSurface((Surface) msg.obj);
+ return;
+ }
+ case DO_SET_VOLUME: {
+ mTvInputSession.setVolume((Float) msg.obj);
+ return;
+ }
+ case DO_TUNE: {
+ mTvInputSession.tune((Uri) msg.obj);
+ return;
+ }
+ default: {
+ Log.w(TAG, "Unhandled message code: " + msg.what);
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void release() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
+ }
+
+ @Override
+ public void setSurface(Surface surface) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
+ }
+
+ @Override
+ public final void setVolume(float volume) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VOLUME, volume));
+ }
+
+ @Override
+ public void tune(Uri channelUri) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TUNE, channelUri));
+ }
+}
diff --git a/core/java/android/tv/TvInputInfo.aidl b/core/java/android/tv/TvInputInfo.aidl
new file mode 100644
index 0000000..abc4b47
--- /dev/null
+++ b/core/java/android/tv/TvInputInfo.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.tv;
+
+parcelable TvInputInfo;
diff --git a/core/java/android/tv/TvInputInfo.java b/core/java/android/tv/TvInputInfo.java
new file mode 100644
index 0000000..90e4177
--- /dev/null
+++ b/core/java/android/tv/TvInputInfo.java
@@ -0,0 +1,152 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to specify meta information of a TV input.
+ */
+public final class TvInputInfo implements Parcelable {
+ private final ResolveInfo mService;
+ private final String mId;
+
+ /**
+ * Constructor.
+ *
+ * @param service The ResolveInfo returned from the package manager about this TV input service.
+ * @hide
+ */
+ public TvInputInfo(ResolveInfo service) {
+ mService = service;
+ ServiceInfo si = service.serviceInfo;
+ mId = new ComponentName(si.packageName, si.name).flattenToShortString();
+ }
+
+ /**
+ * Returns a unique ID for this TV input. The ID is generated from the package and class name
+ * implementing the TV input service.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the .apk package that implements this TV input service.
+ */
+ public String getPackageName() {
+ return mService.serviceInfo.packageName;
+ }
+
+ /**
+ * Returns the class name of the service component that implements this TV input service.
+ */
+ public String getServiceName() {
+ return mService.serviceInfo.name;
+ }
+
+ /**
+ * Returns the component of the service that implements this TV input.
+ */
+ public ComponentName getComponent() {
+ return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name);
+ }
+
+ /**
+ * Loads the user-displayed label for this TV input service.
+ *
+ * @param pm Supplies a PackageManager used to load the TV input's resources.
+ * @return Returns a CharSequence containing the TV input's label. If the TV input does not have
+ * a label, its name is returned.
+ */
+ public CharSequence loadLabel(PackageManager pm) {
+ return mService.loadLabel(pm);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return mId.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof TvInputInfo)) {
+ return false;
+ }
+
+ TvInputInfo obj = (TvInputInfo) o;
+ return mId.equals(obj.mId)
+ && mService.serviceInfo.packageName.equals(obj.mService.serviceInfo.packageName)
+ && mService.serviceInfo.name.equals(obj.mService.serviceInfo.name);
+ }
+
+ @Override
+ public String toString() {
+ return "TvInputInfo{id=" + mId
+ + ", pkg=" + mService.serviceInfo.packageName
+ + ", service=" + mService.serviceInfo.name + "}";
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mId);
+ mService.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ *
+ * @hide
+ */
+ public static final Parcelable.Creator<TvInputInfo> CREATOR =
+ new Parcelable.Creator<TvInputInfo>() {
+ @Override
+ public TvInputInfo createFromParcel(Parcel in) {
+ return new TvInputInfo(in);
+ }
+
+ @Override
+ public TvInputInfo[] newArray(int size) {
+ return new TvInputInfo[size];
+ }
+ };
+
+ private TvInputInfo(Parcel in) {
+ mId = in.readString();
+ mService = ResolveInfo.CREATOR.createFromParcel(in);
+ }
+}
diff --git a/core/java/android/tv/TvInputManager.java b/core/java/android/tv/TvInputManager.java
new file mode 100644
index 0000000..4cf2b35
--- /dev/null
+++ b/core/java/android/tv/TvInputManager.java
@@ -0,0 +1,392 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Surface;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Central system API to the overall TV input framework (TIF) architecture, which arbitrates
+ * interaction between applications and the selected TV inputs.
+ */
+public final class TvInputManager {
+ private static final String TAG = "TvInputManager";
+
+ private final ITvInputManager mService;
+
+ // A mapping from an input to the list of its TvInputListenerRecords.
+ private final Map<ComponentName, List<TvInputListenerRecord>> mTvInputListenerRecordsMap =
+ new HashMap<ComponentName, List<TvInputListenerRecord>>();
+
+ // A mapping from the sequence number of a session to its SessionCreateCallbackRecord.
+ private final SparseArray<SessionCreateCallbackRecord> mSessionCreateCallbackRecordMap =
+ new SparseArray<SessionCreateCallbackRecord>();
+
+ // A sequence number for the next session to be created. Should be protected by a lock
+ // {@code mSessionCreateCallbackRecordMap}.
+ private int mNextSeq;
+
+ private final ITvInputClient mClient;
+
+ private final int mUserId;
+
+ /**
+ * Interface used to receive the created session.
+ */
+ public interface SessionCreateCallback {
+ /**
+ * This is called after {@link TvInputManager#createSession} has been processed.
+ *
+ * @param session A {@link TvInputManager.Session} instance created. This can be
+ * {@code null} if the creation request failed.
+ */
+ void onSessionCreated(Session session);
+ }
+
+ private static final class SessionCreateCallbackRecord {
+ private final SessionCreateCallback mSessionCreateCallback;
+ private final Handler mHandler;
+
+ public SessionCreateCallbackRecord(SessionCreateCallback sessionCreateCallback,
+ Handler handler) {
+ mSessionCreateCallback = sessionCreateCallback;
+ mHandler = handler;
+ }
+
+ public void postSessionCreated(final Session session) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCreateCallback.onSessionCreated(session);
+ }
+ });
+ }
+ }
+
+ /**
+ * Interface used to monitor status of the TV input.
+ */
+ public abstract static class TvInputListener {
+ /**
+ * This is called when the availability status of a given TV input is changed.
+ *
+ * @param name {@link ComponentName} of {@link android.app.Service} that implements the
+ * given TV input.
+ * @param isAvailable {@code true} if the given TV input is available to show TV programs.
+ * {@code false} otherwise.
+ */
+ public void onAvailabilityChanged(ComponentName name, boolean isAvailable) {
+ }
+ }
+
+ private static final class TvInputListenerRecord {
+ private final TvInputListener mListener;
+ private final Handler mHandler;
+
+ public TvInputListenerRecord(TvInputListener listener, Handler handler) {
+ mListener = listener;
+ mHandler = handler;
+ }
+
+ public TvInputListener getListener() {
+ return mListener;
+ }
+
+ public void postAvailabilityChanged(final ComponentName name, final boolean isAvailable) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onAvailabilityChanged(name, isAvailable);
+ }
+ });
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public TvInputManager(ITvInputManager service, int userId) {
+ mService = service;
+ mUserId = userId;
+ mClient = new ITvInputClient.Stub() {
+ @Override
+ public void onSessionCreated(ComponentName name, IBinder token, int seq) {
+ synchronized (mSessionCreateCallbackRecordMap) {
+ SessionCreateCallbackRecord record = mSessionCreateCallbackRecordMap.get(seq);
+ mSessionCreateCallbackRecordMap.delete(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for " + token);
+ return;
+ }
+ Session session = null;
+ if (token != null) {
+ session = new Session(name, token, mService, mUserId);
+ }
+ record.postSessionCreated(session);
+ }
+ }
+
+ @Override
+ public void onAvailabilityChanged(ComponentName name, boolean isAvailable) {
+ synchronized (mTvInputListenerRecordsMap) {
+ List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
+ if (records == null) {
+ // Silently ignore - no listener is registered yet.
+ return;
+ }
+ int recordsCount = records.size();
+ for (int i = 0; i < recordsCount; i++) {
+ records.get(i).postAvailabilityChanged(name, isAvailable);
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns the complete list of TV inputs on the system.
+ *
+ * @return List of {@link TvInputInfo} for each TV input that describes its meta information.
+ */
+ public List<TvInputInfo> getTvInputList() {
+ try {
+ return mService.getTvInputList(mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Returns the availability of a given TV input.
+ *
+ * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV
+ * input.
+ * @throws IllegalArgumentException if the argument is {@code null}.
+ * @throws IllegalStateException If there is no {@link TvInputListener} registered on the given
+ * TV input.
+ */
+ public boolean getAvailability(ComponentName name) {
+ if (name == null) {
+ throw new IllegalArgumentException("name cannot be null");
+ }
+ synchronized (mTvInputListenerRecordsMap) {
+ List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
+ if (records == null || records.size() == 0) {
+ throw new IllegalStateException("At least one listener should be registered.");
+ }
+ }
+ try {
+ return mService.getAvailability(mClient, name, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Registers a {@link TvInputListener} for a given TV input.
+ *
+ * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV
+ * input.
+ * @param listener a listener used to monitor status of the given TV input.
+ * @param handler a {@link Handler} that the status change will be delivered to.
+ * @throws IllegalArgumentException if any of the arguments is {@code null}.
+ */
+ public void registerListener(ComponentName name, TvInputListener listener, Handler handler) {
+ if (name == null) {
+ throw new IllegalArgumentException("name cannot be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener cannot be null");
+ }
+ if (handler == null) {
+ throw new IllegalArgumentException("handler cannot be null");
+ }
+ synchronized (mTvInputListenerRecordsMap) {
+ List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
+ if (records == null) {
+ records = new ArrayList<TvInputListenerRecord>();
+ mTvInputListenerRecordsMap.put(name, records);
+ try {
+ mService.registerCallback(mClient, name, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ records.add(new TvInputListenerRecord(listener, handler));
+ }
+ }
+
+ /**
+ * Unregisters the existing {@link TvInputListener} for a given TV input.
+ *
+ * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV
+ * input.
+ * @param listener the existing listener to remove for the given TV input.
+ * @throws IllegalArgumentException if any of the arguments is {@code null}.
+ */
+ public void unregisterListener(ComponentName name, final TvInputListener listener) {
+ if (name == null) {
+ throw new IllegalArgumentException("name cannot be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener cannot be null");
+ }
+ synchronized (mTvInputListenerRecordsMap) {
+ List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
+ if (records == null) {
+ Log.e(TAG, "No listener found for " + name.getClassName());
+ return;
+ }
+ for (Iterator<TvInputListenerRecord> it = records.iterator(); it.hasNext();) {
+ TvInputListenerRecord record = it.next();
+ if (record.getListener() == listener) {
+ it.remove();
+ }
+ }
+ if (records.isEmpty()) {
+ try {
+ mService.unregisterCallback(mClient, name, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ } finally {
+ mTvInputListenerRecordsMap.remove(name);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a {@link Session} for a given TV input.
+ * <p>
+ * The number of sessions that can be created at the same time is limited by the capability of
+ * the given TV input.
+ * </p>
+ *
+ * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV
+ * input.
+ * @param callback a callback used to receive the created session.
+ * @param handler a {@link Handler} that the session creation will be delivered to.
+ * @throws IllegalArgumentException if any of the arguments is {@code null}.
+ */
+ public void createSession(ComponentName name, final SessionCreateCallback callback,
+ Handler handler) {
+ if (name == null) {
+ throw new IllegalArgumentException("name cannot be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+ if (handler == null) {
+ throw new IllegalArgumentException("handler cannot be null");
+ }
+ SessionCreateCallbackRecord record = new SessionCreateCallbackRecord(callback, handler);
+ synchronized (mSessionCreateCallbackRecordMap) {
+ int seq = mNextSeq++;
+ mSessionCreateCallbackRecordMap.put(seq, record);
+ try {
+ mService.createSession(mClient, name, seq, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /** The Session provides the per-session functionality of TV inputs. */
+ public static final class Session {
+ private final ITvInputManager mService;
+ private final IBinder mToken;
+ private final int mUserId;
+
+ /** @hide */
+ private Session(ComponentName name, IBinder token, ITvInputManager service, int userId) {
+ mToken = token;
+ mService = service;
+ mUserId = userId;
+ }
+
+ /**
+ * Releases this session.
+ */
+ public void release() {
+ try {
+ mService.releaseSession(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sets the {@link android.view.Surface} for this session.
+ *
+ * @param surface A {@link android.view.Surface} used to render video.
+ */
+ public void setSurface(Surface surface) {
+ // surface can be null.
+ try {
+ mService.setSurface(mToken, surface, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sets the relative volume of this session to handle a change of audio focus.
+ *
+ * @param volume A volume value between 0.0f to 1.0f.
+ * @throws IllegalArgumentException if the volume value is out of range.
+ */
+ public void setVolume(float volume) {
+ try {
+ if (volume < 0.0f || volume > 1.0f) {
+ throw new IllegalArgumentException("volume should be between 0.0f and 1.0f");
+ }
+ mService.setVolume(mToken, volume, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tunes to a given channel.
+ *
+ * @param channelUri The URI of a channel.
+ * @throws IllegalArgumentException if the argument is {@code null}.
+ */
+ public void tune(Uri channelUri) {
+ if (channelUri == null) {
+ throw new IllegalArgumentException("channelUri cannot be null");
+ }
+ try {
+ mService.tune(mToken, channelUri, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java
new file mode 100644
index 0000000..d7f6c32
--- /dev/null
+++ b/core/java/android/tv/TvInputService.java
@@ -0,0 +1,237 @@
+/*
+ * 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.tv;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * A base class for implementing television input service.
+ */
+public abstract class TvInputService extends Service {
+ // STOPSHIP: Turn debugging off.
+ private static final boolean DEBUG = true;
+ private static final String TAG = "TvInputService";
+
+ /**
+ * This is the interface name that a service implementing a TV input should say that it support
+ * -- that is, this is the action it uses for its intent filter. To be supported, the service
+ * must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that
+ * other applications cannot abuse it.
+ */
+ public static final String SERVICE_INTERFACE = "android.tv.TvInputService";
+
+ private ComponentName mComponentName;
+ private final Handler mHandler = new ServiceHandler();
+ private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
+ new RemoteCallbackList<ITvInputServiceCallback>();
+ private boolean mAvailable;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mComponentName = new ComponentName(getPackageName(), getClass().getName());
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return new ITvInputService.Stub() {
+ @Override
+ public void registerCallback(ITvInputServiceCallback cb) {
+ if (cb != null) {
+ mCallbacks.register(cb);
+ // The first time a callback is registered, the service needs to report its
+ // availability status so that the system can know its initial value.
+ try {
+ cb.onAvailabilityChanged(mComponentName, mAvailable);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onAvailabilityChanged", e);
+ }
+ }
+ }
+
+ @Override
+ public void unregisterCallback(ITvInputServiceCallback cb) {
+ if (cb != null) {
+ mCallbacks.unregister(cb);
+ }
+ }
+
+ @Override
+ public void createSession(ITvInputSessionCallback cb) {
+ if (cb != null) {
+ mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, cb).sendToTarget();
+ }
+ }
+ };
+ }
+
+ /**
+ * Convenience method to notify an availability change of this TV input service.
+ *
+ * @param available {@code true} if the input service is available to show TV programs.
+ */
+ public final void setAvailable(boolean available) {
+ if (available != mAvailable) {
+ mAvailable = available;
+ mHandler.obtainMessage(ServiceHandler.DO_BROADCAST_AVAILABILITY_CHANGE, available)
+ .sendToTarget();
+ }
+ }
+
+ /**
+ * Get the number of callbacks that are registered.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public final int getRegisteredCallbackCount() {
+ return mCallbacks.getRegisteredCallbackCount();
+ }
+
+ /**
+ * Returns a concrete implementation of {@link TvInputSessionImpl}.
+ * <p>
+ * May return {@code null} if this TV input service fails to create a session for some reason.
+ * </p>
+ */
+ public abstract TvInputSessionImpl onCreateSession();
+
+ /**
+ * Base class for derived classes to implement to provide {@link TvInputManager.Session}.
+ */
+ public abstract static class TvInputSessionImpl {
+ /**
+ * Called when the session is released.
+ */
+ public abstract void onRelease();
+
+ /**
+ * Sets the {@link Surface} for the current input session on which the TV input renders
+ * video.
+ *
+ * @param surface {@link Surface} an application passes to this TV input session.
+ * @return {@code true} if the surface was set, {@code false} otherwise.
+ */
+ public abstract boolean onSetSurface(Surface surface);
+
+ /**
+ * Sets the relative volume of the current TV input session to handle the change of audio
+ * focus by setting.
+ *
+ * @param volume Volume scale from 0.0 to 1.0.
+ */
+ public abstract void onSetVolume(float volume);
+
+ /**
+ * Tunes to a given channel.
+ *
+ * @param channelUri The URI of the channel.
+ * @return {@code true} the tuning was successful, {@code false} otherwise.
+ */
+ public abstract boolean onTune(Uri channelUri);
+
+ /**
+ * This method is called when the application would like to stop using the current input
+ * session.
+ */
+ void release() {
+ onRelease();
+ }
+
+ /**
+ * Calls {@link onSetSurface}.
+ */
+ void setSurface(Surface surface) {
+ onSetSurface(surface);
+ // TODO: Handle failure.
+ }
+
+ /**
+ * Calls {@link onSetVolume}.
+ */
+ void setVolume(float volume) {
+ onSetVolume(volume);
+ }
+
+ /**
+ * Calls {@link onTune}.
+ */
+ void tune(Uri channelUri) {
+ onTune(channelUri);
+ // TODO: Handle failure.
+ }
+ }
+
+ private final class ServiceHandler extends Handler {
+ private static final int DO_CREATE_SESSION = 1;
+ private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2;
+
+ @Override
+ public final void handleMessage(Message msg) {
+ switch (msg.what) {
+ case DO_CREATE_SESSION: {
+ ITvInputSessionCallback cb = (ITvInputSessionCallback) msg.obj;
+ try {
+ TvInputSessionImpl sessionImpl = onCreateSession();
+ if (sessionImpl == null) {
+ // Failed to create a session.
+ cb.onSessionCreated(null);
+ return;
+ }
+ ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
+ sessionImpl);
+ cb.onSessionCreated(stub);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onSessionCreated");
+ }
+ return;
+ }
+ case DO_BROADCAST_AVAILABILITY_CHANGE: {
+ boolean isAvailable = (Boolean) msg.obj;
+ int n = mCallbacks.beginBroadcast();
+ try {
+ for (int i = 0; i < n; i++) {
+ mCallbacks.getBroadcastItem(i).onAvailabilityChanged(mComponentName,
+ isAvailable);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unexpected exception", e);
+ } finally {
+ mCallbacks.finishBroadcast();
+ }
+ return;
+ }
+ default: {
+ Log.w(TAG, "Unhandled message code: " + msg.what);
+ return;
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java
index df1d4cd..9a0b7fc 100644
--- a/core/java/android/util/ArrayMap.java
+++ b/core/java/android/util/ArrayMap.java
@@ -16,6 +16,8 @@
package android.util;
+import libcore.util.EmptyArray;
+
import java.util.Collection;
import java.util.Map;
import java.util.Set;
@@ -234,8 +236,8 @@ public final class ArrayMap<K, V> implements Map<K, V> {
* will grow once items are added to it.
*/
public ArrayMap() {
- mHashes = ContainerHelpers.EMPTY_INTS;
- mArray = ContainerHelpers.EMPTY_OBJECTS;
+ mHashes = EmptyArray.INT;
+ mArray = EmptyArray.OBJECT;
mSize = 0;
}
@@ -244,8 +246,8 @@ public final class ArrayMap<K, V> implements Map<K, V> {
*/
public ArrayMap(int capacity) {
if (capacity == 0) {
- mHashes = ContainerHelpers.EMPTY_INTS;
- mArray = ContainerHelpers.EMPTY_OBJECTS;
+ mHashes = EmptyArray.INT;
+ mArray = EmptyArray.OBJECT;
} else {
allocArrays(capacity);
}
@@ -253,8 +255,8 @@ public final class ArrayMap<K, V> implements Map<K, V> {
}
private ArrayMap(boolean immutable) {
- mHashes = EMPTY_IMMUTABLE_INTS;
- mArray = ContainerHelpers.EMPTY_OBJECTS;
+ mHashes = EmptyArray.INT;
+ mArray = EmptyArray.OBJECT;
mSize = 0;
}
@@ -275,8 +277,8 @@ public final class ArrayMap<K, V> implements Map<K, V> {
public void clear() {
if (mSize > 0) {
freeArrays(mHashes, mArray, mSize);
- mHashes = ContainerHelpers.EMPTY_INTS;
- mArray = ContainerHelpers.EMPTY_OBJECTS;
+ mHashes = EmptyArray.INT;
+ mArray = EmptyArray.OBJECT;
mSize = 0;
}
}
@@ -540,8 +542,8 @@ public final class ArrayMap<K, V> implements Map<K, V> {
// Now empty.
if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
freeArrays(mHashes, mArray, mSize);
- mHashes = ContainerHelpers.EMPTY_INTS;
- mArray = ContainerHelpers.EMPTY_OBJECTS;
+ mHashes = EmptyArray.INT;
+ mArray = EmptyArray.OBJECT;
mSize = 0;
} else {
if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java
index 3c695e9..9d4b720 100644
--- a/core/java/android/util/ArraySet.java
+++ b/core/java/android/util/ArraySet.java
@@ -16,6 +16,8 @@
package android.util;
+import libcore.util.EmptyArray;
+
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
@@ -222,8 +224,8 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
* will grow once items are added to it.
*/
public ArraySet() {
- mHashes = ContainerHelpers.EMPTY_INTS;
- mArray = ContainerHelpers.EMPTY_OBJECTS;
+ mHashes = EmptyArray.INT;
+ mArray = EmptyArray.OBJECT;
mSize = 0;
}
@@ -232,8 +234,8 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
*/
public ArraySet(int capacity) {
if (capacity == 0) {
- mHashes = ContainerHelpers.EMPTY_INTS;
- mArray = ContainerHelpers.EMPTY_OBJECTS;
+ mHashes = EmptyArray.INT;
+ mArray = EmptyArray.OBJECT;
} else {
allocArrays(capacity);
}
@@ -258,8 +260,8 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
public void clear() {
if (mSize != 0) {
freeArrays(mHashes, mArray, mSize);
- mHashes = ContainerHelpers.EMPTY_INTS;
- mArray = ContainerHelpers.EMPTY_OBJECTS;
+ mHashes = EmptyArray.INT;
+ mArray = EmptyArray.OBJECT;
mSize = 0;
}
}
@@ -413,8 +415,8 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
// Now empty.
if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
freeArrays(mHashes, mArray, mSize);
- mHashes = ContainerHelpers.EMPTY_INTS;
- mArray = ContainerHelpers.EMPTY_OBJECTS;
+ mHashes = EmptyArray.INT;
+ mArray = EmptyArray.OBJECT;
mSize = 0;
} else {
if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
diff --git a/core/java/android/util/ContainerHelpers.java b/core/java/android/util/ContainerHelpers.java
index 624c4bd..4e5fefb 100644
--- a/core/java/android/util/ContainerHelpers.java
+++ b/core/java/android/util/ContainerHelpers.java
@@ -17,10 +17,6 @@
package android.util;
class ContainerHelpers {
- static final boolean[] EMPTY_BOOLEANS = new boolean[0];
- static final int[] EMPTY_INTS = new int[0];
- static final long[] EMPTY_LONGS = new long[0];
- static final Object[] EMPTY_OBJECTS = new Object[0];
// This is Arrays.binarySearch(), but doesn't do any argument validation.
static int binarySearch(int[] array, int size, int value) {
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..54a6882
--- /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;
+import libcore.util.EmptyArray;
+
+/**
+ * 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 = EmptyArray.LONG;
+ } else {
+ mValues = ArrayUtils.newUnpaddedLongArray(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 = ArrayUtils.newUnpaddedLongArray(newCapacity);
+ System.arraycopy(mValues, 0, newValues, 0, currentSize);
+ mValues = newValues;
+ }
+ }
+
+ /**
+ * Removes all values from this array.
+ */
+ public void clear() {
+ mSize = 0;
+ }
+
+ @Override
+ 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 + 1, mValues, index, mSize - index - 1);
+ mSize--;
+ }
+
+ /**
+ * Returns the number of values in this array.
+ */
+ public int size() {
+ return mSize;
+ }
+}
diff --git a/core/java/android/util/LongSparseArray.java b/core/java/android/util/LongSparseArray.java
index dab853a..6b45ff4 100644
--- a/core/java/android/util/LongSparseArray.java
+++ b/core/java/android/util/LongSparseArray.java
@@ -17,6 +17,9 @@
package android.util;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
/**
* SparseArray mapping longs to Objects. Unlike a normal array of Objects,
@@ -70,12 +73,11 @@ public class LongSparseArray<E> implements Cloneable {
*/
public LongSparseArray(int initialCapacity) {
if (initialCapacity == 0) {
- mKeys = ContainerHelpers.EMPTY_LONGS;
- mValues = ContainerHelpers.EMPTY_OBJECTS;
+ mKeys = EmptyArray.LONG;
+ mValues = EmptyArray.OBJECT;
} else {
- initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
- mKeys = new long[initialCapacity];
- mValues = new Object[initialCapacity];
+ mKeys = ArrayUtils.newUnpaddedLongArray(initialCapacity);
+ mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
}
mSize = 0;
}
@@ -202,28 +204,8 @@ public class LongSparseArray<E> implements Cloneable {
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
- if (mSize >= mKeys.length) {
- int n = ArrayUtils.idealLongArraySize(mSize + 1);
-
- long[] nkeys = new long[n];
- Object[] nvalues = new Object[n];
-
- // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
- System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
- System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
-
- mKeys = nkeys;
- mValues = nvalues;
- }
-
- if (mSize - i != 0) {
- // Log.e("SparseArray", "move " + (mSize - i));
- System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
- System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
- }
-
- mKeys[i] = key;
- mValues[i] = value;
+ mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
+ mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
@@ -353,24 +335,9 @@ public class LongSparseArray<E> implements Cloneable {
gc();
}
- int pos = mSize;
- if (pos >= mKeys.length) {
- int n = ArrayUtils.idealLongArraySize(pos + 1);
-
- long[] nkeys = new long[n];
- Object[] nvalues = new Object[n];
-
- // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
- System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
- System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
-
- mKeys = nkeys;
- mValues = nvalues;
- }
-
- mKeys[pos] = key;
- mValues[pos] = value;
- mSize = pos + 1;
+ mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
+ mValues = GrowingArrayUtils.append(mValues, mSize, value);
+ mSize++;
}
/**
diff --git a/core/java/android/util/LongSparseLongArray.java b/core/java/android/util/LongSparseLongArray.java
index 6654899..a361457 100644
--- a/core/java/android/util/LongSparseLongArray.java
+++ b/core/java/android/util/LongSparseLongArray.java
@@ -17,8 +17,9 @@
package android.util;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
-import java.util.Arrays;
+import libcore.util.EmptyArray;
/**
* Map of {@code long} to {@code long}. Unlike a normal array of longs, there
@@ -64,12 +65,11 @@ public class LongSparseLongArray implements Cloneable {
*/
public LongSparseLongArray(int initialCapacity) {
if (initialCapacity == 0) {
- mKeys = ContainerHelpers.EMPTY_LONGS;
- mValues = ContainerHelpers.EMPTY_LONGS;
+ mKeys = EmptyArray.LONG;
+ mValues = EmptyArray.LONG;
} else {
- initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
- mKeys = new long[initialCapacity];
- mValues = new long[initialCapacity];
+ mKeys = ArrayUtils.newUnpaddedLongArray(initialCapacity);
+ mValues = new long[mKeys.length];
}
mSize = 0;
}
@@ -142,17 +142,8 @@ public class LongSparseLongArray implements Cloneable {
} else {
i = ~i;
- if (mSize >= mKeys.length) {
- growKeyAndValueArrays(mSize + 1);
- }
-
- if (mSize - i != 0) {
- System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
- System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
- }
-
- mKeys[i] = key;
- mValues[i] = value;
+ mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
+ mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
@@ -236,27 +227,9 @@ public class LongSparseLongArray implements Cloneable {
return;
}
- int pos = mSize;
- if (pos >= mKeys.length) {
- growKeyAndValueArrays(pos + 1);
- }
-
- mKeys[pos] = key;
- mValues[pos] = value;
- mSize = pos + 1;
- }
-
- private void growKeyAndValueArrays(int minNeededSize) {
- int n = ArrayUtils.idealLongArraySize(minNeededSize);
-
- long[] nkeys = new long[n];
- long[] nvalues = new long[n];
-
- System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
- System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
-
- mKeys = nkeys;
- mValues = nvalues;
+ mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
+ mValues = GrowingArrayUtils.append(mValues, mSize, value);
+ mSize++;
}
/**
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/Patterns.java b/core/java/android/util/Patterns.java
index 0f8da44..13cc88b 100644
--- a/core/java/android/util/Patterns.java
+++ b/core/java/android/util/Patterns.java
@@ -28,7 +28,12 @@ public class Patterns {
* List accurate as of 2011/07/18. List taken from:
* http://data.iana.org/TLD/tlds-alpha-by-domain.txt
* This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py
+ *
+ * @deprecated Due to the recent profileration of gTLDs, this API is
+ * expected to become out-of-date very quickly. Therefore it is now
+ * deprecated.
*/
+ @Deprecated
public static final String TOP_LEVEL_DOMAIN_STR =
"((aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
+ "|(biz|b[abdefghijmnorstvwyz])"
@@ -59,7 +64,9 @@ public class Patterns {
/**
* Regular expression pattern to match all IANA top-level domains.
+ * @deprecated This API is deprecated. See {@link #TOP_LEVEL_DOMAIN_STR}.
*/
+ @Deprecated
public static final Pattern TOP_LEVEL_DOMAIN =
Pattern.compile(TOP_LEVEL_DOMAIN_STR);
@@ -68,7 +75,10 @@ public class Patterns {
* List accurate as of 2011/07/18. List taken from:
* http://data.iana.org/TLD/tlds-alpha-by-domain.txt
* This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py
+ *
+ * @deprecated This API is deprecated. See {@link #TOP_LEVEL_DOMAIN_STR}.
*/
+ @Deprecated
public static final String TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL =
"(?:"
+ "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
@@ -107,6 +117,24 @@ public class Patterns {
public static final String GOOD_IRI_CHAR =
"a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
+ public static final Pattern IP_ADDRESS
+ = Pattern.compile(
+ "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
+ + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
+ + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+ + "|[1-9][0-9]|[0-9]))");
+
+ /**
+ * RFC 1035 Section 2.3.4 limits the labels to a maximum 63 octets.
+ */
+ private static final String IRI
+ = "[" + GOOD_IRI_CHAR + "]([" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]){0,1}";
+
+ private static final String HOST_NAME = IRI + "(?:\\." + IRI + ")+";
+
+ public static final Pattern DOMAIN_NAME
+ = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")");
+
/**
* Regular expression pattern to match most part of RFC 3987
* Internationalized URLs, aka IRIs. Commonly used Unicode characters are
@@ -116,13 +144,7 @@ public class Patterns {
"((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
+ "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
+ "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
- + "((?:(?:[" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,64}\\.)+" // named host
- + TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL
- + "|(?:(?:25[0-5]|2[0-4]" // or ip address
- + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
- + "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
- + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
- + "|[1-9][0-9]|[0-9])))"
+ + "(?:" + DOMAIN_NAME + ")"
+ "(?:\\:\\d{1,5})?)" // plus option port number
+ "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
+ "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
@@ -130,19 +152,6 @@ public class Patterns {
// input. This is to stop foo.sure from
// matching as foo.su
- public static final Pattern IP_ADDRESS
- = Pattern.compile(
- "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
- + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
- + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
- + "|[1-9][0-9]|[0-9]))");
-
- public static final Pattern DOMAIN_NAME
- = Pattern.compile(
- "(((([" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]*)*[" + GOOD_IRI_CHAR + "]\\.)+"
- + TOP_LEVEL_DOMAIN + ")|"
- + IP_ADDRESS + ")");
-
public static final Pattern EMAIL_ADDRESS
= Pattern.compile(
"[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
@@ -159,7 +168,7 @@ public class Patterns {
* might be phone numbers in arbitrary text, not for validating whether
* something is in fact a phone number. It will miss many things that
* are legitimate phone numbers.
- *
+ *
* <p> The pattern matches the following:
* <ul>
* <li>Optionally, a + sign followed immediately by one or more digits. Spaces, dots, or dashes
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/SparseArray.java b/core/java/android/util/SparseArray.java
index 46d9d45..92e874f 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -17,6 +17,9 @@
package android.util;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
/**
* SparseArrays map integers to Objects. Unlike a normal array of Objects,
@@ -70,12 +73,11 @@ public class SparseArray<E> implements Cloneable {
*/
public SparseArray(int initialCapacity) {
if (initialCapacity == 0) {
- mKeys = ContainerHelpers.EMPTY_INTS;
- mValues = ContainerHelpers.EMPTY_OBJECTS;
+ mKeys = EmptyArray.INT;
+ mValues = EmptyArray.OBJECT;
} else {
- initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
- mKeys = new int[initialCapacity];
- mValues = new Object[initialCapacity];
+ mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
+ mKeys = new int[mValues.length];
}
mSize = 0;
}
@@ -215,28 +217,8 @@ public class SparseArray<E> implements Cloneable {
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
- if (mSize >= mKeys.length) {
- int n = ArrayUtils.idealIntArraySize(mSize + 1);
-
- int[] nkeys = new int[n];
- Object[] nvalues = new Object[n];
-
- // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
- System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
- System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
-
- mKeys = nkeys;
- mValues = nvalues;
- }
-
- if (mSize - i != 0) {
- // Log.e("SparseArray", "move " + (mSize - i));
- System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
- System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
- }
-
- mKeys[i] = key;
- mValues[i] = value;
+ mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
+ mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
@@ -368,24 +350,9 @@ public class SparseArray<E> implements Cloneable {
gc();
}
- int pos = mSize;
- if (pos >= mKeys.length) {
- int n = ArrayUtils.idealIntArraySize(pos + 1);
-
- int[] nkeys = new int[n];
- Object[] nvalues = new Object[n];
-
- // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
- System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
- System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
-
- mKeys = nkeys;
- mValues = nvalues;
- }
-
- mKeys[pos] = key;
- mValues[pos] = value;
- mSize = pos + 1;
+ mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
+ mValues = GrowingArrayUtils.append(mValues, mSize, value);
+ mSize++;
}
/**
diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java
index 905dcb0..e293b1f 100644
--- a/core/java/android/util/SparseBooleanArray.java
+++ b/core/java/android/util/SparseBooleanArray.java
@@ -17,6 +17,9 @@
package android.util;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
/**
* SparseBooleanArrays map integers to booleans.
@@ -57,12 +60,11 @@ public class SparseBooleanArray implements Cloneable {
*/
public SparseBooleanArray(int initialCapacity) {
if (initialCapacity == 0) {
- mKeys = ContainerHelpers.EMPTY_INTS;
- mValues = ContainerHelpers.EMPTY_BOOLEANS;
+ mKeys = EmptyArray.INT;
+ mValues = EmptyArray.BOOLEAN;
} else {
- initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
- mKeys = new int[initialCapacity];
- mValues = new boolean[initialCapacity];
+ mKeys = ArrayUtils.newUnpaddedIntArray(initialCapacity);
+ mValues = new boolean[mKeys.length];
}
mSize = 0;
}
@@ -115,6 +117,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
@@ -128,28 +137,8 @@ public class SparseBooleanArray implements Cloneable {
} else {
i = ~i;
- if (mSize >= mKeys.length) {
- int n = ArrayUtils.idealIntArraySize(mSize + 1);
-
- int[] nkeys = new int[n];
- boolean[] nvalues = new boolean[n];
-
- // Log.e("SparseBooleanArray", "grow " + mKeys.length + " to " + n);
- System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
- System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
-
- mKeys = nkeys;
- mValues = nvalues;
- }
-
- if (mSize - i != 0) {
- // Log.e("SparseBooleanArray", "move " + (mSize - i));
- System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
- System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
- }
-
- mKeys[i] = key;
- mValues[i] = value;
+ mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
+ mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
@@ -191,6 +180,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
@@ -233,24 +227,9 @@ public class SparseBooleanArray implements Cloneable {
return;
}
- int pos = mSize;
- if (pos >= mKeys.length) {
- int n = ArrayUtils.idealIntArraySize(pos + 1);
-
- int[] nkeys = new int[n];
- boolean[] nvalues = new boolean[n];
-
- // Log.e("SparseBooleanArray", "grow " + mKeys.length + " to " + n);
- System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
- System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
-
- mKeys = nkeys;
- mValues = nvalues;
- }
-
- mKeys[pos] = key;
- mValues[pos] = value;
- mSize = pos + 1;
+ mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
+ mValues = GrowingArrayUtils.append(mValues, mSize, value);
+ mSize++;
}
/**
diff --git a/core/java/android/util/SparseIntArray.java b/core/java/android/util/SparseIntArray.java
index 4f5ca07..2b85a21 100644
--- a/core/java/android/util/SparseIntArray.java
+++ b/core/java/android/util/SparseIntArray.java
@@ -17,6 +17,9 @@
package android.util;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
/**
* SparseIntArrays map integers to integers. Unlike a normal array of integers,
@@ -60,12 +63,11 @@ public class SparseIntArray implements Cloneable {
*/
public SparseIntArray(int initialCapacity) {
if (initialCapacity == 0) {
- mKeys = ContainerHelpers.EMPTY_INTS;
- mValues = ContainerHelpers.EMPTY_INTS;
+ mKeys = EmptyArray.INT;
+ mValues = EmptyArray.INT;
} else {
- initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
- mKeys = new int[initialCapacity];
- mValues = new int[initialCapacity];
+ mKeys = ArrayUtils.newUnpaddedIntArray(initialCapacity);
+ mValues = new int[mKeys.length];
}
mSize = 0;
}
@@ -138,28 +140,8 @@ public class SparseIntArray implements Cloneable {
} else {
i = ~i;
- if (mSize >= mKeys.length) {
- int n = ArrayUtils.idealIntArraySize(mSize + 1);
-
- int[] nkeys = new int[n];
- int[] nvalues = new int[n];
-
- // Log.e("SparseIntArray", "grow " + mKeys.length + " to " + n);
- System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
- System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
-
- mKeys = nkeys;
- mValues = nvalues;
- }
-
- if (mSize - i != 0) {
- // Log.e("SparseIntArray", "move " + (mSize - i));
- System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
- System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
- }
-
- mKeys[i] = key;
- mValues[i] = value;
+ mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
+ mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
@@ -243,24 +225,9 @@ public class SparseIntArray implements Cloneable {
return;
}
- int pos = mSize;
- if (pos >= mKeys.length) {
- int n = ArrayUtils.idealIntArraySize(pos + 1);
-
- int[] nkeys = new int[n];
- int[] nvalues = new int[n];
-
- // Log.e("SparseIntArray", "grow " + mKeys.length + " to " + n);
- System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
- System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
-
- mKeys = nkeys;
- mValues = nvalues;
- }
-
- mKeys[pos] = key;
- mValues[pos] = value;
- mSize = pos + 1;
+ mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
+ mValues = GrowingArrayUtils.append(mValues, mSize, value);
+ mSize++;
}
/**
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index 39fc8a3..0166c4a 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -17,6 +17,9 @@
package android.util;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+
+import libcore.util.EmptyArray;
/**
* SparseLongArrays map integers to longs. Unlike a normal array of longs,
@@ -60,12 +63,11 @@ public class SparseLongArray implements Cloneable {
*/
public SparseLongArray(int initialCapacity) {
if (initialCapacity == 0) {
- mKeys = ContainerHelpers.EMPTY_INTS;
- mValues = ContainerHelpers.EMPTY_LONGS;
+ mKeys = EmptyArray.INT;
+ mValues = EmptyArray.LONG;
} else {
- initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
- mKeys = new int[initialCapacity];
- mValues = new long[initialCapacity];
+ mValues = ArrayUtils.newUnpaddedLongArray(initialCapacity);
+ mKeys = new int[mValues.length];
}
mSize = 0;
}
@@ -138,17 +140,8 @@ public class SparseLongArray implements Cloneable {
} else {
i = ~i;
- if (mSize >= mKeys.length) {
- growKeyAndValueArrays(mSize + 1);
- }
-
- if (mSize - i != 0) {
- System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
- System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
- }
-
- mKeys[i] = key;
- mValues[i] = value;
+ mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
+ mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
@@ -232,27 +225,9 @@ public class SparseLongArray implements Cloneable {
return;
}
- int pos = mSize;
- if (pos >= mKeys.length) {
- growKeyAndValueArrays(pos + 1);
- }
-
- mKeys[pos] = key;
- mValues[pos] = value;
- mSize = pos + 1;
- }
-
- private void growKeyAndValueArrays(int minNeededSize) {
- int n = ArrayUtils.idealLongArraySize(minNeededSize);
-
- int[] nkeys = new int[n];
- long[] nvalues = new long[n];
-
- System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
- System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
-
- mKeys = nkeys;
- mValues = nvalues;
+ mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
+ mValues = GrowingArrayUtils.append(mValues, mSize, value);
+ mSize++;
}
/**
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 41d3700..477c994 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -18,13 +18,14 @@ package android.view;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
-import android.util.SparseLongArray;
+import android.util.LongSparseArray;
import android.view.View.AttachInfo;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -36,8 +37,11 @@ import com.android.internal.util.Predicate;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Queue;
/**
* Class for managing accessibility interactions initiated from the system
@@ -48,6 +52,8 @@ import java.util.Map;
*/
final class AccessibilityInteractionController {
+ private static final boolean ENFORCE_NODE_TREE_CONSISTENT = false;
+
private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
new ArrayList<AccessibilityNodeInfo>();
@@ -138,7 +144,7 @@ final class AccessibilityInteractionController {
}
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
- if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
+ if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
root = mViewRootImpl.mView;
} else {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -210,7 +216,7 @@ final class AccessibilityInteractionController {
}
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
- if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
root = findViewByAccessibilityId(accessibilityViewId);
} else {
root = mViewRootImpl.mView;
@@ -290,7 +296,7 @@ final class AccessibilityInteractionController {
}
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
- if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
root = findViewByAccessibilityId(accessibilityViewId);
} else {
root = mViewRootImpl.mView;
@@ -298,9 +304,14 @@ final class AccessibilityInteractionController {
if (root != null && isShown(root)) {
AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
if (provider != null) {
- infos = provider.findAccessibilityNodeInfosByText(text,
- virtualDescendantId);
- } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
+ if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
+ infos = provider.findAccessibilityNodeInfosByText(text,
+ virtualDescendantId);
+ } else {
+ infos = provider.findAccessibilityNodeInfosByText(text,
+ AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+ } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
ArrayList<View> foundViews = mTempArrayList;
foundViews.clear();
root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
@@ -317,7 +328,7 @@ final class AccessibilityInteractionController {
if (provider != null) {
List<AccessibilityNodeInfo> infosFromProvider =
provider.findAccessibilityNodeInfosByText(text,
- AccessibilityNodeInfo.UNDEFINED);
+ AccessibilityNodeProvider.HOST_VIEW_ID);
if (infosFromProvider != null) {
infos.addAll(infosFromProvider);
}
@@ -392,7 +403,7 @@ final class AccessibilityInteractionController {
}
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
- if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
root = findViewByAccessibilityId(accessibilityViewId);
} else {
root = mViewRootImpl.mView;
@@ -418,7 +429,7 @@ final class AccessibilityInteractionController {
focused = AccessibilityNodeInfo.obtain(
mViewRootImpl.mAccessibilityFocusedVirtualView);
}
- } else if (virtualDescendantId == View.NO_ID) {
+ } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
focused = host.createAccessibilityNodeInfo();
}
} break;
@@ -501,7 +512,7 @@ final class AccessibilityInteractionController {
}
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
- if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
root = findViewByAccessibilityId(accessibilityViewId);
} else {
root = mViewRootImpl.mView;
@@ -577,7 +588,7 @@ final class AccessibilityInteractionController {
}
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View target = null;
- if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
target = findViewByAccessibilityId(accessibilityViewId);
} else {
target = mViewRootImpl.mView;
@@ -585,9 +596,14 @@ final class AccessibilityInteractionController {
if (target != null && isShown(target)) {
AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
if (provider != null) {
- succeeded = provider.performAction(virtualDescendantId, action,
- arguments);
- } else if (virtualDescendantId == View.NO_ID) {
+ if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
+ succeeded = provider.performAction(virtualDescendantId, action,
+ arguments);
+ } else {
+ succeeded = provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
+ action, arguments);
+ }
+ } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
succeeded = target.performAccessibilityAction(action, arguments);
}
}
@@ -735,6 +751,85 @@ final class AccessibilityInteractionController {
}
}
}
+ if (ENFORCE_NODE_TREE_CONSISTENT) {
+ enforceNodeTreeConsistent(outInfos);
+ }
+ }
+
+ private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) {
+ LongSparseArray<AccessibilityNodeInfo> nodeMap =
+ new LongSparseArray<AccessibilityNodeInfo>();
+ final int nodeCount = nodes.size();
+ for (int i = 0; i < nodeCount; i++) {
+ AccessibilityNodeInfo node = nodes.get(i);
+ nodeMap.put(node.getSourceNodeId(), node);
+ }
+
+ // If the nodes are a tree it does not matter from
+ // which node we start to search for the root.
+ AccessibilityNodeInfo root = nodeMap.valueAt(0);
+ AccessibilityNodeInfo parent = root;
+ while (parent != null) {
+ root = parent;
+ parent = nodeMap.get(parent.getParentNodeId());
+ }
+
+ // Traverse the tree and do some checks.
+ AccessibilityNodeInfo accessFocus = null;
+ AccessibilityNodeInfo inputFocus = null;
+ HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>();
+ Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
+ fringe.add(root);
+
+ while (!fringe.isEmpty()) {
+ AccessibilityNodeInfo current = fringe.poll();
+
+ // Check for duplicates
+ if (!seen.add(current)) {
+ throw new IllegalStateException("Duplicate node: "
+ + current + " in window:"
+ + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
+ }
+
+ // Check for one accessibility focus.
+ if (current.isAccessibilityFocused()) {
+ if (accessFocus != null) {
+ throw new IllegalStateException("Duplicate accessibility focus:"
+ + current
+ + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
+ } else {
+ accessFocus = current;
+ }
+ }
+
+ // Check for one input focus.
+ if (current.isFocused()) {
+ if (inputFocus != null) {
+ throw new IllegalStateException("Duplicate input focus: "
+ + current + " in window:"
+ + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
+ } else {
+ inputFocus = current;
+ }
+ }
+
+ final int childCount = current.getChildCount();
+ for (int j = 0; j < childCount; j++) {
+ final long childId = current.getChildId(j);
+ final AccessibilityNodeInfo child = nodeMap.get(childId);
+ if (child != null) {
+ fringe.add(child);
+ }
+ }
+ }
+
+ // Check for disconnected nodes.
+ for (int j = nodeMap.size() - 1; j >= 0; j--) {
+ AccessibilityNodeInfo info = nodeMap.valueAt(j);
+ if (!seen.contains(info)) {
+ throw new IllegalStateException("Disconnected node: " + info);
+ }
+ }
}
private void prefetchPredecessorsOfRealNode(View view,
@@ -775,7 +870,7 @@ final class AccessibilityInteractionController {
info = child.createAccessibilityNodeInfo();
} else {
info = provider.createAccessibilityNodeInfo(
- AccessibilityNodeInfo.UNDEFINED);
+ AccessibilityNodeProvider.HOST_VIEW_ID);
}
if (info != null) {
outInfos.add(info);
@@ -815,7 +910,7 @@ final class AccessibilityInteractionController {
}
} else {
AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
- AccessibilityNodeInfo.UNDEFINED);
+ AccessibilityNodeProvider.HOST_VIEW_ID);
if (info != null) {
outInfos.add(info);
addedChildren.put(child, info);
@@ -846,16 +941,22 @@ final class AccessibilityInteractionController {
List<AccessibilityNodeInfo> outInfos) {
long parentNodeId = root.getParentNodeId();
int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
- while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
return;
}
final int virtualDescendantId =
AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
- if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED
+ if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID
|| accessibilityViewId == providerHost.getAccessibilityViewId()) {
- AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(
- virtualDescendantId);
+ final AccessibilityNodeInfo parent;
+ if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
+ parent = provider.createAccessibilityNodeInfo(
+ virtualDescendantId);
+ } else {
+ parent= provider.createAccessibilityNodeInfo(
+ AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
if (parent != null) {
outInfos.add(parent);
}
@@ -876,18 +977,22 @@ final class AccessibilityInteractionController {
AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
final int parentVirtualDescendantId =
AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
- if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED
+ if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID
|| parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
- AccessibilityNodeInfo parent =
- provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
+ final AccessibilityNodeInfo parent;
+ if (parentAccessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
+ parent = provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
+ } else {
+ parent = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
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 +1011,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/AnimationRenderStats.aidl b/core/java/android/view/AnimationRenderStats.aidl
new file mode 100644
index 0000000..4599708
--- /dev/null
+++ b/core/java/android/view/AnimationRenderStats.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;
+
+parcelable AnimationRenderStats;
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 c4494f4..d7a913d 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -558,6 +558,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();
@@ -570,6 +571,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 5f840d3..b0fe0fa 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
deleted file mode 100644
index 43fd628..0000000
--- a/core/java/android/view/DisplayList.java
+++ /dev/null
@@ -1,686 +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;
-
-/**
- * <p>A display list records a series of graphics related operations and can replay
- * them later. Display lists are usually built by recording operations on a
- * {@link HardwareCanvas}. Replaying the operations from a display list avoids
- * executing application code on every frame, and is thus much more efficient.</p>
- *
- * <p>Display lists are used internally for all views by default, and are not
- * typically used directly. One reason to consider using a display is a custom
- * {@link View} implementation that needs to issue a large number of drawing commands.
- * When the view invalidates, all the drawing commands must be reissued, even if
- * large portions of the drawing command stream stay the same frame to frame, which
- * can become a performance bottleneck. To solve this issue, a custom View might split
- * its content into several display lists. A display list is updated only when its
- * content, and only its content, needs to be updated.</p>
- *
- * <p>A text editor might for instance store each paragraph into its own display list.
- * Thus when the user inserts or removes characters, only the display list of the
- * affected paragraph needs to be recorded again.</p>
- *
- * <h3>Hardware acceleration</h3>
- * <p>Display lists can only be replayed using a {@link HardwareCanvas}. They are not
- * supported in software. Always make sure that the {@link android.graphics.Canvas}
- * you are using to render a display list is hardware accelerated using
- * {@link android.graphics.Canvas#isHardwareAccelerated()}.</p>
- *
- * <h3>Creating a display list</h3>
- * <pre class="prettyprint">
- * HardwareRenderer renderer = myView.getHardwareRenderer();
- * if (renderer != null) {
- * DisplayList displayList = renderer.createDisplayList();
- * HardwareCanvas canvas = displayList.start(width, height);
- * try {
- * // Draw onto the canvas
- * // For instance: canvas.drawBitmap(...);
- * } finally {
- * displayList.end();
- * }
- * }
- * </pre>
- *
- * <h3>Rendering a display list on a View</h3>
- * <pre class="prettyprint">
- * protected void onDraw(Canvas canvas) {
- * if (canvas.isHardwareAccelerated()) {
- * HardwareCanvas hardwareCanvas = (HardwareCanvas) canvas;
- * hardwareCanvas.drawDisplayList(mDisplayList);
- * }
- * }
- * </pre>
- *
- * <h3>Releasing resources</h3>
- * <p>This step is not mandatory but recommended if you want to release resources
- * held by a display list as soon as possible.</p>
- * <pre class="prettyprint">
- * // Mark this display list invalid, it cannot be used for drawing anymore,
- * // and release resources held by this display list
- * displayList.clear();
- * </pre>
- *
- * <h3>Properties</h3>
- * <p>In addition, a display list offers several properties, such as
- * {@link #setScaleX(float)} or {@link #setLeft(int)}, that can be used to affect all
- * the drawing commands recorded within. For instance, these properties can be used
- * to move around a large number of images without re-issuing all the individual
- * <code>drawBitmap()</code> calls.</p>
- *
- * <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();
- * }
- * }
- * }
- *
- * protected void onDraw(Canvas canvas) {
- * if (canvas.isHardwareAccelerated()) {
- * HardwareCanvas hardwareCanvas = (HardwareCanvas) canvas;
- * hardwareCanvas.drawDisplayList(mDisplayList);
- * }
- * }
- *
- * private void moveContentBy(int x) {
- * // This will move all the bitmaps recorded inside the display list
- * // by x pixels to the right and redraw this view. All the commands
- * // recorded in createDisplayList() won't be re-issued, only onDraw()
- * // will be invoked and will execute very quickly
- * mDisplayList.offsetLeftAndRight(x);
- * invalidate();
- * }
- * </pre>
- *
- * <h3>Threading</h3>
- * <p>Display lists must be created on and manipulated from the UI thread only.</p>
- *
- * @hide
- */
-public abstract class DisplayList {
- private boolean mDirty;
-
- /**
- * Flag used when calling
- * {@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.
- *
- * @hide
- */
- public static final int FLAG_CLIP_CHILDREN = 0x1;
-
- // NOTE: The STATUS_* values *must* match the enum in DrawGlInfo.h
-
- /**
- * Indicates that the display list is done drawing.
- *
- * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
- *
- * @hide
- */
- public static final int STATUS_DONE = 0x0;
-
- /**
- * Indicates that the display list needs another drawing pass.
- *
- * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
- *
- * @hide
- */
- public static final int STATUS_DRAW = 0x1;
-
- /**
- * Indicates that the display list needs to re-execute its GL functors.
- *
- * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
- * @see HardwareCanvas#callDrawGLFunction(int)
- *
- * @hide
- */
- public static final int STATUS_INVOKE = 0x2;
-
- /**
- * Indicates that the display list performed GL drawing operations.
- *
- * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
- *
- * @hide
- */
- public static final int STATUS_DREW = 0x4;
-
- /**
- * Starts recording the display list. All operations performed on the
- * returned canvas are recorded and stored in this display list.
- *
- * Calling this method will mark the display list invalid until
- * {@link #end()} is called. Only valid display lists can be replayed.
- *
- * @param width The width of the display list's viewport
- * @param height The height of the display list's viewport
- *
- * @return A canvas to record drawing operations.
- *
- * @see #end()
- * @see #isValid()
- */
- public abstract HardwareCanvas start(int width, int height);
-
- /**
- * Ends the recording for this display list. A display list cannot be
- * replayed if recording is not finished. Calling this method marks
- * the display list valid and {@link #isValid()} will return true.
- *
- * @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();
-
-
- /**
- * 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();
-
- /**
- * 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;
- }
-
- /**
- * 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;
- }
-
- /**
- * Returns whether the display list is currently usable. If this returns false,
- * the display list should be re-recorded prior to replaying it.
- *
- * @return boolean true if the display list is able to be replayed, false otherwise.
- */
- public abstract boolean isValid();
-
- /**
- * Return the amount of memory used by this display list.
- *
- * @return The size of this display list in bytes
- *
- * @hide
- */
- public abstract int getSize();
-
- ///////////////////////////////////////////////////////////////////////////
- // DisplayList Property Setters
- ///////////////////////////////////////////////////////////////////////////
-
- /**
- * Set the caching property on the display list, which indicates whether the display list
- * holds a layer. Layer display lists should avoid creating an alpha layer, since alpha is
- * handled in the drawLayer operation directly (and more efficiently).
- *
- * @param caching true if the display list represents a hardware layer, false otherwise.
- *
- * @hide
- */
- public abstract void setCaching(boolean caching);
-
- /**
- * Set whether the display list should clip itself to its bounds. This property is controlled by
- * the view's parent.
- *
- * @param clipToBounds true if the display list should clip to its bounds
- */
- public abstract void setClipToBounds(boolean 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.)
- *
- * @param matrix A transform matrix to apply to this display list
- *
- * @see #getMatrix(android.graphics.Matrix)
- * @see #getMatrix()
- */
- public abstract void setMatrix(Matrix matrix);
-
- /**
- * Returns the static matrix set on this display list.
- *
- * @return A new {@link Matrix} instance populated with this display list's static
- * matrix
- *
- * @see #getMatrix(android.graphics.Matrix)
- * @see #setMatrix(android.graphics.Matrix)
- */
- public Matrix getMatrix() {
- return getMatrix(new Matrix());
- }
-
- /**
- * Copies this display list's static matrix into the specified matrix.
- *
- * @param matrix The {@link Matrix} instance in which to copy this display
- * list's static matrix. Cannot be null
- *
- * @return The <code>matrix</code> parameter, for convenience
- *
- * @see #getMatrix()
- * @see #setMatrix(android.graphics.Matrix)
- */
- public abstract Matrix getMatrix(Matrix matrix);
-
- /**
- * Set the Animation matrix on the display list. This matrix exists if an Animation is
- * currently playing on a View, and is set on the display list during at draw() time. When
- * the Animation finishes, the matrix should be cleared by sending <code>null</code>
- * for the matrix parameter.
- *
- * @param matrix The matrix, null indicates that the matrix should be cleared.
- *
- * @hide
- */
- public abstract void setAnimationMatrix(Matrix matrix);
-
- /**
- * Sets the translucency level for the display list.
- *
- * @param alpha The translucency of the display list, must be a value between 0.0f and 1.0f
- *
- * @see View#setAlpha(float)
- * @see #getAlpha()
- */
- public abstract void setAlpha(float alpha);
-
- /**
- * Returns the translucency level of this display list.
- *
- * @return A value between 0.0f and 1.0f
- *
- * @see #setAlpha(float)
- */
- public abstract float getAlpha();
-
- /**
- * Sets whether the display list renders content which overlaps. Non-overlapping rendering
- * can use a fast path for alpha that avoids rendering to an offscreen buffer. By default
- * display lists consider they do not have overlapping content.
- *
- * @param hasOverlappingRendering False if the content is guaranteed to be non-overlapping,
- * true otherwise.
- *
- * @see android.view.View#hasOverlappingRendering()
- * @see #hasOverlappingRendering()
- */
- public abstract void setHasOverlappingRendering(boolean hasOverlappingRendering);
-
- /**
- * Indicates whether the content of this display list overlaps.
- *
- * @return True if this display list renders content which overlaps, false otherwise.
- *
- * @see #setHasOverlappingRendering(boolean)
- */
- public abstract boolean hasOverlappingRendering();
-
- /**
- * 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);
-
- /**
- * Returns the translation value for this display list on the X axis, in pixels.
- *
- * @see #setTranslationX(float)
- */
- public abstract float getTranslationX();
-
- /**
- * 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);
-
- /**
- * Returns the translation value for this display list on the Y axis, in pixels.
- *
- * @see #setTranslationY(float)
- */
- public abstract float getTranslationY();
-
- /**
- * 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);
-
- /**
- * Returns the rotation value for this display list around the Z axis, in degrees.
- *
- * @see #setRotation(float)
- */
- public abstract float getRotation();
-
- /**
- * 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);
-
- /**
- * Returns the rotation value for this display list around the X axis, in degrees.
- *
- * @see #setRotationX(float)
- */
- public abstract float getRotationX();
-
- /**
- * 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);
-
- /**
- * Returns the rotation value for this display list around the Y axis, in degrees.
- *
- * @see #setRotationY(float)
- */
- public abstract float getRotationY();
-
- /**
- * 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);
-
- /**
- * Returns the scale value for this display list on the X axis.
- *
- * @see #setScaleX(float)
- */
- public abstract float getScaleX();
-
- /**
- * 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);
-
- /**
- * Returns the scale value for this display list on the Y axis.
- *
- * @see #setScaleY(float)
- */
- public abstract float getScaleY();
-
- /**
- * Sets all of the transform-related values of the display list
- *
- * @param alpha The alpha value of the display list
- * @param translationX The translationX value of the display list
- * @param translationY The translationY value of the display list
- * @param rotation The rotation value of the display list
- * @param rotationX The rotationX value of the display list
- * @param rotationY The rotationY value of the display list
- * @param scaleX The scaleX value of the display list
- * @param scaleY The scaleY value of the display list
- *
- * @hide
- */
- public abstract void setTransformationInfo(float alpha, float translationX, float translationY,
- float rotation, float rotationX, float rotationY, float scaleX, float scaleY);
-
- /**
- * Sets the pivot value for the display list on the X axis
- *
- * @param pivotX The pivot value of the display list on the X axis, in pixels
- *
- * @see View#setPivotX(float)
- * @see #getPivotX()
- */
- public abstract void setPivotX(float pivotX);
-
- /**
- * Returns the pivot value for this display list on the X axis, in pixels.
- *
- * @see #setPivotX(float)
- */
- public abstract float getPivotX();
-
- /**
- * Sets the pivot value for the display list on the Y axis
- *
- * @param pivotY The pivot value of the display list on the Y axis, in pixels
- *
- * @see View#setPivotY(float)
- * @see #getPivotY()
- */
- public abstract void setPivotY(float pivotY);
-
- /**
- * Returns the pivot value for this display list on the Y axis, in pixels.
- *
- * @see #setPivotY(float)
- */
- public abstract float getPivotY();
-
- /**
- * Sets the camera distance for the display list. Refer to
- * {@link View#setCameraDistance(float)} for more information on how to
- * use this property.
- *
- * @param distance The distance in Z of the camera of the display list
- *
- * @see View#setCameraDistance(float)
- * @see #getCameraDistance()
- */
- public abstract void setCameraDistance(float distance);
-
- /**
- * Returns the distance in Z of the camera of the display list.
- *
- * @see #setCameraDistance(float)
- */
- public abstract float getCameraDistance();
-
- /**
- * Sets the left position for the display list.
- *
- * @param left The left position, in pixels, of the display list
- *
- * @see View#setLeft(int)
- * @see #getLeft()
- */
- public abstract void setLeft(int left);
-
- /**
- * Returns the left position for the display list in pixels.
- *
- * @see #setLeft(int)
- */
- public abstract float getLeft();
-
- /**
- * Sets the top position for the display list.
- *
- * @param top The top position, in pixels, of the display list
- *
- * @see View#setTop(int)
- * @see #getTop()
- */
- public abstract void setTop(int top);
-
- /**
- * Returns the top position for the display list in pixels.
- *
- * @see #setTop(int)
- */
- public abstract float getTop();
-
- /**
- * Sets the right position for the display list.
- *
- * @param right The right position, in pixels, of the display list
- *
- * @see View#setRight(int)
- * @see #getRight()
- */
- public abstract void setRight(int right);
-
- /**
- * Returns the right position for the display list in pixels.
- *
- * @see #setRight(int)
- */
- public abstract float getRight();
-
- /**
- * Sets the bottom position for the display list.
- *
- * @param bottom The bottom position, in pixels, of the display list
- *
- * @see View#setBottom(int)
- * @see #getBottom()
- */
- public abstract void setBottom(int bottom);
-
- /**
- * Returns the bottom position for the display list in pixels.
- *
- * @see #setBottom(int)
- */
- public abstract float getBottom();
-
- /**
- * Sets the left and top positions for the display list
- *
- * @param left The left position of the display list, in pixels
- * @param top The top position of the display list, in pixels
- * @param right The right position of the display list, in pixels
- * @param bottom The bottom position of the display list, in pixels
- *
- * @see View#setLeft(int)
- * @see View#setTop(int)
- * @see View#setRight(int)
- * @see View#setBottom(int)
- */
- public abstract void setLeftTopRightBottom(int left, int top, int right, int bottom);
-
- /**
- * Offsets the left and right positions for the display list
- *
- * @param offset The amount that the left and right positions of the display
- * list are offset, in pixels
- *
- * @see View#offsetLeftAndRight(int)
- */
- public abstract void offsetLeftAndRight(float offset);
-
- /**
- * Offsets the top and bottom values for the display list
- *
- * @param offset The amount that the top and bottom positions of the display
- * list are offset, in pixels
- *
- * @see View#offsetTopAndBottom(int)
- */
- public abstract void offsetTopAndBottom(float offset);
-}
diff --git a/core/java/android/view/FrameStats.java b/core/java/android/view/FrameStats.java
new file mode 100644
index 0000000..541b336
--- /dev/null
+++ b/core/java/android/view/FrameStats.java
@@ -0,0 +1,97 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This is the base class for frame statistics.
+ */
+public abstract class FrameStats {
+ /**
+ * Undefined time.
+ */
+ public static final long UNDEFINED_TIME_NANO = -1;
+
+ protected long mRefreshPeriodNano;
+ protected long[] mFramesPresentedTimeNano;
+
+ /**
+ * Gets the refresh period of the display hosting the window(s) for
+ * which these statistics apply.
+ *
+ * @return The refresh period in nanoseconds.
+ */
+ public final long getRefreshPeriodNano() {
+ return mRefreshPeriodNano;
+ }
+
+ /**
+ * Gets the number of frames for which there is data.
+ *
+ * @return The number of frames.
+ */
+ public final int getFrameCount() {
+ return mFramesPresentedTimeNano != null
+ ? mFramesPresentedTimeNano.length : 0;
+ }
+
+ /**
+ * Gets the start time of the interval for which these statistics
+ * apply. The start interval is the time when the first frame was
+ * presented.
+ *
+ * @return The start time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+ * if there is no frame data.
+ */
+ public final long getStartTimeNano() {
+ if (getFrameCount() <= 0) {
+ return UNDEFINED_TIME_NANO;
+ }
+ return mFramesPresentedTimeNano[0];
+ }
+
+ /**
+ * Gets the end time of the interval for which these statistics
+ * apply. The end interval is the time when the last frame was
+ * presented.
+ *
+ * @return The end time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+ * if there is no frame data.
+ */
+ public final long getEndTimeNano() {
+ if (getFrameCount() <= 0) {
+ return UNDEFINED_TIME_NANO;
+ }
+ return mFramesPresentedTimeNano[mFramesPresentedTimeNano.length - 1];
+ }
+
+ /**
+ * Get the time a frame at a given index was presented.
+ *
+ * @param index The frame index.
+ * @return The presented time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+ * if the frame is not presented yet.
+ */
+ public final long getFramePresentedTimeNano(int index) {
+ if (mFramesPresentedTimeNano == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mFramesPresentedTimeNano[index];
+ }
+}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index d533060..c274fc4 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);
+ protected static native long nFinishRecording(long renderer);
@Override
- void outputDisplayList(DisplayList displayList) {
- nOutputDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList());
- }
-
- private static native void nOutputDisplayList(long renderer, long displayList);
-
- @Override
- public int drawDisplayList(DisplayList displayList, Rect dirty, int flags) {
- return nDrawDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList(),
+ public int drawDisplayList(RenderNode displayList, Rect dirty, int flags) {
+ 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..eba4f7f
--- /dev/null
+++ b/core/java/android/view/GLRenderer.java
@@ -0,0 +1,1566 @@
+/*
+ * 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
+ public void invokeFunctor(long functor, boolean waitForCompletion) {
+ boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR;
+ boolean hasContext = !needsContext;
+
+ if (needsContext) {
+ GLRendererEglContext managedContext =
+ (GLRendererEglContext) sEglContextStorage.get();
+ if (managedContext != null) {
+ usePbufferSurface(managedContext.getContext());
+ hasContext = true;
+ }
+ }
+
+ try {
+ nInvokeFunctor(functor, hasContext);
+ } finally {
+ if (needsContext) {
+ sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
+ EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ }
+ }
+ }
+
+ private static native void nInvokeFunctor(long functor, boolean hasContext);
+
+ @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);
+ }
+ }
+
+ @Override
+ void pauseSurface(Surface surface) {
+ // No-op
+ }
+
+ 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) {
+ mCanvas.invokeFunctors(mRedrawClip);
+ }
+ }
+ }
+
+ @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);
+
+ RenderNode 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 = RenderNode.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--;
+ }
+ }
+ }
+
+ @Override
+ void fence() {
+ // Everything is immediate, so this is a no-op
+ }
+
+ private RenderNode buildDisplayList(View view, HardwareCanvas canvas) {
+ if (mDrawDelta <= 0) {
+ return view.mRenderNode;
+ }
+
+ 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");
+ RenderNode renderNode = view.getDisplayList();
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+
+ endBuildDisplayListProfiling(buildDisplayListStartTime);
+
+ return renderNode;
+ }
+
+ 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,
+ RenderNode displayList, int status) {
+
+ long drawDisplayListStartTime = 0;
+ if (mProfileEnabled) {
+ drawDisplayListStartTime = System.nanoTime();
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList");
+ nPrepareTree(displayList.getNativeDisplayList());
+ try {
+ status |= canvas.drawDisplayList(displayList, mRedrawClip,
+ RenderNode.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;
+ }
+
+ return status;
+ }
+
+ private void swapBuffers(int status) {
+ if ((status & RenderNode.STATUS_DREW) == RenderNode.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);
+ }
+ }
+ }
+
+ @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);
+
+ private static native void nPrepareTree(long displayListPtr);
+
+ 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..233f846 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -23,11 +23,10 @@ import android.graphics.Rect;
/**
* Hardware accelerated canvas.
- *
+ *
* @hide
*/
public abstract class HardwareCanvas extends Canvas {
- private String mName;
@Override
public boolean isHardwareAccelerated() {
@@ -40,37 +39,10 @@ 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.
- * @return {@link DisplayList#STATUS_DREW} if anything was drawn (such as a call to clear
+ * @return {@link RenderNode#STATUS_DREW} if anything was drawn (such as a call to clear
* the canvas).
*
* @hide
@@ -86,40 +58,28 @@ public abstract class HardwareCanvas extends Canvas {
/**
* Draws the specified display list onto this canvas. The display list can only
- * be drawn if {@link android.view.DisplayList#isValid()} returns true.
+ * be drawn if {@link android.view.RenderNode#isValid()} returns true.
*
* @param displayList The display list to replay.
*/
- public void drawDisplayList(DisplayList displayList) {
- drawDisplayList(displayList, null, DisplayList.FLAG_CLIP_CHILDREN);
+ public void drawDisplayList(RenderNode displayList) {
+ drawDisplayList(displayList, null, RenderNode.FLAG_CLIP_CHILDREN);
}
/**
* Draws the specified display list onto this canvas.
*
* @param displayList The display list to replay.
- * @param dirty The dirty region to redraw in the next pass, matters only
- * if this method returns {@link DisplayList#STATUS_DRAW}, can be null.
- * @param flags Optional flags about drawing, see {@link DisplayList} for
+ * @param dirty Ignored, can be null.
+ * @param flags Optional flags about drawing, see {@link RenderNode} for
* the possible flags.
*
- * @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW}, or
- * {@link DisplayList#STATUS_INVOKE}, or'd with {@link DisplayList#STATUS_DREW}
+ * @return One of {@link RenderNode#STATUS_DONE} or {@link RenderNode#STATUS_DREW}
* if anything was drawn.
*
* @hide
*/
- 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);
+ public abstract int drawDisplayList(RenderNode displayList, Rect dirty, int flags);
/**
* Draws the specified layer onto this canvas.
@@ -139,29 +99,27 @@ public abstract class HardwareCanvas extends Canvas {
* This function may return true if an invalidation is needed after the call.
*
* @param drawGLFunction A native function pointer
- *
- * @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW} or
- * {@link DisplayList#STATUS_INVOKE}
+ *
+ * @return {@link RenderNode#STATUS_DONE}
*
* @hide
*/
public int callDrawGLFunction(long drawGLFunction) {
// Noop - this is done in the display list recorder subclass
- return DisplayList.STATUS_DONE;
+ return RenderNode.STATUS_DONE;
}
/**
* Invoke all the functors who requested to be invoked during the previous frame.
- *
- * @param dirty The region to redraw when the functors return {@link DisplayList#STATUS_DRAW}
- *
- * @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW} or
- * {@link DisplayList#STATUS_INVOKE}
+ *
+ * @param dirty Ignored
+ *
+ * @return Ignored
*
* @hide
*/
public int invokeFunctors(Rect dirty) {
- return DisplayList.STATUS_DONE;
+ return RenderNode.STATUS_DONE;
}
/**
@@ -192,7 +150,7 @@ public abstract class HardwareCanvas extends Canvas {
/**
* Indicates that the specified layer must be updated as soon as possible.
- *
+ *
* @param layer The layer to update
*
* @see #clearLayerUpdates()
@@ -225,7 +183,7 @@ public abstract class HardwareCanvas extends Canvas {
/**
* Removes all enqueued layer updates.
- *
+ *
* @see #pushLayerUpdate(HardwareLayer)
*
* @hide
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index 23383d9..4d78733 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 RenderNode 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 RenderNode 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 = RenderNode.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..56d96e1 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 = true;
private boolean mEnabled;
private boolean mRequested = true;
@@ -282,11 +234,11 @@ 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
+ * Stops any rendering into the surface. Use this if it is unclear whether
+ * or not the surface used by the HardwareRenderer will be changing. It
+ * Suspends any rendering into the surface, but will not do any destruction
*/
- abstract void destroyLayers(View view);
+ abstract void pauseSurface(Surface surface);
/**
* Destroys all hardware rendering resources associated with the specified
@@ -305,15 +257,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 +293,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 +315,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 +326,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 +336,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 +393,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 +420,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 +428,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 +439,21 @@ 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)
+ *
+ */
+ abstract void attachFunctor(View.AttachInfo attachInfo, long functor);
+
+ /**
+ * Schedules the functor for execution in either kModeProcess or
+ * kModeProcessNoContext, depending on whether or not there is an EGLContext.
*
- * @return true if the functor was attached successfully
+ * @param functor The native functor to invoke
+ * @param waitForCompletion If true, this will not return until the functor
+ * has invoked. If false, the functor may be invoked
+ * asynchronously.
*/
- abstract boolean attachFunctor(View.AttachInfo attachInfo, long functor);
+ public abstract void invokeFunctor(long functor, boolean waitForCompletion);
/**
* Initializes the hardware renderer for the specified surface and setup the
@@ -621,17 +493,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 +531,7 @@ public abstract class HardwareRenderer {
* see {@link android.content.ComponentCallbacks}
*/
static void startTrimMemory(int level) {
- Gl20Renderer.startTrimMemory(level);
+ GLRenderer.startTrimMemory(level);
}
/**
@@ -664,7 +539,7 @@ public abstract class HardwareRenderer {
* cleanup special resources used by the memory trimming process.
*/
static void endTrimMemory() {
- Gl20Renderer.endTrimMemory();
+ GLRenderer.endTrimMemory();
}
/**
@@ -706,6 +581,11 @@ public abstract class HardwareRenderer {
}
/**
+ * Blocks until all previously queued work has completed.
+ */
+ abstract void fence();
+
+ /**
* 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 +678,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/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index c92a104..7d13399 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -27,7 +27,6 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.IRemoteCallback;
import android.view.IApplicationToken;
-import android.view.IMagnificationCallbacks;
import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
import android.view.IWindowSession;
@@ -38,6 +37,7 @@ import android.view.MotionEvent;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.IInputFilter;
+import android.view.WindowContentFrameStats;
/**
* System private interface to the window manager.
@@ -197,7 +197,7 @@ interface IWindowManager
void thawRotation();
/**
- * Gets whether the rotation is frozen.
+ * Gets whether the rotation is frozen.
*
* @return Whether the rotation is frozen.
*/
@@ -231,55 +231,28 @@ interface IWindowManager
void lockNow(in Bundle options);
/**
- * Gets the token for the focused window.
- */
- IBinder getFocusedWindowToken();
-
- /**
- * Sets an input filter for manipulating the input event stream.
- */
- void setInputFilter(in IInputFilter filter);
-
- /**
- * Gets the frame of a window given its token.
- */
- void getWindowFrame(IBinder token, out Rect outFrame);
-
- /**
* Device is in safe mode.
*/
boolean isSafeModeEnabled();
/**
- * Sets the display magnification callbacks. These callbacks notify
- * the client for contextual changes related to display magnification.
- *
- * @param callbacks The magnification callbacks.
- */
- void setMagnificationCallbacks(IMagnificationCallbacks callbacks);
-
- /**
- * Sets the magnification spec to be applied to all windows that can be
- * magnified.
- *
- * @param spec The current magnification spec.
+ * Enables the screen if all conditions are met.
*/
- void setMagnificationSpec(in MagnificationSpec spec);
+ void enableScreenIfNeeded();
/**
- * Gets the magnification spec for a window given its token. If the
- * window has a compatibility scale it is also folded in the returned
- * magnification spec.
+ * Clears the frame statistics for a given window.
*
- * @param windowToken The unique window token.
- * @return The magnification spec if such or null.
+ * @param token The window token.
+ * @return Whether the frame statistics were cleared.
*/
- MagnificationSpec getCompatibleMagnificationSpecForWindow(in IBinder windowToken);
+ boolean clearWindowContentFrameStats(IBinder token);
/**
- * Sets the current touch exploration state.
+ * Gets the content frame statistics for a given window.
*
- * @param enabled Whether touch exploration is enabled.
+ * @param token The window token.
+ * @return The frame statistics or null if the window does not exist.
*/
- void setTouchExplorationEnabled(boolean enabled);
+ WindowContentFrameStats getWindowContentFrameStats(IBinder token);
}
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 7b389c0..c3f429c 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;
@@ -1178,20 +1177,20 @@ public class KeyEvent extends InputEvent implements Parcelable {
* 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
@@ -1200,7 +1199,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
@@ -1209,29 +1208,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
@@ -1288,7 +1287,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";
@@ -1318,10 +1317,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.
*/
@@ -1334,10 +1333,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.
*/
@@ -1345,10 +1344,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.
*/
@@ -1357,11 +1356,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.
*/
@@ -1377,7 +1376,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.
@@ -1391,7 +1390,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})
@@ -1414,7 +1413,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})
@@ -1439,7 +1438,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})
@@ -1468,7 +1467,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})
@@ -1499,7 +1498,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})
@@ -1535,7 +1534,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.
@@ -1573,10 +1572,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.
@@ -1692,7 +1691,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.
@@ -1702,11 +1701,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.
@@ -1722,10 +1721,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.
*/
@@ -1747,18 +1746,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.
*/
@@ -1783,7 +1782,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
@@ -1795,7 +1794,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
*/
@@ -1851,16 +1850,13 @@ public class KeyEvent extends InputEvent implements Parcelable {
}
}
- /**
- * Returns true if the key event should be treated as a confirming action.
- * @return True for a confirmation key, such as {@link #KEYCODE_DPAD_CENTER},
- * {@link #KEYCODE_ENTER}, or {@link #KEYCODE_BUTTON_A}.
+ /** Whether key will, by default, trigger a click on the focused view.
+ * @hide
*/
- public final boolean isConfirmKey() {
- switch (mKeyCode) {
+ public static final boolean isConfirmKey(int keyCode) {
+ switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
- case KeyEvent.KEYCODE_BUTTON_A:
return true;
default:
return false;
@@ -1868,19 +1864,27 @@ public class KeyEvent extends InputEvent implements Parcelable {
}
/**
- * Returns true if the key event should be treated as a cancelling action.
- * @return True for a cancellation key, such as {@link #KEYCODE_ESCAPE},
- * {@link #KEYCODE_BACK}, or {@link #KEYCODE_BUTTON_B}.
+ * Whether this key is a media key, which can be send to apps that are
+ * interested in media key events.
+ *
+ * @hide
*/
- public final boolean isCancelKey() {
- switch (mKeyCode) {
- case KeyEvent.KEYCODE_BUTTON_B:
- case KeyEvent.KEYCODE_ESCAPE:
- case KeyEvent.KEYCODE_BACK:
+ 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;
- default:
- return false;
}
+ return false;
}
/** {@inheritDoc} */
@@ -2352,7 +2356,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() {
@@ -2366,7 +2370,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
@@ -2377,7 +2381,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
@@ -2386,7 +2390,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}.
@@ -2394,11 +2398,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() {
@@ -2409,14 +2413,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.
@@ -2433,7 +2437,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() {
@@ -2447,7 +2451,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
*/
@@ -2459,7 +2463,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
@@ -2488,7 +2492,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
/**
* Renamed to {@link #getDeviceId}.
- *
+ *
* @hide
* @deprecated use {@link #getDeviceId()} instead.
*/
@@ -2520,7 +2524,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.
@@ -2543,7 +2547,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.
@@ -2567,7 +2571,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.
*
@@ -2582,7 +2586,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.
@@ -2597,7 +2601,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
@@ -2610,7 +2614,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>
@@ -2634,7 +2638,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public char getNumber() {
return getKeyCharacterMap().getNumber(mKeyCode);
}
-
+
/**
* Returns true if this key produces a glyph.
*
@@ -2643,7 +2647,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
public boolean isPrintingKey() {
return getKeyCharacterMap().isPrintingKey(mKeyCode);
}
-
+
/**
* @deprecated Use {@link #dispatch(Callback, DispatcherState, Object)} instead.
*/
@@ -2651,16 +2655,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,
@@ -2726,7 +2730,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
int mDownKeyCode;
Object mDownTarget;
SparseIntArray mActiveLongPresses = new SparseIntArray();
-
+
/**
* Reset back to initial state.
*/
@@ -2736,7 +2740,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
mDownTarget = null;
mActiveLongPresses.clear();
}
-
+
/**
* Stop any tracking associated with this target.
*/
@@ -2747,14 +2751,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}.
*/
@@ -2767,7 +2771,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.
@@ -2775,7 +2779,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.
@@ -2785,7 +2789,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.
@@ -2940,12 +2944,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 cd905fa..e19bda9 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");
}
@@ -670,31 +669,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;
}
}
@@ -721,9 +757,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;
@@ -739,24 +780,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);
}
}
@@ -764,10 +801,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) {
@@ -775,9 +816,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) {
@@ -812,9 +874,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
@@ -837,7 +900,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/RenderNode.java b/core/java/android/view/RenderNode.java
new file mode 100644
index 0000000..30e4281
--- /dev/null
+++ b/core/java/android/view/RenderNode.java
@@ -0,0 +1,900 @@
+/*
+ * 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.annotation.NonNull;
+import android.graphics.Matrix;
+import android.graphics.Outline;
+
+/**
+ * <p>A display list records a series of graphics related operations and can replay
+ * them later. Display lists are usually built by recording operations on a
+ * {@link HardwareCanvas}. Replaying the operations from a display list avoids
+ * executing application code on every frame, and is thus much more efficient.</p>
+ *
+ * <p>Display lists are used internally for all views by default, and are not
+ * typically used directly. One reason to consider using a display is a custom
+ * {@link View} implementation that needs to issue a large number of drawing commands.
+ * When the view invalidates, all the drawing commands must be reissued, even if
+ * large portions of the drawing command stream stay the same frame to frame, which
+ * can become a performance bottleneck. To solve this issue, a custom View might split
+ * its content into several display lists. A display list is updated only when its
+ * content, and only its content, needs to be updated.</p>
+ *
+ * <p>A text editor might for instance store each paragraph into its own display list.
+ * Thus when the user inserts or removes characters, only the display list of the
+ * affected paragraph needs to be recorded again.</p>
+ *
+ * <h3>Hardware acceleration</h3>
+ * <p>Display lists can only be replayed using a {@link HardwareCanvas}. They are not
+ * supported in software. Always make sure that the {@link android.graphics.Canvas}
+ * you are using to render a display list is hardware accelerated using
+ * {@link android.graphics.Canvas#isHardwareAccelerated()}.</p>
+ *
+ * <h3>Creating a display list</h3>
+ * <pre class="prettyprint">
+ * HardwareRenderer renderer = myView.getHardwareRenderer();
+ * if (renderer != null) {
+ * DisplayList displayList = renderer.createDisplayList();
+ * HardwareCanvas canvas = displayList.start(width, height);
+ * try {
+ * // Draw onto the canvas
+ * // For instance: canvas.drawBitmap(...);
+ * } finally {
+ * displayList.end();
+ * }
+ * }
+ * </pre>
+ *
+ * <h3>Rendering a display list on a View</h3>
+ * <pre class="prettyprint">
+ * protected void onDraw(Canvas canvas) {
+ * if (canvas.isHardwareAccelerated()) {
+ * HardwareCanvas hardwareCanvas = (HardwareCanvas) canvas;
+ * hardwareCanvas.drawDisplayList(mDisplayList);
+ * }
+ * }
+ * </pre>
+ *
+ * <h3>Releasing resources</h3>
+ * <p>This step is not mandatory but recommended if you want to release resources
+ * held by a display list as soon as possible.</p>
+ * <pre class="prettyprint">
+ * // Mark this display list invalid, it cannot be used for drawing anymore,
+ * // and release resources held by this display list
+ * displayList.clear();
+ * </pre>
+ *
+ * <h3>Properties</h3>
+ * <p>In addition, a display list offers several properties, such as
+ * {@link #setScaleX(float)} or {@link #setLeft(int)}, that can be used to affect all
+ * the drawing commands recorded within. For instance, these properties can be used
+ * to move around a large number of images without re-issuing all the individual
+ * <code>drawBitmap()</code> calls.</p>
+ *
+ * <pre class="prettyprint">
+ * private void createDisplayList() {
+ * 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();
+ * }
+ * }
+ *
+ * protected void onDraw(Canvas canvas) {
+ * if (canvas.isHardwareAccelerated()) {
+ * HardwareCanvas hardwareCanvas = (HardwareCanvas) canvas;
+ * hardwareCanvas.drawDisplayList(mDisplayList);
+ * }
+ * }
+ *
+ * private void moveContentBy(int x) {
+ * // This will move all the bitmaps recorded inside the display list
+ * // by x pixels to the right and redraw this view. All the commands
+ * // recorded in createDisplayList() won't be re-issued, only onDraw()
+ * // will be invoked and will execute very quickly
+ * mDisplayList.offsetLeftAndRight(x);
+ * invalidate();
+ * }
+ * </pre>
+ *
+ * <h3>Threading</h3>
+ * <p>Display lists must be created on and manipulated from the UI thread only.</p>
+ *
+ * @hide
+ */
+public class RenderNode {
+ /**
+ * Flag used when calling
+ * {@link HardwareCanvas#drawDisplayList(RenderNode, 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.
+ *
+ * @hide
+ */
+ public static final int FLAG_CLIP_CHILDREN = 0x1;
+
+ // NOTE: The STATUS_* values *must* match the enum in DrawGlInfo.h
+
+ /**
+ * Indicates that the display list is done drawing.
+ *
+ * @see HardwareCanvas#drawDisplayList(RenderNode, android.graphics.Rect, int)
+ *
+ * @hide
+ */
+ public static final int STATUS_DONE = 0x0;
+
+ /**
+ * Indicates that the display list needs another drawing pass.
+ *
+ * @see HardwareCanvas#drawDisplayList(RenderNode, android.graphics.Rect, int)
+ *
+ * @hide
+ */
+ public static final int STATUS_DRAW = 0x1;
+
+ /**
+ * Indicates that the display list needs to re-execute its GL functors.
+ *
+ * @see HardwareCanvas#drawDisplayList(RenderNode, android.graphics.Rect, int)
+ * @see HardwareCanvas#callDrawGLFunction(long)
+ *
+ * @hide
+ */
+ public static final int STATUS_INVOKE = 0x2;
+
+ /**
+ * Indicates that the display list performed GL drawing operations.
+ *
+ * @see HardwareCanvas#drawDisplayList(RenderNode, android.graphics.Rect, int)
+ *
+ * @hide
+ */
+ public static final int STATUS_DREW = 0x4;
+
+ private boolean mValid;
+ private final long mNativeRenderNode;
+
+ private RenderNode(String name) {
+ mNativeRenderNode = nCreate(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 RenderNode create(String name) {
+ return new RenderNode(name);
+ }
+
+ /**
+ * Starts recording a display list for the render node. All
+ * operations performed on the returned canvas are recorded and
+ * stored in this display list.
+ *
+ * Calling this method will mark the render node invalid until
+ * {@link #end(HardwareCanvas)} is called.
+ * Only valid render nodes can be replayed.
+ *
+ * @param width The width of the recording viewport
+ * @param height The height of the recording viewport
+ *
+ * @return A canvas to record drawing operations.
+ *
+ * @see #end(HardwareCanvas)
+ * @see #isValid()
+ */
+ 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
+ * replayed if recording is not finished. Calling this method marks
+ * the display list valid and {@link #isValid()} will return true.
+ *
+ * @see #start(int, int)
+ * @see #isValid()
+ */
+ public void end(HardwareCanvas endCanvas) {
+ if (!(endCanvas instanceof GLES20RecordingCanvas)) {
+ throw new IllegalArgumentException("Passed an invalid canvas to end!");
+ }
+
+ GLES20RecordingCanvas canvas = (GLES20RecordingCanvas) endCanvas;
+ canvas.onPostDraw();
+ long renderNodeData = canvas.finishRecording();
+ nSetDisplayListData(mNativeRenderNode, renderNodeData);
+ 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.
+ *
+ * @hide
+ */
+ public void destroyDisplayListData() {
+ if (!mValid) return;
+
+ nSetDisplayListData(mNativeRenderNode, 0);
+ mValid = false;
+ }
+
+ /**
+ * Returns whether the RenderNode's display list content is currently usable.
+ * If this returns false, the display list should be re-recorded prior to replaying it.
+ *
+ * @return boolean true if the display list is able to be replayed, false otherwise.
+ */
+ public boolean isValid() { return mValid; }
+
+ long getNativeDisplayList() {
+ if (!mValid) {
+ throw new IllegalStateException("The display list is not valid.");
+ }
+ return mNativeRenderNode;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Matrix manipulation
+ ///////////////////////////////////////////////////////////////////////////
+
+ public boolean hasIdentityMatrix() {
+ return nHasIdentityMatrix(mNativeRenderNode);
+ }
+
+ public void getMatrix(@NonNull Matrix outMatrix) {
+ nGetTransformMatrix(mNativeRenderNode, outMatrix.native_instance);
+ }
+
+ public void getInverseMatrix(@NonNull Matrix outMatrix) {
+ nGetInverseTransformMatrix(mNativeRenderNode, outMatrix.native_instance);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // RenderProperty Setters
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Set the caching property on the display list, which indicates whether the display list
+ * holds a layer. Layer display lists should avoid creating an alpha layer, since alpha is
+ * handled in the drawLayer operation directly (and more efficiently).
+ *
+ * @param caching true if the display list represents a hardware layer, false otherwise.
+ *
+ * @hide
+ */
+ public void setCaching(boolean caching) {
+ nSetCaching(mNativeRenderNode, caching);
+ }
+
+ /**
+ * Set whether the Render node should clip itself to its bounds. This property is controlled by
+ * the view's parent.
+ *
+ * @param clipToBounds true if the display list should clip to its bounds
+ */
+ public void setClipToBounds(boolean clipToBounds) {
+ nSetClipToBounds(mNativeRenderNode, clipToBounds);
+ }
+
+ /**
+ * Sets whether the display list should be drawn immediately after the
+ * closest ancestor display list containing a projection receiver.
+ *
+ * @param shouldProject true if the display list should be projected onto a
+ * containing volume.
+ */
+ public void setProjectBackwards(boolean shouldProject) {
+ nSetProjectBackwards(mNativeRenderNode, 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(mNativeRenderNode, shouldRecieve);
+ }
+
+ /**
+ * Sets the outline, defining the shape that casts a shadow, and the path to
+ * be clipped if setClipToOutline is set.
+ *
+ * Deep copies the data into native to simplify reference ownership.
+ */
+ public void setOutline(Outline outline) {
+ if (outline == null) {
+ nSetOutlineEmpty(mNativeRenderNode);
+ } else if (!outline.isValid()) {
+ throw new IllegalArgumentException("Outline must be valid");
+ } else if (outline.mRect != null) {
+ nSetOutlineRoundRect(mNativeRenderNode, outline.mRect.left, outline.mRect.top,
+ outline.mRect.right, outline.mRect.bottom, outline.mRadius);
+ } else if (outline.mPath != null) {
+ nSetOutlineConvexPath(mNativeRenderNode, outline.mPath.mNativePath);
+ }
+ }
+
+ /**
+ * Enables or disables clipping to the outline.
+ *
+ * @param clipToOutline true if clipping to the outline.
+ */
+ public void setClipToOutline(boolean clipToOutline) {
+ nSetClipToOutline(mNativeRenderNode, clipToOutline);
+ }
+
+ /**
+ * Controls the RenderNode's circular reveal clip.
+ */
+ public void setRevealClip(boolean shouldClip, boolean inverseClip,
+ float x, float y, float radius) {
+ nSetRevealClip(mNativeRenderNode, shouldClip, inverseClip, x, y, radius);
+ }
+
+ /**
+ * 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.)
+ *
+ * @param matrix A transform matrix to apply to this display list
+ */
+ public void setStaticMatrix(Matrix matrix) {
+ nSetStaticMatrix(mNativeRenderNode, matrix.native_instance);
+ }
+
+ /**
+ * Set the Animation matrix on the display list. This matrix exists if an Animation is
+ * currently playing on a View, and is set on the display list during at draw() time. When
+ * the Animation finishes, the matrix should be cleared by sending <code>null</code>
+ * for the matrix parameter.
+ *
+ * @param matrix The matrix, null indicates that the matrix should be cleared.
+ *
+ * @hide
+ */
+ public void setAnimationMatrix(Matrix matrix) {
+ nSetAnimationMatrix(mNativeRenderNode,
+ (matrix != null) ? matrix.native_instance : 0);
+ }
+
+ /**
+ * Sets the translucency level for the display list.
+ *
+ * @param alpha The translucency of the display list, must be a value between 0.0f and 1.0f
+ *
+ * @see View#setAlpha(float)
+ * @see #getAlpha()
+ */
+ public void setAlpha(float alpha) {
+ nSetAlpha(mNativeRenderNode, alpha);
+ }
+
+ /**
+ * Returns the translucency level of this display list.
+ *
+ * @return A value between 0.0f and 1.0f
+ *
+ * @see #setAlpha(float)
+ */
+ public float getAlpha() {
+ return nGetAlpha(mNativeRenderNode);
+ }
+
+ /**
+ * Sets whether the display list renders content which overlaps. Non-overlapping rendering
+ * can use a fast path for alpha that avoids rendering to an offscreen buffer. By default
+ * display lists consider they do not have overlapping content.
+ *
+ * @param hasOverlappingRendering False if the content is guaranteed to be non-overlapping,
+ * true otherwise.
+ *
+ * @see android.view.View#hasOverlappingRendering()
+ * @see #hasOverlappingRendering()
+ */
+ public void setHasOverlappingRendering(boolean hasOverlappingRendering) {
+ nSetHasOverlappingRendering(mNativeRenderNode, hasOverlappingRendering);
+ }
+
+ /**
+ * Indicates whether the content of this display list overlaps.
+ *
+ * @return True if this display list renders content which overlaps, false otherwise.
+ *
+ * @see #setHasOverlappingRendering(boolean)
+ */
+ public boolean hasOverlappingRendering() {
+ //noinspection SimplifiableIfStatement
+ return nHasOverlappingRendering(mNativeRenderNode);
+ }
+
+ /**
+ * 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 void setTranslationX(float translationX) {
+ nSetTranslationX(mNativeRenderNode, translationX);
+ }
+
+ /**
+ * Returns the translation value for this display list on the X axis, in pixels.
+ *
+ * @see #setTranslationX(float)
+ */
+ public float getTranslationX() {
+ return nGetTranslationX(mNativeRenderNode);
+ }
+
+ /**
+ * 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 void setTranslationY(float translationY) {
+ nSetTranslationY(mNativeRenderNode, translationY);
+ }
+
+ /**
+ * Returns the translation value for this display list on the Y axis, in pixels.
+ *
+ * @see #setTranslationY(float)
+ */
+ public float getTranslationY() {
+ return nGetTranslationY(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the translation value for the display list on the Z axis.
+ *
+ * @see View#setTranslationZ(float)
+ * @see #getTranslationZ()
+ */
+ public void setTranslationZ(float translationZ) {
+ nSetTranslationZ(mNativeRenderNode, translationZ);
+ }
+
+ /**
+ * Returns the translation value for this display list on the Z axis.
+ *
+ * @see #setTranslationZ(float)
+ */
+ public float getTranslationZ() {
+ return nGetTranslationZ(mNativeRenderNode);
+ }
+
+ /**
+ * 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 void setRotation(float rotation) {
+ nSetRotation(mNativeRenderNode, rotation);
+ }
+
+ /**
+ * Returns the rotation value for this display list around the Z axis, in degrees.
+ *
+ * @see #setRotation(float)
+ */
+ public float getRotation() {
+ return nGetRotation(mNativeRenderNode);
+ }
+
+ /**
+ * 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 void setRotationX(float rotationX) {
+ nSetRotationX(mNativeRenderNode, rotationX);
+ }
+
+ /**
+ * Returns the rotation value for this display list around the X axis, in degrees.
+ *
+ * @see #setRotationX(float)
+ */
+ public float getRotationX() {
+ return nGetRotationX(mNativeRenderNode);
+ }
+
+ /**
+ * 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 void setRotationY(float rotationY) {
+ nSetRotationY(mNativeRenderNode, rotationY);
+ }
+
+ /**
+ * Returns the rotation value for this display list around the Y axis, in degrees.
+ *
+ * @see #setRotationY(float)
+ */
+ public float getRotationY() {
+ return nGetRotationY(mNativeRenderNode);
+ }
+
+ /**
+ * 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 void setScaleX(float scaleX) {
+ nSetScaleX(mNativeRenderNode, scaleX);
+ }
+
+ /**
+ * Returns the scale value for this display list on the X axis.
+ *
+ * @see #setScaleX(float)
+ */
+ public float getScaleX() {
+ return nGetScaleX(mNativeRenderNode);
+ }
+
+ /**
+ * 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 void setScaleY(float scaleY) {
+ nSetScaleY(mNativeRenderNode, scaleY);
+ }
+
+ /**
+ * Returns the scale value for this display list on the Y axis.
+ *
+ * @see #setScaleY(float)
+ */
+ public float getScaleY() {
+ return nGetScaleY(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the pivot value for the display list on the X axis
+ *
+ * @param pivotX The pivot value of the display list on the X axis, in pixels
+ *
+ * @see View#setPivotX(float)
+ * @see #getPivotX()
+ */
+ public void setPivotX(float pivotX) {
+ nSetPivotX(mNativeRenderNode, pivotX);
+ }
+
+ /**
+ * Returns the pivot value for this display list on the X axis, in pixels.
+ *
+ * @see #setPivotX(float)
+ */
+ public float getPivotX() {
+ return nGetPivotX(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the pivot value for the display list on the Y axis
+ *
+ * @param pivotY The pivot value of the display list on the Y axis, in pixels
+ *
+ * @see View#setPivotY(float)
+ * @see #getPivotY()
+ */
+ public void setPivotY(float pivotY) {
+ nSetPivotY(mNativeRenderNode, pivotY);
+ }
+
+ /**
+ * Returns the pivot value for this display list on the Y axis, in pixels.
+ *
+ * @see #setPivotY(float)
+ */
+ public float getPivotY() {
+ return nGetPivotY(mNativeRenderNode);
+ }
+
+ public boolean isPivotExplicitlySet() {
+ return nIsPivotExplicitlySet(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the camera distance for the display list. Refer to
+ * {@link View#setCameraDistance(float)} for more information on how to
+ * use this property.
+ *
+ * @param distance The distance in Z of the camera of the display list
+ *
+ * @see View#setCameraDistance(float)
+ * @see #getCameraDistance()
+ */
+ public void setCameraDistance(float distance) {
+ nSetCameraDistance(mNativeRenderNode, distance);
+ }
+
+ /**
+ * Returns the distance in Z of the camera of the display list.
+ *
+ * @see #setCameraDistance(float)
+ */
+ public float getCameraDistance() {
+ return nGetCameraDistance(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the left position for the display list.
+ *
+ * @param left The left position, in pixels, of the display list
+ *
+ * @see View#setLeft(int)
+ * @see #getLeft()
+ */
+ public void setLeft(int left) {
+ nSetLeft(mNativeRenderNode, left);
+ }
+
+ /**
+ * Returns the left position for the display list in pixels.
+ *
+ * @see #setLeft(int)
+ */
+ public float getLeft() {
+ return nGetLeft(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the top position for the display list.
+ *
+ * @param top The top position, in pixels, of the display list
+ *
+ * @see View#setTop(int)
+ * @see #getTop()
+ */
+ public void setTop(int top) {
+ nSetTop(mNativeRenderNode, top);
+ }
+
+ /**
+ * Returns the top position for the display list in pixels.
+ *
+ * @see #setTop(int)
+ */
+ public float getTop() {
+ return nGetTop(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the right position for the display list.
+ *
+ * @param right The right position, in pixels, of the display list
+ *
+ * @see View#setRight(int)
+ * @see #getRight()
+ */
+ public void setRight(int right) {
+ nSetRight(mNativeRenderNode, right);
+ }
+
+ /**
+ * Returns the right position for the display list in pixels.
+ *
+ * @see #setRight(int)
+ */
+ public float getRight() {
+ return nGetRight(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the bottom position for the display list.
+ *
+ * @param bottom The bottom position, in pixels, of the display list
+ *
+ * @see View#setBottom(int)
+ * @see #getBottom()
+ */
+ public void setBottom(int bottom) {
+ nSetBottom(mNativeRenderNode, bottom);
+ }
+
+ /**
+ * Returns the bottom position for the display list in pixels.
+ *
+ * @see #setBottom(int)
+ */
+ public float getBottom() {
+ return nGetBottom(mNativeRenderNode);
+ }
+
+ /**
+ * Sets the left and top positions for the display list
+ *
+ * @param left The left position of the display list, in pixels
+ * @param top The top position of the display list, in pixels
+ * @param right The right position of the display list, in pixels
+ * @param bottom The bottom position of the display list, in pixels
+ *
+ * @see View#setLeft(int)
+ * @see View#setTop(int)
+ * @see View#setRight(int)
+ * @see View#setBottom(int)
+ */
+ public void setLeftTopRightBottom(int left, int top, int right, int bottom) {
+ nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, bottom);
+ }
+
+ /**
+ * Offsets the left and right positions for the display list
+ *
+ * @param offset The amount that the left and right positions of the display
+ * list are offset, in pixels
+ *
+ * @see View#offsetLeftAndRight(int)
+ */
+ public void offsetLeftAndRight(float offset) {
+ nOffsetLeftAndRight(mNativeRenderNode, offset);
+ }
+
+ /**
+ * Offsets the top and bottom values for the display list
+ *
+ * @param offset The amount that the top and bottom positions of the display
+ * list are offset, in pixels
+ *
+ * @see View#offsetTopAndBottom(int)
+ */
+ public void offsetTopAndBottom(float offset) {
+ nOffsetTopAndBottom(mNativeRenderNode, 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(mNativeRenderNode);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Native methods
+ ///////////////////////////////////////////////////////////////////////////
+
+ private static native long nCreate(String name);
+ private static native void nDestroyRenderNode(long renderNode);
+ private static native void nSetDisplayListData(long renderNode, long newData);
+
+ // Matrix
+
+ private static native void nGetTransformMatrix(long renderNode, long nativeMatrix);
+ private static native void nGetInverseTransformMatrix(long renderNode, long nativeMatrix);
+ private static native boolean nHasIdentityMatrix(long renderNode);
+
+ // Properties
+
+ private static native void nOffsetTopAndBottom(long renderNode, float offset);
+ private static native void nOffsetLeftAndRight(long renderNode, float offset);
+ private static native void nSetLeftTopRightBottom(long renderNode, int left, int top,
+ int right, int bottom);
+ private static native void nSetBottom(long renderNode, int bottom);
+ private static native void nSetRight(long renderNode, int right);
+ private static native void nSetTop(long renderNode, int top);
+ private static native void nSetLeft(long renderNode, int left);
+ private static native void nSetCameraDistance(long renderNode, float distance);
+ private static native void nSetPivotY(long renderNode, float pivotY);
+ private static native void nSetPivotX(long renderNode, float pivotX);
+ private static native void nSetCaching(long renderNode, boolean caching);
+ private static native void nSetClipToBounds(long renderNode, boolean clipToBounds);
+ private static native void nSetProjectBackwards(long renderNode, boolean shouldProject);
+ private static native void nSetProjectionReceiver(long renderNode, boolean shouldRecieve);
+ private static native void nSetOutlineRoundRect(long renderNode, int left, int top,
+ int right, int bottom, float radius);
+ private static native void nSetOutlineConvexPath(long renderNode, long nativePath);
+ private static native void nSetOutlineEmpty(long renderNode);
+ private static native void nSetClipToOutline(long renderNode, boolean clipToOutline);
+ private static native void nSetRevealClip(long renderNode,
+ boolean shouldClip, boolean inverseClip, float x, float y, float radius);
+ private static native void nSetAlpha(long renderNode, float alpha);
+ private static native void nSetHasOverlappingRendering(long renderNode,
+ boolean hasOverlappingRendering);
+ private static native void nSetTranslationX(long renderNode, float translationX);
+ private static native void nSetTranslationY(long renderNode, float translationY);
+ private static native void nSetTranslationZ(long renderNode, float translationZ);
+ private static native void nSetRotation(long renderNode, float rotation);
+ private static native void nSetRotationX(long renderNode, float rotationX);
+ private static native void nSetRotationY(long renderNode, float rotationY);
+ private static native void nSetScaleX(long renderNode, float scaleX);
+ private static native void nSetScaleY(long renderNode, float scaleY);
+ private static native void nSetStaticMatrix(long renderNode, long nativeMatrix);
+ private static native void nSetAnimationMatrix(long renderNode, long animationMatrix);
+
+ private static native boolean nHasOverlappingRendering(long renderNode);
+ private static native float nGetAlpha(long renderNode);
+ private static native float nGetLeft(long renderNode);
+ private static native float nGetTop(long renderNode);
+ private static native float nGetRight(long renderNode);
+ private static native float nGetBottom(long renderNode);
+ private static native float nGetCameraDistance(long renderNode);
+ private static native float nGetScaleX(long renderNode);
+ private static native float nGetScaleY(long renderNode);
+ private static native float nGetTranslationX(long renderNode);
+ private static native float nGetTranslationY(long renderNode);
+ private static native float nGetTranslationZ(long renderNode);
+ private static native float nGetRotation(long renderNode);
+ private static native float nGetRotationX(long renderNode);
+ private static native float nGetRotationY(long renderNode);
+ private static native boolean nIsPivotExplicitlySet(long renderNode);
+ private static native float nGetPivotX(long renderNode);
+ private static native float nGetPivotY(long renderNode);
+ private static native void nOutput(long renderNode);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Finalization
+ ///////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nDestroyRenderNode(mNativeRenderNode);
+ } finally {
+ super.finalize();
+ }
+ }
+}
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..2d55a01 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -20,9 +20,7 @@ import dalvik.system.CloseGuard;
import android.graphics.Bitmap;
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 +38,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();
@@ -58,6 +58,11 @@ public class SurfaceControl {
private static native void nativeSetWindowCrop(long nativeObject, int l, int t, int r, int b);
private static native void nativeSetLayerStack(long nativeObject, int layerStack);
+ private static native boolean nativeClearContentFrameStats(long nativeObject);
+ private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
+ private static native boolean nativeClearAnimationFrameStats();
+ private static native boolean nativeGetAnimationFrameStats(WindowAnimationFrameStats outStats);
+
private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
private static native IBinder nativeCreateDisplay(String name, boolean secure);
private static native void nativeDestroyDisplay(IBinder displayToken);
@@ -178,13 +183,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 +197,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;
@@ -356,6 +361,24 @@ public class SurfaceControl {
nativeSetTransparentRegionHint(mNativeObject, region);
}
+ public boolean clearContentFrameStats() {
+ checkNotReleased();
+ return nativeClearContentFrameStats(mNativeObject);
+ }
+
+ public boolean getContentFrameStats(WindowContentFrameStats outStats) {
+ checkNotReleased();
+ return nativeGetContentFrameStats(mNativeObject, outStats);
+ }
+
+ public static boolean clearAnimationFrameStats() {
+ return nativeClearAnimationFrameStats();
+ }
+
+ public static boolean getAnimationFrameStats(WindowAnimationFrameStats outStats) {
+ return nativeGetAnimationFrameStats(outStats);
+ }
+
/**
* Sets an alpha value for the entire Surface. This value is combined with the
* per-pixel alpha. It may be used with opaque Surfaces.
@@ -370,18 +393,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) {
@@ -553,7 +564,6 @@ public class SurfaceControl {
return nativeGetBuiltInDisplay(builtInDisplayId);
}
-
/**
* Copy the current screen contents into the provided {@link Surface}
*
@@ -567,10 +577,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 +600,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,10 +610,9 @@ 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);
}
-
/**
* Copy the current screen contents into a bitmap and return it.
*
@@ -615,20 +629,25 @@ 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);
}
/**
- * Like {@link SurfaceControl#screenshot(int, int, int, int)} but includes all
- * Surfaces in the screenshot.
+ * Like {@link SurfaceControl#screenshot(int, int, int, int, boolean)} but
+ * includes all Surfaces in the screenshot.
*
* @param width The desired width of the returned bitmap; the raw
* screen will be scaled down to this size.
@@ -642,17 +661,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..23123dd 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
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..1429837
--- /dev/null
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -0,0 +1,313 @@
+/*
+ * 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;
+ private boolean mInitialized = false;
+ private RenderNode mRootNode;
+
+ ThreadedRenderer(boolean translucent) {
+ mNativeProxy = nCreateProxy(translucent);
+ mRootNode = RenderNode.create("RootNode");
+ mRootNode.setClipToBounds(false);
+ }
+
+ @Override
+ void destroy(boolean full) {
+ mInitialized = false;
+ updateEnabledState(null);
+ nDestroyCanvasAndSurface(mNativeProxy);
+ }
+
+ private void updateEnabledState(Surface surface) {
+ if (surface == null || !surface.isValid()) {
+ setEnabled(false);
+ } else {
+ setEnabled(mInitialized);
+ }
+ }
+
+ @Override
+ boolean initialize(Surface surface) throws OutOfResourcesException {
+ mInitialized = true;
+ updateEnabledState(surface);
+ return nInitialize(mNativeProxy, surface);
+ }
+
+ @Override
+ void updateSurface(Surface surface) throws OutOfResourcesException {
+ updateEnabledState(surface);
+ nUpdateSurface(mNativeProxy, surface);
+ }
+
+ @Override
+ void pauseSurface(Surface surface) {
+ nPauseSurface(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;
+ mRootNode.setLeftTopRightBottom(0, 0, mWidth, mHeight);
+ 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() {
+ }
+
+ private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
+ 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");
+ HardwareCanvas canvas = mRootNode.start(mWidth, mHeight);
+ callbacks.onHardwarePostDraw(canvas);
+ canvas.drawDisplayList(view.getDisplayList());
+ callbacks.onHardwarePostDraw(canvas);
+ mRootNode.end(canvas);
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+
+ view.mRecreateDisplayList = false;
+ }
+
+ @Override
+ void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) {
+ attachInfo.mIgnoreDirtyState = true;
+ attachInfo.mDrawingTime = SystemClock.uptimeMillis();
+
+ updateRootDisplayList(view, callbacks);
+
+ if (dirty == null) {
+ dirty = NULL_RECT;
+ }
+ nDrawDisplayList(mNativeProxy, mRootNode.getNativeDisplayList(),
+ dirty.left, dirty.top, dirty.right, dirty.bottom);
+ }
+
+ @Override
+ void detachFunctor(long functor) {
+ // no-op, we never attach functors to need to detach them
+ }
+
+ @Override
+ void attachFunctor(AttachInfo attachInfo, long functor) {
+ invokeFunctor(functor, true);
+ }
+
+ @Override
+ public void invokeFunctor(long functor, boolean waitForCompletion) {
+ nInvokeFunctor(mNativeProxy, functor, waitForCompletion);
+ }
+
+ @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
+ void fence() {
+ nFence(mNativeProxy);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nDeleteProxy(mNativeProxy);
+ mNativeProxy = 0;
+ } 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 nPauseSurface(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 nDestroyCanvasAndSurface(long nativeProxy);
+
+ private static native void nInvokeFunctor(long nativeProxy, long functor, boolean waitForCompletion);
+
+ 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);
+
+ private static native void nFence(long nativeProxy);
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index eb7f45e..f44cc87 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,11 @@
package android.view;
+import android.animation.RevealAnimator;
+import android.animation.ValueAnimator;
+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;
@@ -28,6 +33,7 @@ import android.graphics.Insets;
import android.graphics.Interpolator;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
+import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Point;
@@ -86,6 +92,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 +103,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 +668,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 +677,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 +741,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 +913,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 +962,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 +1052,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 +1073,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 +1650,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #setTag(Object)
* @see #getTag()
*/
- protected Object mTag;
+ protected Object mTag = null;
// for mPrivateFlags:
/** {@hide} */
@@ -1729,12 +1792,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static final int PFLAG_HOVERED = 0x10000000;
/**
- * Indicates that pivotX or pivotY were explicitly set and we should not assume the center
- * for transform operations
- *
- * @hide
+ * no longer needed, should be reused
*/
- private static final int PFLAG_PIVOT_EXPLICITLY_SET = 0x20000000;
+ private static final int PFLAG_DOES_NOTHING_REUSE_PLEASE = 0x20000000;
/** {@hide} */
static final int PFLAG_ACTIVATED = 0x40000000;
@@ -1804,6 +1864,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 +2061,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 +2326,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,16 +2370,25 @@ 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 a view's outline has been specifically defined.
+ */
+ static final int PFLAG3_OUTLINE_DEFINED = 0x40;
/**
* Flag indicating that we're in the process of applying window insets.
*/
- static final int PFLAG3_APPLYING_INSETS = 0x40;
+ static final int PFLAG3_APPLYING_INSETS = 0x80;
/**
* Flag indicating that we're in the process of fitting system windows using the old method.
*/
- static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x80;
+ static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x100;
/* End of masks for mPrivateFlags3 */
@@ -2674,6 +2785,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.
*
@@ -2695,7 +2812,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)
*
@@ -2810,120 +2927,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
static class TransformationInfo {
/**
* The transform matrix for the View. This transform is calculated internally
- * based on the rotation, scaleX, and scaleY properties. The identity matrix
- * is used by default. Do *not* use this variable directly; instead call
- * getMatrix(), which will automatically recalculate the matrix if necessary
- * to get the correct matrix based on the latest rotation and scale properties.
+ * based on the translation, rotation, and scale properties.
+ *
+ * Do *not* use this variable directly; instead call getMatrix(), which will
+ * load the value from the View's RenderNode.
*/
private final Matrix mMatrix = new Matrix();
/**
- * The transform matrix for the View. This transform is calculated internally
- * based on the rotation, scaleX, and scaleY properties. The identity matrix
- * is used by default. Do *not* use this variable directly; instead call
- * getInverseMatrix(), which will automatically recalculate the matrix if necessary
- * to get the correct matrix based on the latest rotation and scale properties.
+ * The inverse transform matrix for the View. This transform is calculated
+ * internally based on the translation, rotation, and scale properties.
+ *
+ * Do *not* use this variable directly; instead call getInverseMatrix(),
+ * which will load the value from the View's RenderNode.
*/
private Matrix mInverseMatrix;
/**
- * An internal variable that tracks whether we need to recalculate the
- * transform matrix, based on whether the rotation or scaleX/Y properties
- * have changed since the matrix was last calculated.
- */
- boolean mMatrixDirty = false;
-
- /**
- * An internal variable that tracks whether we need to recalculate the
- * transform matrix, based on whether the rotation or scaleX/Y properties
- * have changed since the matrix was last calculated.
- */
- private boolean mInverseMatrixDirty = true;
-
- /**
- * A variable that tracks whether we need to recalculate the
- * transform matrix, based on whether the rotation or scaleX/Y properties
- * have changed since the matrix was last calculated. This variable
- * is only valid after a call to updateMatrix() or to a function that
- * calls it such as getMatrix(), hasIdentityMatrix() and getInverseMatrix().
- */
- private boolean mMatrixIsIdentity = true;
-
- /**
- * The Camera object is used to compute a 3D matrix when rotationX or rotationY are set.
- */
- private Camera mCamera = null;
-
- /**
- * This matrix is used when computing the matrix for 3D rotations.
- */
- private Matrix matrix3D = null;
-
- /**
- * These prev values are used to recalculate a centered pivot point when necessary. The
- * pivot point is only used in matrix operations (when rotation, scale, or translation are
- * set), so thes values are only used then as well.
- */
- private int mPrevWidth = -1;
- private int mPrevHeight = -1;
-
- /**
- * The degrees rotation around the vertical axis through the pivot point.
- */
- @ViewDebug.ExportedProperty
- float mRotationY = 0f;
-
- /**
- * The degrees rotation around the horizontal axis through the pivot point.
- */
- @ViewDebug.ExportedProperty
- float mRotationX = 0f;
-
- /**
- * The degrees rotation around the pivot point.
- */
- @ViewDebug.ExportedProperty
- float mRotation = 0f;
-
- /**
- * The amount of translation of the object away from its left property (post-layout).
- */
- @ViewDebug.ExportedProperty
- float mTranslationX = 0f;
-
- /**
- * The amount of translation of the object away from its top property (post-layout).
- */
- @ViewDebug.ExportedProperty
- float mTranslationY = 0f;
-
- /**
- * The amount of scale in the x direction around the pivot point. A
- * value of 1 means no scaling is applied.
- */
- @ViewDebug.ExportedProperty
- float mScaleX = 1f;
-
- /**
- * The amount of scale in the y direction around the pivot point. A
- * value of 1 means no scaling is applied.
- */
- @ViewDebug.ExportedProperty
- float mScaleY = 1f;
-
- /**
- * The x location of the point around which the view is rotated and scaled.
- */
- @ViewDebug.ExportedProperty
- float mPivotX = 0f;
-
- /**
- * The y location of the point around which the view is rotated and scaled.
- */
- @ViewDebug.ExportedProperty
- float mPivotY = 0f;
-
- /**
* The opacity of the View. This is a value from 0 to 1, where 0 means
* completely transparent and 1 means completely opaque.
*/
@@ -2948,12 +2968,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private boolean mLastIsOpaque;
/**
- * Convenience value to check for float values that are close enough to zero to be considered
- * zero.
- */
- private static final float NONZERO_EPSILON = .001f;
-
- /**
* The distance in pixels from the left edge of this view's parent
* to the left edge of this view.
* {@hide}
@@ -3135,6 +3149,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 RenderNode mBackgroundDisplayList;
+
private int mBackgroundResource;
private boolean mBackgroundSizeChanged;
@@ -3208,6 +3231,14 @@ 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.
+ *
+ * TODO: once RenderNode is long-lived, remove this and rely on native copy.
+ */
+ private Outline mOutline;
+
+ /**
* When this view has focus and the next focus is {@link #FOCUS_LEFT},
* the user may specify which view to go to next.
*/
@@ -3410,7 +3441,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private Bitmap mDrawingCache;
private Bitmap mUnscaledDrawingCache;
- DisplayList mDisplayList;
+ /**
+ * RenderNode holding View properties, potentially holding a DisplayList of View content.
+ * <p>
+ * When non-null and valid, this is expected to contain an up-to-date copy
+ * of the View content. Its DisplayList content is cleared on temporary detach and reset on
+ * cleanup.
+ */
+ final RenderNode mRenderNode;
/**
* Set to true when the view is sending hover accessibility events because it
@@ -3461,6 +3499,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
mUserPaddingStart = UNDEFINED_PADDING;
mUserPaddingEnd = UNDEFINED_PADDING;
+ mRenderNode = RenderNode.create(getClass().getName());
if (!sCompatibilityDone && context != null) {
final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
@@ -3497,27 +3536,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;
@@ -3540,6 +3616,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;
@@ -3619,6 +3696,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;
@@ -3871,6 +3952,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;
}
}
@@ -3960,6 +4044,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (transformSet) {
setTranslationX(tx);
setTranslationY(ty);
+ setTranslationZ(tz);
setRotation(rotation);
setRotationX(rotationX);
setRotationY(rotationY);
@@ -3979,6 +4064,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
View() {
mResources = null;
+ mRenderNode = RenderNode.create(getClass().getName());
}
public String toString() {
@@ -4026,7 +4112,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
out.append(" #");
out.append(Integer.toHexString(id));
final Resources r = mResources;
- if (id != 0 && r != null) {
+ if (Resources.resourceHasPackage(id) && r != null) {
try {
String pkgname;
switch (id&0xff000000) {
@@ -4608,7 +4694,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()");
}
@@ -4626,12 +4712,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(R.attr.state_focused, x, y);
+
+ if (!focused) {
+ mBackground.removeHotspot(R.attr.state_focused);
+ }
+ }
+ }
+
+ /**
* Request that a rectangle of this view be visible on the screen,
* scrolling if necessary just enough.
*
@@ -4717,7 +4834,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
System.out.println(this + " clearFocus()");
}
- clearFocusInternal(true, true);
+ clearFocusInternal(null, true, true);
}
/**
@@ -4729,10 +4846,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);
}
@@ -4766,12 +4887,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);
}
/**
@@ -4819,7 +4940,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 {
@@ -4881,10 +5003,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see AccessibilityDelegate
*/
public void sendAccessibilityEvent(int eventType) {
- // Excluded views do not send accessibility events.
- if (!includeForAccessibility()) {
- return;
- }
if (mAccessibilityDelegate != null) {
mAccessibilityDelegate.sendAccessibilityEvent(this, eventType);
} else {
@@ -5502,7 +5620,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
public int getAccessibilityWindowId() {
- return mAttachInfo != null ? mAttachInfo.mAccessibilityWindowId : NO_ID;
+ return mAttachInfo != null ? mAttachInfo.mAccessibilityWindowId
+ : AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
}
/**
@@ -5679,6 +5798,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;
}
@@ -5696,7 +5816,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);
}
@@ -5913,7 +6033,7 @@ 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
@@ -5930,7 +6050,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPrivateFlags3 |= PFLAG3_FITTING_SYSTEM_WINDOWS;
return !dispatchApplyWindowInsets(new WindowInsets(insets)).hasInsets();
} finally {
- mPrivateFlags3 &= PFLAG3_FITTING_SYSTEM_WINDOWS;
+ mPrivateFlags3 &= ~PFLAG3_FITTING_SYSTEM_WINDOWS;
}
} else {
// We're being called from the newer apply insets path.
@@ -6155,6 +6275,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;
}
@@ -6166,7 +6287,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);
}
@@ -6325,6 +6446,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;
}
@@ -6347,7 +6469,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;
@@ -6377,6 +6499,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) {
@@ -6746,7 +6869,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 {
@@ -6765,7 +6888,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;
}
@@ -6777,7 +6900,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;
@@ -6827,7 +6950,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;
@@ -6841,7 +6964,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);
}
@@ -6861,7 +6984,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;
}
@@ -6890,7 +7014,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);
@@ -6937,7 +7062,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;
@@ -7285,11 +7409,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)
@@ -7342,9 +7493,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);
- }
+
}
/**
@@ -7358,7 +7507,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
@@ -7381,7 +7529,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.
@@ -7400,7 +7548,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
*/
@@ -7718,8 +7866,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
public void dispatchStartTemporaryDetach() {
- clearDisplayList();
-
onStartTemporaryDetach();
}
@@ -8080,7 +8226,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);
}
@@ -8091,7 +8238,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();
@@ -8110,7 +8257,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);
}
@@ -8123,7 +8270,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) {
}
/**
@@ -8134,7 +8281,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);
}
@@ -8148,7 +8295,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();
}
@@ -8160,6 +8307,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;
}
@@ -8320,7 +8468,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public boolean onKeyDown(int keyCode, KeyEvent event) {
boolean result = false;
- if (event.isConfirmKey()) {
+ if (KeyEvent.isConfirmKey(keyCode)) {
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
@@ -8362,7 +8510,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param event The KeyEvent object that defines the button action.
*/
public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (event.isConfirmKey()) {
+ if (KeyEvent.isConfirmKey(keyCode)) {
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
@@ -8440,7 +8588,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.
*/
@@ -8629,11 +8784,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
&& !pointInView(event.getX(), event.getY()))) {
mSendingHoverAccessibilityEvents = false;
sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
- // If the window does not have input focus we take away accessibility
- // focus as soon as the user stop hovering over the view.
- if (mAttachInfo != null && !mAttachInfo.mHasWindowFocus) {
- getViewRootImpl().setAccessibilityFocus(null, null);
- }
}
}
@@ -8756,10 +8906,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
+ final float x = event.getX();
+ final float y = event.getY();
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
+ clearHotspot(R.attr.state_pressed);
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
@@ -8792,6 +8945,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
+ setHotspot(R.attr.state_pressed, x, y);
setPressed(true);
}
@@ -8824,7 +8978,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// If the post failed, unpress right now
mUnsetPressedState.run();
}
+
removeTapCallback();
+ } else {
+ clearHotspot(R.attr.state_pressed);
}
break;
@@ -8845,23 +9002,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
+ mPendingCheckForTap.x = event.getX();
+ mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
+ setHotspot(R.attr.state_pressed, x, y);
setPressed(true);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
+ clearHotspot(R.attr.state_pressed);
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
- final int x = (int) event.getX();
- final int y = (int) event.getY();
+ setHotspot(R.attr.state_pressed, x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
@@ -8876,12 +9036,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
break;
}
+
return true;
}
return false;
}
+ private void setHotspot(int id, float x, float y) {
+ final Drawable bg = mBackground;
+ if (bg != null && bg.supportsHotspots()) {
+ bg.setHotspot(id, x, y);
+ }
+ }
+
+ private void clearHotspot(int id) {
+ final Drawable bg = mBackground;
+ if (bg != null && bg.supportsHotspots()) {
+ bg.removeHotspot(id);
+ }
+ }
+
/**
* @hide
*/
@@ -8919,6 +9094,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
private void removeUnsetPressCallback() {
if ((mPrivateFlags & PFLAG_PRESSED) != 0 && mUnsetPressedState != null) {
+ clearHotspot(R.attr.state_pressed);
setPressed(false);
removeCallbacks(mUnsetPressedState);
}
@@ -9081,7 +9257,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();
}
@@ -9093,6 +9269,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mParent.invalidateChild(this, null);
}
dispatchVisibilityChanged(this, newVisibility);
+
+ notifySubtreeAccessibilityStateChangedIfNeeded();
}
if ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
@@ -9395,21 +9573,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return The current transform matrix for the view
*/
public Matrix getMatrix() {
- if (mTransformationInfo != null) {
- updateMatrix();
- return mTransformationInfo.mMatrix;
- }
- return Matrix.IDENTITY_MATRIX;
- }
-
- /**
- * Utility function to determine if the value is far enough away from zero to be
- * considered non-zero.
- * @param value A floating point value to check for zero-ness
- * @return whether the passed-in value is far enough away from zero to be considered non-zero
- */
- private static boolean nonzero(float value) {
- return (value < -NONZERO_EPSILON || value > NONZERO_EPSILON);
+ ensureTransformationInfo();
+ final Matrix matrix = mTransformationInfo.mMatrix;
+ mRenderNode.getMatrix(matrix);
+ return matrix;
}
/**
@@ -9419,11 +9586,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return True if the transform matrix is the identity matrix, false otherwise.
*/
final boolean hasIdentityMatrix() {
- if (mTransformationInfo != null) {
- updateMatrix();
- return mTransformationInfo.mMatrixIsIdentity;
- }
- return true;
+ return mRenderNode.hasIdentityMatrix();
}
void ensureTransformationInfo() {
@@ -9432,53 +9595,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
- /**
- * Recomputes the transform matrix if necessary.
- */
- private void updateMatrix() {
- final TransformationInfo info = mTransformationInfo;
- if (info == null) {
- return;
- }
- if (info.mMatrixDirty) {
- // transform-related properties have changed since the last time someone
- // asked for the matrix; recalculate it with the current values
-
- // Figure out if we need to update the pivot point
- if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
- if ((mRight - mLeft) != info.mPrevWidth || (mBottom - mTop) != info.mPrevHeight) {
- info.mPrevWidth = mRight - mLeft;
- info.mPrevHeight = mBottom - mTop;
- info.mPivotX = info.mPrevWidth / 2f;
- info.mPivotY = info.mPrevHeight / 2f;
- }
- }
- info.mMatrix.reset();
- if (!nonzero(info.mRotationX) && !nonzero(info.mRotationY)) {
- info.mMatrix.setTranslate(info.mTranslationX, info.mTranslationY);
- info.mMatrix.preRotate(info.mRotation, info.mPivotX, info.mPivotY);
- info.mMatrix.preScale(info.mScaleX, info.mScaleY, info.mPivotX, info.mPivotY);
- } else {
- if (info.mCamera == null) {
- info.mCamera = new Camera();
- info.matrix3D = new Matrix();
- }
- info.mCamera.save();
- info.mMatrix.preScale(info.mScaleX, info.mScaleY, info.mPivotX, info.mPivotY);
- info.mCamera.rotate(info.mRotationX, info.mRotationY, -info.mRotation);
- info.mCamera.getMatrix(info.matrix3D);
- info.matrix3D.preTranslate(-info.mPivotX, -info.mPivotY);
- info.matrix3D.postTranslate(info.mPivotX + info.mTranslationX,
- info.mPivotY + info.mTranslationY);
- info.mMatrix.postConcat(info.matrix3D);
- info.mCamera.restore();
- }
- info.mMatrixDirty = false;
- info.mMatrixIsIdentity = info.mMatrix.isIdentity();
- info.mInverseMatrixDirty = true;
- }
- }
-
/**
* Utility method to retrieve the inverse of the current mMatrix property.
* We cache the matrix to avoid recalculating it when transform properties
@@ -9487,19 +9603,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return The inverse of the current matrix of this view.
*/
final Matrix getInverseMatrix() {
- final TransformationInfo info = mTransformationInfo;
- if (info != null) {
- updateMatrix();
- if (info.mInverseMatrixDirty) {
- if (info.mInverseMatrix == null) {
- info.mInverseMatrix = new Matrix();
- }
- info.mMatrix.invert(info.mInverseMatrix);
- info.mInverseMatrixDirty = false;
- }
- return info.mInverseMatrix;
+ ensureTransformationInfo();
+ if (mTransformationInfo.mInverseMatrix == null) {
+ mTransformationInfo.mInverseMatrix = new Matrix();
}
- return Matrix.IDENTITY_MATRIX;
+ final Matrix matrix = mTransformationInfo.mInverseMatrix;
+ mRenderNode.getInverseMatrix(matrix);
+ return matrix;
}
/**
@@ -9510,14 +9620,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @return The distance along the Z axis.
*/
public float getCameraDistance() {
- ensureTransformationInfo();
final float dpi = mResources.getDisplayMetrics().densityDpi;
- final TransformationInfo info = mTransformationInfo;
- if (info.mCamera == null) {
- info.mCamera = new Camera();
- info.matrix3D = new Matrix();
- }
- return -(info.mCamera.getLocationZ() * dpi);
+ return -(mRenderNode.getCameraDistance() * dpi);
}
/**
@@ -9560,27 +9664,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #setRotationY(float)
*/
public void setCameraDistance(float distance) {
- invalidateViewProperty(true, false);
-
- ensureTransformationInfo();
final float dpi = mResources.getDisplayMetrics().densityDpi;
- final TransformationInfo info = mTransformationInfo;
- if (info.mCamera == null) {
- info.mCamera = new Camera();
- info.matrix3D = new Matrix();
- }
-
- info.mCamera.setLocation(0.0f, 0.0f, -Math.abs(distance) / dpi);
- info.mMatrixDirty = true;
+ invalidateViewProperty(true, false);
+ mRenderNode.setCameraDistance(-Math.abs(distance) / dpi);
invalidateViewProperty(false, false);
- if (mDisplayList != null) {
- mDisplayList.setCameraDistance(-Math.abs(distance) / dpi);
- }
- 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();
- }
+
+ invalidateParentIfNeededAndWasQuickRejected();
}
/**
@@ -9594,7 +9684,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@ViewDebug.ExportedProperty(category = "drawing")
public float getRotation() {
- return mTransformationInfo != null ? mTransformationInfo.mRotation : 0;
+ return mRenderNode.getRotation();
}
/**
@@ -9612,21 +9702,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @attr ref android.R.styleable#View_rotation
*/
public void setRotation(float rotation) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- if (info.mRotation != rotation) {
+ if (rotation != getRotation()) {
// Double-invalidation is necessary to capture view's old and new areas
invalidateViewProperty(true, false);
- info.mRotation = rotation;
- info.mMatrixDirty = true;
+ mRenderNode.setRotation(rotation);
invalidateViewProperty(false, true);
- if (mDisplayList != null) {
- mDisplayList.setRotation(rotation);
- }
- 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();
- }
+
+ invalidateParentIfNeededAndWasQuickRejected();
}
}
@@ -9641,7 +9723,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@ViewDebug.ExportedProperty(category = "drawing")
public float getRotationY() {
- return mTransformationInfo != null ? mTransformationInfo.mRotationY : 0;
+ return mRenderNode.getRotationY();
}
/**
@@ -9664,20 +9746,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @attr ref android.R.styleable#View_rotationY
*/
public void setRotationY(float rotationY) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- if (info.mRotationY != rotationY) {
+ if (rotationY != getRotationY()) {
invalidateViewProperty(true, false);
- info.mRotationY = rotationY;
- info.mMatrixDirty = true;
+ mRenderNode.setRotationY(rotationY);
invalidateViewProperty(false, true);
- if (mDisplayList != null) {
- mDisplayList.setRotationY(rotationY);
- }
- 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();
- }
+
+ invalidateParentIfNeededAndWasQuickRejected();
}
}
@@ -9692,7 +9766,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@ViewDebug.ExportedProperty(category = "drawing")
public float getRotationX() {
- return mTransformationInfo != null ? mTransformationInfo.mRotationX : 0;
+ return mRenderNode.getRotationX();
}
/**
@@ -9715,20 +9789,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @attr ref android.R.styleable#View_rotationX
*/
public void setRotationX(float rotationX) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- if (info.mRotationX != rotationX) {
+ if (rotationX != getRotationX()) {
invalidateViewProperty(true, false);
- info.mRotationX = rotationX;
- info.mMatrixDirty = true;
+ mRenderNode.setRotationX(rotationX);
invalidateViewProperty(false, true);
- if (mDisplayList != null) {
- mDisplayList.setRotationX(rotationX);
- }
- 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();
- }
+
+ invalidateParentIfNeededAndWasQuickRejected();
}
}
@@ -9744,7 +9810,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@ViewDebug.ExportedProperty(category = "drawing")
public float getScaleX() {
- return mTransformationInfo != null ? mTransformationInfo.mScaleX : 1;
+ return mRenderNode.getScaleX();
}
/**
@@ -9758,20 +9824,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @attr ref android.R.styleable#View_scaleX
*/
public void setScaleX(float scaleX) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- if (info.mScaleX != scaleX) {
+ if (scaleX != getScaleX()) {
invalidateViewProperty(true, false);
- info.mScaleX = scaleX;
- info.mMatrixDirty = true;
+ mRenderNode.setScaleX(scaleX);
invalidateViewProperty(false, true);
- if (mDisplayList != null) {
- mDisplayList.setScaleX(scaleX);
- }
- 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();
- }
+
+ invalidateParentIfNeededAndWasQuickRejected();
}
}
@@ -9787,7 +9845,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@ViewDebug.ExportedProperty(category = "drawing")
public float getScaleY() {
- return mTransformationInfo != null ? mTransformationInfo.mScaleY : 1;
+ return mRenderNode.getScaleY();
}
/**
@@ -9801,20 +9859,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @attr ref android.R.styleable#View_scaleY
*/
public void setScaleY(float scaleY) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- if (info.mScaleY != scaleY) {
+ if (scaleY != getScaleY()) {
invalidateViewProperty(true, false);
- info.mScaleY = scaleY;
- info.mMatrixDirty = true;
+ mRenderNode.setScaleY(scaleY);
invalidateViewProperty(false, true);
- if (mDisplayList != null) {
- mDisplayList.setScaleY(scaleY);
- }
- 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();
- }
+
+ invalidateParentIfNeededAndWasQuickRejected();
}
}
@@ -9832,7 +9882,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@ViewDebug.ExportedProperty(category = "drawing")
public float getPivotX() {
- return mTransformationInfo != null ? mTransformationInfo.mPivotX : 0;
+ return mRenderNode.getPivotX();
}
/**
@@ -9851,23 +9901,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @attr ref android.R.styleable#View_transformPivotX
*/
public void setPivotX(float pivotX) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- boolean pivotSet = (mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) ==
- PFLAG_PIVOT_EXPLICITLY_SET;
- if (info.mPivotX != pivotX || !pivotSet) {
- mPrivateFlags |= PFLAG_PIVOT_EXPLICITLY_SET;
+ if (mRenderNode.isPivotExplicitlySet() || pivotX != getPivotX()) {
invalidateViewProperty(true, false);
- info.mPivotX = pivotX;
- info.mMatrixDirty = true;
+ mRenderNode.setPivotX(pivotX);
invalidateViewProperty(false, true);
- if (mDisplayList != null) {
- mDisplayList.setPivotX(pivotX);
- }
- 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();
- }
+
+ invalidateParentIfNeededAndWasQuickRejected();
}
}
@@ -9885,7 +9924,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@ViewDebug.ExportedProperty(category = "drawing")
public float getPivotY() {
- return mTransformationInfo != null ? mTransformationInfo.mPivotY : 0;
+ return mRenderNode.getPivotY();
}
/**
@@ -9903,23 +9942,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @attr ref android.R.styleable#View_transformPivotY
*/
public void setPivotY(float pivotY) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- boolean pivotSet = (mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) ==
- PFLAG_PIVOT_EXPLICITLY_SET;
- if (info.mPivotY != pivotY || !pivotSet) {
- mPrivateFlags |= PFLAG_PIVOT_EXPLICITLY_SET;
+ if (mRenderNode.isPivotExplicitlySet() || pivotY != getPivotY()) {
invalidateViewProperty(true, false);
- info.mPivotY = pivotY;
- info.mMatrixDirty = true;
+ mRenderNode.setPivotY(pivotY);
invalidateViewProperty(false, true);
- if (mDisplayList != null) {
- mDisplayList.setPivotY(pivotY);
- }
- 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();
- }
+
+ invalidateParentIfNeededAndWasQuickRejected();
}
}
@@ -9997,9 +10025,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
} else {
mPrivateFlags &= ~PFLAG_ALPHA_SET;
invalidateViewProperty(true, false);
- if (mDisplayList != null) {
- mDisplayList.setAlpha(getFinalAlpha());
- }
+ mRenderNode.setAlpha(getFinalAlpha());
}
}
}
@@ -10024,9 +10050,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return true;
} else {
mPrivateFlags &= ~PFLAG_ALPHA_SET;
- if (mDisplayList != null) {
- mDisplayList.setAlpha(getFinalAlpha());
- }
+ mRenderNode.setAlpha(getFinalAlpha());
}
}
return false;
@@ -10047,9 +10071,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mTransformationInfo.mTransitionAlpha = alpha;
mPrivateFlags &= ~PFLAG_ALPHA_SET;
invalidateViewProperty(true, false);
- if (mDisplayList != null) {
- mDisplayList.setAlpha(getFinalAlpha());
- }
+ mRenderNode.setAlpha(getFinalAlpha());
}
}
@@ -10096,9 +10118,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public final void setTop(int top) {
if (top != mTop) {
- updateMatrix();
- final boolean matrixIsIdentity = mTransformationInfo == null
- || mTransformationInfo.mMatrixIsIdentity;
+ final boolean matrixIsIdentity = hasIdentityMatrix();
if (matrixIsIdentity) {
if (mAttachInfo != null) {
int minTop;
@@ -10121,17 +10141,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int oldHeight = mBottom - mTop;
mTop = top;
- if (mDisplayList != null) {
- mDisplayList.setTop(mTop);
- }
+ mRenderNode.setTop(mTop);
sizeChange(width, mBottom - mTop, width, oldHeight);
if (!matrixIsIdentity) {
- if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
- // A change in dimension means an auto-centered pivot point changes, too
- mTransformationInfo.mMatrixDirty = true;
- }
mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
invalidate(true);
}
@@ -10172,9 +10186,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public final void setBottom(int bottom) {
if (bottom != mBottom) {
- updateMatrix();
- final boolean matrixIsIdentity = mTransformationInfo == null
- || mTransformationInfo.mMatrixIsIdentity;
+ final boolean matrixIsIdentity = hasIdentityMatrix();
if (matrixIsIdentity) {
if (mAttachInfo != null) {
int maxBottom;
@@ -10194,17 +10206,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int oldHeight = mBottom - mTop;
mBottom = bottom;
- if (mDisplayList != null) {
- mDisplayList.setBottom(mBottom);
- }
+ mRenderNode.setBottom(mBottom);
sizeChange(width, mBottom - mTop, width, oldHeight);
if (!matrixIsIdentity) {
- if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
- // A change in dimension means an auto-centered pivot point changes, too
- mTransformationInfo.mMatrixDirty = true;
- }
mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
invalidate(true);
}
@@ -10236,9 +10242,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public final void setLeft(int left) {
if (left != mLeft) {
- updateMatrix();
- final boolean matrixIsIdentity = mTransformationInfo == null
- || mTransformationInfo.mMatrixIsIdentity;
+ final boolean matrixIsIdentity = hasIdentityMatrix();
if (matrixIsIdentity) {
if (mAttachInfo != null) {
int minLeft;
@@ -10261,17 +10265,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int height = mBottom - mTop;
mLeft = left;
- if (mDisplayList != null) {
- mDisplayList.setLeft(left);
- }
+ mRenderNode.setLeft(left);
sizeChange(mRight - mLeft, height, oldWidth, height);
if (!matrixIsIdentity) {
- if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
- // A change in dimension means an auto-centered pivot point changes, too
- mTransformationInfo.mMatrixDirty = true;
- }
mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
invalidate(true);
}
@@ -10303,9 +10301,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public final void setRight(int right) {
if (right != mRight) {
- updateMatrix();
- final boolean matrixIsIdentity = mTransformationInfo == null
- || mTransformationInfo.mMatrixIsIdentity;
+ final boolean matrixIsIdentity = hasIdentityMatrix();
if (matrixIsIdentity) {
if (mAttachInfo != null) {
int maxRight;
@@ -10325,17 +10321,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int height = mBottom - mTop;
mRight = right;
- if (mDisplayList != null) {
- mDisplayList.setRight(mRight);
- }
+ mRenderNode.setRight(mRight);
sizeChange(mRight - mLeft, height, oldWidth, height);
if (!matrixIsIdentity) {
- if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
- // A change in dimension means an auto-centered pivot point changes, too
- mTransformationInfo.mMatrixDirty = true;
- }
mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
invalidate(true);
}
@@ -10357,7 +10347,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@ViewDebug.ExportedProperty(category = "drawing")
public float getX() {
- return mLeft + (mTransformationInfo != null ? mTransformationInfo.mTranslationX : 0);
+ return mLeft + getTranslationX();
}
/**
@@ -10380,7 +10370,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@ViewDebug.ExportedProperty(category = "drawing")
public float getY() {
- return mTop + (mTransformationInfo != null ? mTransformationInfo.mTranslationY : 0);
+ return mTop + getTranslationY();
}
/**
@@ -10404,7 +10394,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@ViewDebug.ExportedProperty(category = "drawing")
public float getTranslationX() {
- return mTransformationInfo != null ? mTransformationInfo.mTranslationX : 0;
+ return mRenderNode.getTranslationX();
}
/**
@@ -10418,21 +10408,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @attr ref android.R.styleable#View_translationX
*/
public void setTranslationX(float translationX) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- if (info.mTranslationX != translationX) {
- // Double-invalidation is necessary to capture view's old and new areas
+ if (translationX != getTranslationX()) {
invalidateViewProperty(true, false);
- info.mTranslationX = translationX;
- info.mMatrixDirty = true;
+ mRenderNode.setTranslationX(translationX);
invalidateViewProperty(false, true);
- if (mDisplayList != null) {
- mDisplayList.setTranslationX(translationX);
- }
- 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();
- }
+
+ invalidateParentIfNeededAndWasQuickRejected();
}
}
@@ -10446,7 +10427,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
@ViewDebug.ExportedProperty(category = "drawing")
public float getTranslationY() {
- return mTransformationInfo != null ? mTransformationInfo.mTranslationY : 0;
+ return mRenderNode.getTranslationY();
}
/**
@@ -10460,37 +10441,169 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @attr ref android.R.styleable#View_translationY
*/
public void setTranslationY(float translationY) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- if (info.mTranslationY != translationY) {
+ if (translationY != getTranslationY()) {
invalidateViewProperty(true, false);
- info.mTranslationY = translationY;
- info.mMatrixDirty = true;
+ mRenderNode.setTranslationY(translationY);
invalidateViewProperty(false, true);
- if (mDisplayList != null) {
- mDisplayList.setTranslationY(translationY);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ }
+ }
+
+ /**
+ * 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 mRenderNode.getTranslationZ();
+ }
+
+ /**
+ * Sets the depth location of this view relative to its parent.
+ *
+ * @attr ref android.R.styleable#View_translationZ
+ */
+ public void setTranslationZ(float translationZ) {
+ if (translationZ != getTranslationZ()) {
+ invalidateViewProperty(true, false);
+ mRenderNode.setTranslationZ(translationZ);
+ invalidateViewProperty(false, true);
+
+ invalidateParentIfNeededAndWasQuickRejected();
+ }
+ }
+
+ /**
+ * Returns a ValueAnimator which can animate a clipping circle.
+ * <p>
+ * The View will be clipped to the animating circle.
+ * <p>
+ * Any shadow cast by the View will respect the circular clip from this animator.
+ *
+ * @param centerX The x coordinate of the center of the animating circle.
+ * @param centerY The y coordinate of the center of the animating circle.
+ * @param startRadius The starting radius of the animating circle.
+ * @param endRadius The ending radius of the animating circle.
+ */
+ public final ValueAnimator createRevealAnimator(int centerX, int centerY,
+ float startRadius, float endRadius) {
+ return RevealAnimator.ofRevealCircle(this, centerX, centerY,
+ startRadius, endRadius, false);
+ }
+
+ /**
+ * Returns a ValueAnimator which can animate a clearing circle.
+ * <p>
+ * The View is prevented from drawing within the circle, so the content
+ * behind the View shows through.
+ *
+ * @param centerX The x coordinate of the center of the animating circle.
+ * @param centerY The y coordinate of the center of the animating circle.
+ * @param startRadius The starting radius of the animating circle.
+ * @param endRadius The ending radius of the animating circle.
+ *
+ * @hide
+ */
+ public final ValueAnimator createClearCircleAnimator(int centerX, int centerY,
+ float startRadius, float endRadius) {
+ return RevealAnimator.ofRevealCircle(this, centerX, centerY,
+ startRadius, endRadius, true);
+ }
+
+ /**
+ * Sets the outline of the view, which defines the shape of the shadow it
+ * casts, and can used for clipping.
+ * <p>
+ * If the outline is not set or is null, 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 {@link android.graphics.Outline#isValid() valid.}
+ *
+ * @see #getClipToOutline()
+ * @see #setClipToOutline(boolean)
+ */
+ public void setOutline(@Nullable Outline outline) {
+ if (outline != null && !outline.isValid()) {
+ throw new IllegalArgumentException("Outline must not be invalid");
+ }
+
+ mPrivateFlags3 |= PFLAG3_OUTLINE_DEFINED;
+
+ if (outline == null) {
+ mOutline = null;
+ } else {
+ // always copy the path since caller may reuse
+ if (mOutline == null) {
+ mOutline = new Outline();
}
- 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();
+ mOutline.set(outline);
+ }
+ mRenderNode.setOutline(mOutline);
+ }
+
+ /**
+ * Returns whether the outline of the View will be used for clipping.
+ *
+ * @see #setOutline(Outline)
+ */
+ 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
+ * {@link Canvas#clipPath(Path) path clipping},
+ * 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 #setOutline(Outline)
+ */
+ 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;
}
+ mRenderNode.setClipToOutline(clipToOutline);
}
}
/**
+ * Private API to be used for reveal animation
+ *
+ * @hide
+ */
+ public void setRevealClip(boolean shouldClip, boolean inverseClip,
+ float x, float y, float radius) {
+ mRenderNode.setRevealClip(shouldClip, inverseClip, x, y, radius);
+ // TODO: Handle this invalidate in a better way, or purely in native.
+ invalidate();
+ }
+
+ /**
* Hit rectangle in parent's coordinates
*
* @param outRect The hit rectangle of the view.
*/
public void getHitRect(Rect outRect) {
- updateMatrix();
- final TransformationInfo info = mTransformationInfo;
- if (info == null || info.mMatrixIsIdentity || mAttachInfo == null) {
+ if (hasIdentityMatrix() || mAttachInfo == null) {
outRect.set(mLeft, mTop, mRight, mBottom);
} else {
final RectF tmpRect = mAttachInfo.mTmpTransformRect;
tmpRect.set(0, 0, getWidth(), getHeight());
- info.mMatrix.mapRect(tmpRect);
+ getMatrix().mapRect(tmpRect); // TODO: mRenderNode.mapRect(tmpRect)
outRect.set((int) tmpRect.left + mLeft, (int) tmpRect.top + mTop,
(int) tmpRect.right + mLeft, (int) tmpRect.bottom + mTop);
}
@@ -10579,11 +10692,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public void offsetTopAndBottom(int offset) {
if (offset != 0) {
- updateMatrix();
- final boolean matrixIsIdentity = mTransformationInfo == null
- || mTransformationInfo.mMatrixIsIdentity;
+ final boolean matrixIsIdentity = hasIdentityMatrix();
if (matrixIsIdentity) {
- if (mDisplayList != null) {
+ if (isHardwareAccelerated()) {
invalidateViewProperty(false, false);
} else {
final ViewParent p = mParent;
@@ -10611,8 +10722,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mTop += offset;
mBottom += offset;
- if (mDisplayList != null) {
- mDisplayList.offsetTopAndBottom(offset);
+ mRenderNode.offsetTopAndBottom(offset);
+ if (isHardwareAccelerated()) {
invalidateViewProperty(false, false);
} else {
if (!matrixIsIdentity) {
@@ -10630,11 +10741,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public void offsetLeftAndRight(int offset) {
if (offset != 0) {
- updateMatrix();
- final boolean matrixIsIdentity = mTransformationInfo == null
- || mTransformationInfo.mMatrixIsIdentity;
+ final boolean matrixIsIdentity = hasIdentityMatrix();
if (matrixIsIdentity) {
- if (mDisplayList != null) {
+ if (isHardwareAccelerated()) {
invalidateViewProperty(false, false);
} else {
final ViewParent p = mParent;
@@ -10659,8 +10768,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mLeft += offset;
mRight += offset;
- if (mDisplayList != null) {
- mDisplayList.offsetLeftAndRight(offset);
+ mRenderNode.offsetLeftAndRight(offset);
+ if (isHardwareAccelerated()) {
invalidateViewProperty(false, false);
} else {
if (!matrixIsIdentity) {
@@ -10931,94 +11040,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);
@@ -11026,47 +11095,102 @@ 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 (isHardwareAccelerated() && getTranslationZ() != 0) {
+ damageShadowReceiver();
+ }
+ }
+ }
+
+ /**
+ * @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 that can be covered by this View's shadow.
+ *
+ * This method will guarantee that any changes to shadows cast by a View
+ * are damaged on the screen for future redraw.
+ */
+ private void damageShadowReceiver() {
+ final AttachInfo ai = mAttachInfo;
+ if (ai != null) {
+ ViewParent p = getParent();
+ if (p != null && p instanceof ViewGroup) {
+ final ViewGroup vg = (ViewGroup) p;
+ vg.damageInParent();
}
}
}
@@ -11088,7 +11212,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* list properties are not being used in this view
*/
void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
- if (mDisplayList == null || (mPrivateFlags & PFLAG_DRAW_ANIMATION) == PFLAG_DRAW_ANIMATION) {
+ if (!isHardwareAccelerated()
+ || !mRenderNode.isValid()
+ || (mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
if (invalidateParent) {
invalidateParentCaches();
}
@@ -11097,16 +11223,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 (isHardwareAccelerated() && invalidateParent && getTranslationZ() != 0) {
+ damageShadowReceiver();
+ }
+ }
+
+ /**
+ * 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).damageChild(this, r);
+ } else {
+ mParent.invalidateChild(this, r);
}
}
}
@@ -11157,6 +11295,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * @hide
+ */
+ protected void invalidateParentIfNeededAndWasQuickRejected() {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) != 0) {
+ // View was rejected last time it was drawn by its parent; this may have changed
+ invalidateParentIfNeeded();
+ }
+ }
+
+ /**
* Indicates whether this View is opaque. An opaque View guarantees that it will
* draw all the pixels overlapping its bounds using a fully opaque color.
*
@@ -11230,6 +11378,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>
*
@@ -11346,10 +11501,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;
}
@@ -11840,7 +11994,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();
@@ -11864,6 +12018,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;
}
@@ -12255,10 +12410,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
InputMethodManager imm = InputMethodManager.peekInstance();
imm.focusIn(this);
}
-
- if (mDisplayList != null) {
- mDisplayList.clearDirty();
- }
}
/**
@@ -12365,7 +12516,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) {
}
/**
@@ -12559,6 +12710,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;
@@ -12576,15 +12740,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();
}
}
@@ -12752,6 +12910,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
onDetachedFromWindow();
+ onDetachedFromWindowInternal();
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
@@ -13053,11 +13212,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;
}
@@ -13114,7 +13269,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 {
@@ -13174,19 +13329,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:
@@ -13207,8 +13358,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;
@@ -13218,16 +13367,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
@@ -13235,23 +13378,21 @@ 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);
-
+ RenderNode displayList = mHardwareLayer.startRecording();
+ updateDisplayListIfDirty(displayList, true);
+ mHardwareLayer.endRecording(mLocalDirtyRect);
mLocalDirtyRect.setEmpty();
}
@@ -13268,18 +13409,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;
@@ -13394,46 +13528,37 @@ 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).
*
- * @param displayList The previous version of this displayList, could be null.
+ * @param renderNode The previous version of this displayList, could be null.
* @param isLayer Whether the requester of the display list is a layer. If so,
* the view will avoid creating a layer inside the resulting display list.
* @return A new or reused DisplayList object.
*/
- private DisplayList getDisplayList(DisplayList displayList, boolean isLayer) {
+ private void updateDisplayListIfDirty(@NonNull RenderNode renderNode, boolean isLayer) {
+ if (renderNode == null) {
+ throw new IllegalArgumentException("RenderNode must not be null");
+ }
if (!canHaveDisplayList()) {
- return null;
+ // can't populate RenderNode, don't try
+ return;
}
- if (((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 ||
- displayList == null || !displayList.isValid() ||
- (!isLayer && mRecreateDisplayList))) {
+ if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
+ || !renderNode.isValid()
+ || (!isLayer && mRecreateDisplayList)) {
// Don't need to recreate the display list, just need to tell our
// children to restore/recreate theirs
- if (displayList != null && displayList.isValid() &&
- !isLayer && !mRecreateDisplayList) {
+ if (renderNode.isValid()
+ && !isLayer
+ && !mRecreateDisplayList) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchGetDisplayList();
- return displayList;
+ return; // no work needed
}
if (!isLayer) {
@@ -13441,20 +13566,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// we copy in child display lists into ours in drawChild()
mRecreateDisplayList = true;
}
- if (displayList == null) {
- displayList = mAttachInfo.mHardwareRenderer.createDisplayList(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.
- invalidateParentCaches();
- }
boolean caching = false;
int width = mRight - mLeft;
int height = mBottom - mTop;
int layerType = getLayerType();
- final HardwareCanvas canvas = displayList.start(width, height);
+ final HardwareCanvas canvas = renderNode.start(width, height);
try {
if (!isLayer && layerType != LAYER_TYPE_NONE) {
@@ -13497,57 +13615,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
} finally {
- displayList.end();
- displayList.setCaching(caching);
+ renderNode.end(canvas);
+ renderNode.setCaching(caching);
if (isLayer) {
- displayList.setLeftTopRightBottom(0, 0, width, height);
+ renderNode.setLeftTopRightBottom(0, 0, width, height);
} else {
- setDisplayListProperties(displayList);
+ setDisplayListProperties(renderNode);
}
}
} else if (!isLayer) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
-
- return displayList;
}
/**
- * Get the DisplayList for the HardwareLayer
+ * Returns a RenderNode with View draw content recorded, which can be
+ * used to draw this view again without executing its draw method.
*
- * @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>
- *
- * @return A DisplayList ready to replay, or null if caching is not enabled.
+ * @return A RenderNode ready to replay, or null if caching is not enabled.
*
* @hide
*/
- public DisplayList getDisplayList() {
- mDisplayList = getDisplayList(mDisplayList, false);
- return mDisplayList;
+ public RenderNode getDisplayList() {
+ updateDisplayListIfDirty(mRenderNode, false);
+ return mRenderNode;
}
- private void clearDisplayList() {
- if (mDisplayList != null) {
- mDisplayList.clear();
+ private void resetDisplayList() {
+ if (mRenderNode.isValid()) {
+ mRenderNode.destroyDisplayListData();
}
- }
- private void resetDisplayList() {
- if (mDisplayList != null) {
- mDisplayList.reset();
+ if (mBackgroundDisplayList != null && mBackgroundDisplayList.isValid()) {
+ mBackgroundDisplayList.destroyDisplayListData();
}
}
@@ -14137,17 +14238,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * This method is called by getDisplayList() when a display list is created or re-rendered.
- * It sets or resets the current value of all properties on that display list (resetting is
- * necessary when a display list is being re-created, because we need to make sure that
- * previously-set transform values
+ * This method is called by getDisplayList() when a display list is recorded for a View.
+ * It pushes any properties to the RenderNode that aren't managed by the RenderNode.
*/
- void setDisplayListProperties(DisplayList displayList) {
- if (displayList != null) {
- displayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
- displayList.setHasOverlappingRendering(hasOverlappingRendering());
+ void setDisplayListProperties(RenderNode renderNode) {
+ if (renderNode != null) {
+ renderNode.setHasOverlappingRendering(hasOverlappingRendering());
if (mParent instanceof ViewGroup) {
- displayList.setClipToBounds(
+ renderNode.setClipToBounds(
(((ViewGroup) mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0);
}
float alpha = 1;
@@ -14162,7 +14260,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
alpha = t.getAlpha();
}
if ((transformType & Transformation.TYPE_MATRIX) != 0) {
- displayList.setMatrix(t.getMatrix());
+ renderNode.setStaticMatrix(t.getMatrix());
}
}
}
@@ -14175,22 +14273,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
alpha = 1;
}
}
- displayList.setTransformationInfo(alpha,
- mTransformationInfo.mTranslationX, mTransformationInfo.mTranslationY,
- mTransformationInfo.mRotation, mTransformationInfo.mRotationX,
- mTransformationInfo.mRotationY, mTransformationInfo.mScaleX,
- mTransformationInfo.mScaleY);
- if (mTransformationInfo.mCamera == null) {
- mTransformationInfo.mCamera = new Camera();
- mTransformationInfo.matrix3D = new Matrix();
- }
- displayList.setCameraDistance(mTransformationInfo.mCamera.getLocationZ());
- if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == PFLAG_PIVOT_EXPLICITLY_SET) {
- displayList.setPivotX(getPivotX());
- displayList.setPivotY(getPivotY());
- }
+ renderNode.setAlpha(alpha);
} else if (alpha < 1) {
- displayList.setAlpha(alpha);
+ renderNode.setAlpha(alpha);
}
}
}
@@ -14237,10 +14322,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
transformToApply = parent.getChildTransformation();
} else {
- if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) ==
- PFLAG3_VIEW_IS_ANIMATING_TRANSFORM && mDisplayList != null) {
+ if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) != 0) {
// No longer animating: clear out old animation matrix
- mDisplayList.setAnimationMatrix(null);
+ mRenderNode.setAnimationMatrix(null);
mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
if (!useDisplayListProperties &&
@@ -14278,7 +14362,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPrivateFlags &= ~PFLAG_INVALIDATED;
}
- DisplayList displayList = null;
+ RenderNode displayList = null;
Bitmap cache = null;
boolean hasDisplayList = false;
if (caching) {
@@ -14570,24 +14654,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)
@@ -14754,6 +14821,89 @@ 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;
+ if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) == 0) {
+ // Outline not currently define, query from background
+ mOutline = background.getOutline();
+ mRenderNode.setOutline(mOutline);
+ }
+ }
+
+ // Attempt to use a display list if requested.
+ if (canvas.isHardwareAccelerated() && mAttachInfo != null
+ && mAttachInfo.mHardwareRenderer != null) {
+ mBackgroundDisplayList = getDrawableDisplayList(background, mBackgroundDisplayList);
+
+ final RenderNode 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(RenderNode 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 RenderNode getDrawableDisplayList(Drawable drawable, RenderNode displayList) {
+ if (displayList == null) {
+ displayList = RenderNode.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(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
@@ -15022,20 +15172,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mTop = top;
mRight = right;
mBottom = bottom;
- if (mDisplayList != null) {
- mDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
- }
+ mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
if (sizeChanged) {
- if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
- // A change in dimension means an auto-centered pivot point changes, too
- if (mTransformationInfo != null) {
- mTransformationInfo.mMatrixDirty = true;
- }
- }
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
@@ -15094,9 +15236,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;
@@ -15113,6 +15256,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();
@@ -15132,14 +15276,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);
}
}
@@ -15202,7 +15346,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @hide
*/
- public void onResolveDrawables(int layoutDirection) {
+ public void onResolveDrawables(@ResolvedLayoutDir int layoutDirection) {
}
/**
@@ -15249,7 +15393,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());
}
@@ -15434,7 +15578,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);
@@ -15969,8 +16113,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;
}
@@ -15988,54 +16134,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());
}
}
@@ -16318,7 +16470,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)
@@ -16348,7 +16501,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()
@@ -18020,6 +18174,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;
}
@@ -18043,7 +18198,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;
@@ -18084,6 +18239,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;
@@ -18246,6 +18402,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
//
@@ -18298,6 +18481,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.
*/
@@ -18552,10 +18751,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
- class CheckForLongPress implements Runnable {
-
+ private final class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
+ @Override
public void run() {
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
@@ -18571,14 +18770,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
private final class CheckForTap implements Runnable {
+ public float x;
+ public float y;
+
+ @Override
public void run() {
mPrivateFlags &= ~PFLAG_PREPRESSED;
+ setHotspot(R.attr.state_pressed, x, y);
setPressed(true);
checkForLongClick(ViewConfiguration.getTapTimeout());
}
}
private final class PerformClick implements Runnable {
+ @Override
public void run() {
performClick();
}
@@ -18603,6 +18808,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
@@ -18828,7 +19062,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
private final class UnsetPressedState implements Runnable {
+ @Override
public void run() {
+ clearHotspot(R.attr.state_pressed);
setPressed(false);
}
}
@@ -18921,8 +19157,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final Callbacks mRootCallbacks;
- HardwareCanvas mHardwareCanvas;
-
IWindowId mIWindowId;
WindowId mWindowId;
@@ -18932,7 +19166,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
View mRootView;
IBinder mPanelParentWindowToken;
- Surface mSurface;
boolean mHardwareAccelerated;
boolean mHardwareAccelerationRequested;
@@ -19177,7 +19410,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* The id of the window for accessibility purposes.
*/
- int mAccessibilityWindowId = View.NO_ID;
+ int mAccessibilityWindowId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
/**
* Flags related to accessibility processing.
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index e67659c..5112b9a 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -80,7 +80,7 @@ public class ViewConfiguration {
* is a tap or a scroll. If the user does not move within this interval, it is
* considered to be a tap.
*/
- private static final int TAP_TIMEOUT = 180;
+ private static final int TAP_TIMEOUT = 100;
/**
* Defines the duration in milliseconds we will wait to see if a touch event
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index f346ee8..d2c6302 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -38,7 +38,6 @@ import android.util.Log;
import android.util.Pools.SynchronizedPool;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
@@ -51,6 +50,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;
@@ -355,6 +356,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
private static final int FLAG_LAYOUT_MODE_WAS_EXPLICITLY_SET = 0x800000;
+ static final int FLAG_IS_TRANSITION_GROUP = 0x1000000;
+
+ static final int FLAG_IS_TRANSITION_GROUP_SET = 0x2000000;
+
/**
* 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.
@@ -456,20 +461,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() {
@@ -499,8 +505,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++) {
@@ -545,6 +553,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 +608,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 +626,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 +822,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 +2288,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 +2553,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 +2627,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 +3148,7 @@ 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 children are clipped to their bounds before drawing.
* The default value is true.
* @see #setClipChildren(boolean)
*
@@ -3128,8 +3173,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
setBooleanFlag(FLAG_CLIP_CHILDREN, clipChildren);
for (int i = 0; i < mChildrenCount; ++i) {
View child = getChildAt(i);
- if (child.mDisplayList != null) {
- child.mDisplayList.setClipToBounds(clipChildren);
+ if (child.mRenderNode != null) {
+ child.mRenderNode.setClipToBounds(clipChildren);
}
}
}
@@ -3618,7 +3663,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
childHasTransientStateChanged(child, true);
}
- if (child.isImportantForAccessibility() && child.getVisibility() != View.GONE) {
+ if (child.getVisibility() != View.GONE) {
notifySubtreeAccessibilityStateChangedIfNeeded();
}
}
@@ -3826,7 +3871,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
boolean clearChildFocus = false;
if (view == mFocused) {
- view.unFocus();
+ view.unFocus(null);
clearChildFocus = true;
}
@@ -3861,7 +3906,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
onViewRemoved(view);
- if (view.isImportantForAccessibility() && view.getVisibility() != View.GONE) {
+ if (view.getVisibility() != View.GONE) {
notifySubtreeAccessibilityStateChangedIfNeeded();
}
}
@@ -3921,7 +3966,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
if (view == focused) {
- view.unFocus();
+ view.unFocus(null);
clearChildFocus = true;
}
@@ -4008,7 +4053,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
if (view == focused) {
- view.unFocus();
+ view.unFocus(null);
clearChildFocus = true;
}
@@ -4404,7 +4449,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*
* @hide
*/
- public void invalidateChildFast(View child, final Rect dirty) {
+ public void damageChild(View child, final Rect dirty) {
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
@@ -4427,7 +4472,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
parentVG.invalidate();
parent = null;
} else {
- parent = parentVG.invalidateChildInParentFast(left, top, dirty);
+ parent = parentVG.damageChildInParent(left, top, dirty);
left = parentVG.mLeft;
top = parentVG.mTop;
}
@@ -4449,9 +4494,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*
* @hide
*/
- protected ViewParent invalidateChildInParentFast(int left, int top, final Rect dirty) {
- if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
- (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
+ protected ViewParent damageChildInParent(int left, int top, final Rect dirty) {
+ if ((mPrivateFlags & PFLAG_DRAWN) != 0
+ || (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) != 0) {
dirty.offset(left - mScrollX, top - mScrollY);
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
@@ -4564,9 +4609,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final View v = children[i];
v.mTop += offset;
v.mBottom += offset;
- if (v.mDisplayList != null) {
+ if (v.mRenderNode != null) {
invalidate = true;
- v.mDisplayList.offsetTopAndBottom(offset);
+ v.mRenderNode.offsetTopAndBottom(offset);
}
}
@@ -5217,8 +5262,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();
}
}
@@ -5794,6 +5848,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..0cf9ddd 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();
@@ -285,7 +290,11 @@ public class ViewOverlay {
}
}
- public void invalidateChildFast(View child, final Rect dirty) {
+ /**
+ * @hide
+ */
+ @Override
+ public void damageChild(View child, final Rect dirty) {
if (mHostView != null) {
// Note: This is not a "fast" invalidation. Would be nice to instead invalidate
// using DisplayList properties and a dirty rect instead of causing a real
@@ -304,9 +313,9 @@ public class ViewOverlay {
* @hide
*/
@Override
- protected ViewParent invalidateChildInParentFast(int left, int top, Rect dirty) {
+ protected ViewParent damageChildInParent(int left, int top, Rect dirty) {
if (mHostView instanceof ViewGroup) {
- return ((ViewGroup) mHostView).invalidateChildInParentFast(left, top, dirty);
+ return ((ViewGroup) mHostView).damageChildInParent(left, top, dirty);
}
return null;
}
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 67a94be..6b21451 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.
*
@@ -899,47 +925,41 @@ public class ViewPropertyAnimator {
*/
private void setValue(int propertyConstant, float value) {
final View.TransformationInfo info = mView.mTransformationInfo;
- final DisplayList displayList = mView.mDisplayList;
+ final RenderNode renderNode = mView.mRenderNode;
switch (propertyConstant) {
case TRANSLATION_X:
- info.mTranslationX = value;
- if (displayList != null) displayList.setTranslationX(value);
+ renderNode.setTranslationX(value);
break;
case TRANSLATION_Y:
- info.mTranslationY = value;
- if (displayList != null) displayList.setTranslationY(value);
+ renderNode.setTranslationY(value);
+ break;
+ case TRANSLATION_Z:
+ renderNode.setTranslationZ(value);
break;
case ROTATION:
- info.mRotation = value;
- if (displayList != null) displayList.setRotation(value);
+ renderNode.setRotation(value);
break;
case ROTATION_X:
- info.mRotationX = value;
- if (displayList != null) displayList.setRotationX(value);
+ renderNode.setRotationX(value);
break;
case ROTATION_Y:
- info.mRotationY = value;
- if (displayList != null) displayList.setRotationY(value);
+ renderNode.setRotationY(value);
break;
case SCALE_X:
- info.mScaleX = value;
- if (displayList != null) displayList.setScaleX(value);
+ renderNode.setScaleX(value);
break;
case SCALE_Y:
- info.mScaleY = value;
- if (displayList != null) displayList.setScaleY(value);
+ renderNode.setScaleY(value);
break;
case X:
- info.mTranslationX = value - mView.mLeft;
- if (displayList != null) displayList.setTranslationX(value - mView.mLeft);
+ renderNode.setTranslationX(value - mView.mLeft);
break;
case Y:
- info.mTranslationY = value - mView.mTop;
- if (displayList != null) displayList.setTranslationY(value - mView.mTop);
+ renderNode.setTranslationY(value - mView.mTop);
break;
case ALPHA:
info.mAlpha = value;
- if (displayList != null) displayList.setAlpha(value);
+ renderNode.setAlpha(value);
break;
}
}
@@ -951,28 +971,30 @@ public class ViewPropertyAnimator {
* @return float The value of the named property
*/
private float getValue(int propertyConstant) {
- final View.TransformationInfo info = mView.mTransformationInfo;
+ final RenderNode node = mView.mRenderNode;
switch (propertyConstant) {
case TRANSLATION_X:
- return info.mTranslationX;
+ return node.getTranslationX();
case TRANSLATION_Y:
- return info.mTranslationY;
+ return node.getTranslationY();
+ case TRANSLATION_Z:
+ return node.getTranslationZ();
case ROTATION:
- return info.mRotation;
+ return node.getRotation();
case ROTATION_X:
- return info.mRotationX;
+ return node.getRotationX();
case ROTATION_Y:
- return info.mRotationY;
+ return node.getRotationY();
case SCALE_X:
- return info.mScaleX;
+ return node.getScaleX();
case SCALE_Y:
- return info.mScaleY;
+ return node.getScaleY();
case X:
- return mView.mLeft + info.mTranslationX;
+ return mView.mLeft + node.getTranslationX();
case Y:
- return mView.mTop + info.mTranslationY;
+ return mView.mTop + node.getTranslationY();
case ALPHA:
- return info.mAlpha;
+ return mView.mTransformationInfo.mAlpha;
}
return 0;
}
@@ -1061,7 +1083,7 @@ public class ViewPropertyAnimator {
// Shouldn't happen, but just to play it safe
return;
}
- boolean useDisplayListProperties = mView.mDisplayList != null;
+ boolean useRenderNodeProperties = mView.mRenderNode != null;
// alpha requires slightly different treatment than the other (transform) properties.
// The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
@@ -1069,7 +1091,7 @@ public class ViewPropertyAnimator {
// We track what kinds of properties are set, and how alpha is handled when it is
// set, and perform the invalidation steps appropriately.
boolean alphaHandled = false;
- if (!useDisplayListProperties) {
+ if (!useRenderNodeProperties) {
mView.invalidateParentCaches();
}
float fraction = animation.getAnimatedFraction();
@@ -1091,8 +1113,7 @@ public class ViewPropertyAnimator {
}
}
if ((propertyMask & TRANSFORM_MASK) != 0) {
- mView.mTransformationInfo.mMatrixDirty = true;
- if (!useDisplayListProperties) {
+ if (!useRenderNodeProperties) {
mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1c7f31c..ef22def 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;
@@ -57,6 +58,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;
@@ -70,7 +72,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;
@@ -110,7 +111,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
@@ -270,6 +270,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;
@@ -293,8 +297,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)}
*/
@@ -621,7 +623,6 @@ public final class ViewRootImpl implements ViewParent,
}
void destroyHardwareResources() {
- invalidateDisplayLists();
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.destroyHardwareResources(mView);
mAttachInfo.mHardwareRenderer.destroy(false);
@@ -635,23 +636,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();
}
}
@@ -661,15 +664,15 @@ public final class ViewRootImpl implements ViewParent,
mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(MSG_FLUSH_LAYER_UPDATES));
}
- public boolean attachFunctor(long functor) {
+ public void attachFunctor(long 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(long functor) {
+ mBlockResizeBuffer = true;
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.detachFunctor(functor);
}
@@ -720,7 +723,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 =
@@ -960,14 +963,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;
}
}
@@ -1151,6 +1149,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;
@@ -1221,11 +1241,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
@@ -1436,6 +1451,12 @@ public final class ViewRootImpl implements ViewParent,
host.getMeasuredHeight() + ", params=" + params);
}
+ if (mAttachInfo.mHardwareRenderer != null) {
+ // relayoutWindow may decide to destroy mSurface. As that decision
+ // happens in WindowManager service, we need to be defensive here
+ // and stop using the surface in case it gets destroyed.
+ mAttachInfo.mHardwareRenderer.pauseSurface(mSurface);
+ }
final int surfaceGenerationId = mSurface.getGenerationId();
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
if (!mDrawDuringWindowsAnimating &&
@@ -1470,67 +1491,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);
+ RenderNode layerRenderNode = mResizeBuffer.startRecording();
+ HardwareCanvas layerCanvas = layerRenderNode.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();
- }
- }
+ RenderNode renderNode = mView.mRenderNode;
+ if (renderNode != null && renderNode.isValid()) {
+ layerCanvas.drawDisplayList(renderNode, null,
+ RenderNode.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);
+ layerRenderNode.end(layerCanvas);
+ layerRenderNode.setCaching(true);
+ layerRenderNode.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: "
@@ -1574,7 +1584,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;
@@ -1601,7 +1611,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;
@@ -1684,7 +1694,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;
}
}
@@ -2201,11 +2211,9 @@ public final class ViewRootImpl implements ViewParent,
* @hide
*/
void outputDisplayList(View view) {
- if (mAttachInfo != null && mAttachInfo.mHardwareCanvas != null) {
- DisplayList displayList = view.getDisplayList();
- if (displayList != null) {
- mAttachInfo.mHardwareCanvas.outputDisplayList(displayList);
- }
+ RenderNode renderNode = view.getDisplayList();
+ if (renderNode != null) {
+ renderNode.output();
}
}
@@ -2284,6 +2292,9 @@ public final class ViewRootImpl implements ViewParent,
if (mReportNextDraw) {
mReportNextDraw = false;
+ if (mAttachInfo.mHardwareRenderer != null) {
+ mAttachInfo.mHardwareRenderer.fence();
+ }
if (LOCAL_LOGV) {
Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
@@ -2390,8 +2401,6 @@ public final class ViewRootImpl implements ViewParent,
appScale + ", width=" + mWidth + ", height=" + mHeight);
}
- invalidateDisplayLists();
-
attachInfo.mTreeObserver.dispatchOnDraw();
if (!dirty.isEmpty() || mIsAnimating) {
@@ -2404,6 +2413,7 @@ public final class ViewRootImpl implements ViewParent,
mCurrentDirty.set(dirty);
dirty.setEmpty();
+ mBlockResizeBuffer = false;
attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
animating ? null : mCurrentDirty);
} else {
@@ -2421,7 +2431,7 @@ public final class ViewRootImpl implements ViewParent,
try {
attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
- mHolder.getSurface());
+ mSurface);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
@@ -2556,28 +2566,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);
@@ -2593,7 +2610,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;
@@ -2601,20 +2618,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
*/
@@ -2856,10 +2859,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();
}
@@ -2876,7 +2875,6 @@ public final class ViewRootImpl implements ViewParent,
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
- mAttachInfo.mSurface = null;
mSurface.release();
@@ -3132,7 +3130,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 {
@@ -3181,8 +3179,6 @@ public final class ViewRootImpl implements ViewParent,
mHasHadWindowFocus = true;
}
- setAccessibilityFocus(null, null);
-
if (mView != null && mAccessibilityManager.isEnabled()) {
if (hasWindowFocus) {
mView.sendAccessibilityEvent(
@@ -3325,7 +3321,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;
}
}
@@ -4529,8 +4525,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;
@@ -4558,9 +4553,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);
}
@@ -4627,7 +4619,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;
@@ -5257,10 +5248,10 @@ public final class ViewRootImpl implements ViewParent,
}
private static void getGfxInfo(View view, int[] info) {
- DisplayList displayList = view.mDisplayList;
+ RenderNode renderNode = view.mRenderNode;
info[0]++;
- if (displayList != null) {
- info[1] += displayList.getSize();
+ if (renderNode != null) {
+ info[1] += 0; /* TODO: Memory used by RenderNodes (properties + DisplayLists) */
}
if (view instanceof ViewGroup) {
@@ -5308,7 +5299,6 @@ public final class ViewRootImpl implements ViewParent,
}
if (mAdded && !mFirst) {
- invalidateDisplayLists();
destroyHardwareRenderer();
if (mView != null) {
@@ -5354,7 +5344,7 @@ public final class ViewRootImpl implements ViewParent,
// Hardware rendering
if (mAttachInfo.mHardwareRenderer != null) {
- if (mAttachInfo.mHardwareRenderer.loadSystemProperties(mHolder.getSurface())) {
+ if (mAttachInfo.mHardwareRenderer.loadSystemProperties()) {
invalidate();
}
}
@@ -5557,24 +5547,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);
@@ -5775,10 +5764,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
@@ -5801,6 +5786,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();
@@ -5817,7 +5805,6 @@ public final class ViewRootImpl implements ViewParent,
event.getDeviceId(), event.getScanCode(),
flags, event.getSource(), null);
fallbackAction.recycle();
-
dispatchInputEvent(fallbackEvent);
}
}
@@ -6287,68 +6274,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) {
@@ -6458,7 +6383,7 @@ public final class ViewRootImpl implements ViewParent,
public void ensureConnection() {
if (mAttachInfo != null) {
final boolean registered =
- mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED;
+ mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
if (!registered) {
mAttachInfo.mAccessibilityWindowId =
mAccessibilityManager.addAccessibilityInteractionConnection(mWindow,
@@ -6469,9 +6394,9 @@ public final class ViewRootImpl implements ViewParent,
public void ensureNoConnection() {
final boolean registered =
- mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED;
+ mAttachInfo.mAccessibilityWindowId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
if (registered) {
- mAttachInfo.mAccessibilityWindowId = AccessibilityNodeInfo.UNDEFINED;
+ mAttachInfo.mAccessibilityWindowId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow);
}
}
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/VolumePanel.java b/core/java/android/view/VolumePanel.java
index 52f9c0b..4730e59 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -56,10 +56,8 @@ import java.util.HashMap;
*
* @hide
*/
-public class VolumePanel extends Handler implements OnSeekBarChangeListener, View.OnClickListener,
- VolumeController
-{
- private static final String TAG = "VolumePanel";
+public class VolumePanel extends Handler implements VolumeController {
+ private static final String TAG = VolumePanel.class.getSimpleName();
private static boolean LOGD = false;
/**
@@ -187,7 +185,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
this.iconMuteRes = iconMuteRes;
this.show = show;
}
- };
+ }
// List of stream types and their order
private static final StreamResources[] STREAMS = {
@@ -238,6 +236,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
cleanUp();
}
+ @Override
public void onDismiss(DialogInterface unused) {
mContext.unregisterReceiver(this);
cleanUp();
@@ -253,14 +252,14 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
}
- public VolumePanel(final Context context, AudioService volumeService) {
+ public VolumePanel(Context context, AudioService volumeService) {
mContext = context;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mAudioService = volumeService;
// For now, only show master volume if master volume is supported
- boolean useMasterVolume = context.getResources().getBoolean(
- com.android.internal.R.bool.config_useMasterVolume);
+ final Resources res = context.getResources();
+ final boolean useMasterVolume = res.getBoolean(R.bool.config_useMasterVolume);
if (useMasterVolume) {
for (int i = 0; i < STREAMS.length; i++) {
StreamResources streamRes = STREAMS[i];
@@ -268,21 +267,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
}
}
- LayoutInflater inflater = (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View view = mView = inflater.inflate(R.layout.volume_adjust, null);
- mView.setOnTouchListener(new View.OnTouchListener() {
- public boolean onTouch(View v, MotionEvent event) {
- resetTimeout();
- return false;
- }
- });
- mPanel = (ViewGroup) mView.findViewById(R.id.visible_panel);
- mSliderGroup = (ViewGroup) mView.findViewById(R.id.slider_group);
- mMoreButton = (ImageView) mView.findViewById(R.id.expand_button);
- mDivider = (ImageView) mView.findViewById(R.id.expand_button_divider);
-
- mDialog = new Dialog(context, R.style.Theme_Panel_Volume) {
+ mDialog = new Dialog(context) {
+ @Override
public boolean onTouchEvent(MotionEvent event) {
if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE &&
sConfirmSafeVolumeDialog == null) {
@@ -292,47 +278,65 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
return false;
}
};
- mDialog.setTitle("Volume control"); // No need to localize
- mDialog.setContentView(mView);
- mDialog.setOnDismissListener(new OnDismissListener() {
- public void onDismiss(DialogInterface dialog) {
- mActiveStreamType = -1;
- mAudioManager.forceVolumeControlStream(mActiveStreamType);
- }
- });
+
// Change some window properties
- Window window = mDialog.getWindow();
- window.setGravity(Gravity.TOP);
- LayoutParams lp = window.getAttributes();
+ final Window window = mDialog.getWindow();
+ final LayoutParams lp = window.getAttributes();
lp.token = null;
// Offset from the top
- lp.y = mContext.getResources().getDimensionPixelOffset(
- com.android.internal.R.dimen.volume_panel_top);
+ lp.y = res.getDimensionPixelOffset(R.dimen.volume_panel_top);
lp.type = LayoutParams.TYPE_VOLUME_OVERLAY;
- lp.width = LayoutParams.WRAP_CONTENT;
- lp.height = LayoutParams.WRAP_CONTENT;
+ lp.windowAnimations = R.style.Animation_VolumePanel;
window.setAttributes(lp);
- window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCH_MODAL
+ window.setGravity(Gravity.TOP);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE
+ | LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
- mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
- mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
+ mDialog.setCanceledOnTouchOutside(true);
+ mDialog.setContentView(R.layout.volume_adjust);
+ mDialog.setOnDismissListener(new OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ mActiveStreamType = -1;
+ mAudioManager.forceVolumeControlStream(mActiveStreamType);
+ }
+ });
+
+ mDialog.create();
+ mView = window.findViewById(R.id.content);
+ mView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ resetTimeout();
+ return false;
+ }
+ });
+
+ mPanel = (ViewGroup) mView.findViewById(R.id.visible_panel);
+ mSliderGroup = (ViewGroup) mView.findViewById(R.id.slider_group);
+ mMoreButton = mView.findViewById(R.id.expand_button);
+ mDivider = mView.findViewById(R.id.expand_button_divider);
+
+ mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
+ mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
+
+ // If we don't want to show multiple volumes, hide the settings button
+ // and divider.
mShowCombinedVolumes = !mVoiceCapable && !useMasterVolume;
- // If we don't want to show multiple volumes, hide the settings button and divider
if (!mShowCombinedVolumes) {
mMoreButton.setVisibility(View.GONE);
mDivider.setVisibility(View.GONE);
} else {
- mMoreButton.setOnClickListener(this);
+ mMoreButton.setOnClickListener(mClickListener);
}
- boolean masterVolumeOnly = context.getResources().getBoolean(
- com.android.internal.R.bool.config_useMasterVolume);
- boolean masterVolumeKeySounds = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_useVolumeKeySounds);
-
+ final boolean masterVolumeOnly = res.getBoolean(R.bool.config_useMasterVolume);
+ final boolean masterVolumeKeySounds = res.getBoolean(R.bool.config_useVolumeKeySounds);
mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds;
listenToRingerMode();
@@ -347,7 +351,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
final IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
mContext.registerReceiver(new BroadcastReceiver() {
-
+ @Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
@@ -400,17 +404,21 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
}
private void createSliders() {
- LayoutInflater inflater = (LayoutInflater) mContext
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ final Resources res = mContext.getResources();
+ final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
mStreamControls = new HashMap<Integer, StreamControl>(STREAMS.length);
- Resources res = mContext.getResources();
+
for (int i = 0; i < STREAMS.length; i++) {
StreamResources streamRes = STREAMS[i];
- int streamType = streamRes.streamType;
+
+ final int streamType = streamRes.streamType;
if (mVoiceCapable && streamRes == StreamResources.NotificationStream) {
streamRes = StreamResources.RingerStream;
}
- StreamControl sc = new StreamControl();
+
+ final StreamControl sc = new StreamControl();
sc.streamType = streamType;
sc.group = (ViewGroup) inflater.inflate(R.layout.volume_adjust_item, null);
sc.group.setTag(sc);
@@ -421,10 +429,10 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
sc.iconMuteRes = streamRes.iconMuteRes;
sc.icon.setImageResource(sc.iconRes);
sc.seekbarView = (SeekBar) sc.group.findViewById(R.id.seekbar);
- int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
+ final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne);
- sc.seekbarView.setOnSeekBarChangeListener(this);
+ sc.seekbarView.setOnSeekBarChangeListener(mSeekListener);
sc.seekbarView.setTag(sc);
mStreamControls.put(streamType, sc);
}
@@ -433,7 +441,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
private void reorderSliders(int activeStreamType) {
mSliderGroup.removeAllViews();
- StreamControl active = mStreamControls.get(activeStreamType);
+ final StreamControl active = mStreamControls.get(activeStreamType);
if (active == null) {
Log.e("VolumePanel", "Missing stream type! - " + activeStreamType);
mActiveStreamType = -1;
@@ -486,10 +494,6 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
}
}
- private boolean isExpanded() {
- return mMoreButton.getVisibility() != View.VISIBLE;
- }
-
private void expand() {
final int count = mSliderGroup.getChildCount();
for (int i = 0; i < count; i++) {
@@ -527,6 +531,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
}
+ @Override
public void postRemoteVolumeChanged(int streamType, int flags) {
if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return;
synchronized (this) {
@@ -538,6 +543,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
obtainMessage(MSG_REMOTE_VOLUME_CHANGED, streamType, flags).sendToTarget();
}
+ @Override
public void postRemoteSliderVisibility(boolean visible) {
obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED,
AudioService.STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget();
@@ -554,6 +560,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
* as a request to update the volume), the application will likely set a new volume. If the UI
* is still up, we need to refresh the display to show this new value.
*/
+ @Override
public void postHasNewRemotePlaybackInfo() {
if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return;
// don't create or prevent resources to be freed, if they disappear, this update came too
@@ -733,7 +740,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
int stream = (streamType == AudioService.STREAM_REMOTE_MUSIC) ? -1 : streamType;
// when the stream is for remote playback, use -1 to reset the stream type evaluation
mAudioManager.forceVolumeControlStream(stream);
- mDialog.setContentView(mView);
+
// Showing dialog - use collapsed state
if (mShowCombinedVolumes) {
collapse();
@@ -787,7 +794,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
return;
}
- mVibrator.vibrate(VIBRATE_DURATION);
+ mVibrator.vibrate(VIBRATE_DURATION, AudioManager.STREAM_SYSTEM);
}
protected void onRemoteVolumeChanged(int streamType, int flags) {
@@ -865,6 +872,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
.setMessage(com.android.internal.R.string.safe_media_volume_warning)
.setPositiveButton(com.android.internal.R.string.yes,
new DialogInterface.OnClickListener() {
+ @Override
public void onClick(DialogInterface dialog, int which) {
mAudioService.disableSafeMediaVolume();
}
@@ -1021,39 +1029,48 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
sendMessage(obtainMessage(MSG_TIMEOUT));
}
- public void onProgressChanged(SeekBar seekBar, int progress,
- boolean fromUser) {
- final Object tag = seekBar.getTag();
- if (fromUser && tag instanceof StreamControl) {
- StreamControl sc = (StreamControl) tag;
- if (getStreamVolume(sc.streamType) != progress) {
- setStreamVolume(sc.streamType, progress, 0);
+ private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ final Object tag = seekBar.getTag();
+ if (fromUser && tag instanceof StreamControl) {
+ StreamControl sc = (StreamControl) tag;
+ if (getStreamVolume(sc.streamType) != progress) {
+ setStreamVolume(sc.streamType, progress, 0);
+ }
}
+ resetTimeout();
}
- resetTimeout();
- }
- public void onStartTrackingTouch(SeekBar seekBar) {
- }
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
- public void onStopTrackingTouch(SeekBar seekBar) {
- final Object tag = seekBar.getTag();
- if (tag instanceof StreamControl) {
- StreamControl sc = (StreamControl) tag;
- // because remote volume updates are asynchronous, AudioService might have received
- // a new remote volume value since the finger adjusted the slider. So when the
- // progress of the slider isn't being tracked anymore, adjust the slider to the last
- // "published" remote volume value, so the UI reflects the actual volume.
- if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
- seekBar.setProgress(getStreamVolume(AudioService.STREAM_REMOTE_MUSIC));
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ final Object tag = seekBar.getTag();
+ if (tag instanceof StreamControl) {
+ StreamControl sc = (StreamControl) tag;
+ // Because remote volume updates are asynchronous, AudioService
+ // might have received a new remote volume value since the
+ // finger adjusted the slider. So when the progress of the
+ // slider isn't being tracked anymore, adjust the slider to the
+ // last "published" remote volume value, so the UI reflects the
+ // actual volume.
+ if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
+ seekBar.setProgress(getStreamVolume(AudioService.STREAM_REMOTE_MUSIC));
+ }
}
}
- }
+ };
- public void onClick(View v) {
- if (v == mMoreButton) {
- expand();
+ private final View.OnClickListener mClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (v == mMoreButton) {
+ expand();
+ }
+ resetTimeout();
}
- resetTimeout();
- }
+ };
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index c450f3c..7bd1f56 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -16,6 +16,8 @@
package android.view;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -25,8 +27,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 +96,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 +260,7 @@ public abstract class Window {
*
* @see #onPreparePanel
*/
+ @Nullable
public View onCreatePanelView(int featureId);
/**
@@ -373,6 +389,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 +997,7 @@ public abstract class Window {
*
* @return View The current View with focus or null.
*/
+ @Nullable
public abstract View getCurrentFocus();
/**
@@ -988,10 +1006,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 +1052,7 @@ public abstract class Window {
*/
public void setBackgroundDrawableResource(int resid)
{
- setBackgroundDrawable(mContext.getResources().getDrawable(resid));
+ setBackgroundDrawable(mContext.getDrawable(resid));
}
/**
@@ -1328,4 +1348,123 @@ 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(Bundle options, SceneTransitionListener listener) {
+ }
+
+ /**
+ * A callback for Window 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 how the Activity's start Scene is faded in and when the enter scene
+ * is triggered to start.
+ * <p>When allow 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 allow is false, the Activity enter Scene and
+ * background fade will be triggered when the calling Activity's exit transition
+ * completes.</p>
+ * @param allow 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. The default value is true.
+ */
+ public void setAllowOverlappingEnterTransition(boolean allow) {
+ }
+
+ /**
+ * Controls how the Activity's Scene fades out and when the calling Activity's
+ * enter scene is triggered when finishing to return to a calling Activity.
+ * <p>When allow is true, the Scene will fade out quickly
+ * and inform the calling Activity to transition in when the fade completes.
+ * When allow is false, the calling Activity will transition in after
+ * the Activity's Scene has exited.
+ * </p>
+ * @param allow Set to true to have the Activity fade out as soon as possible
+ * and transition in the calling Activity. The default value is
+ * true.
+ */
+ public void setAllowOverlappingExitTransition(boolean allow) {
+ }
+
+ /**
+ * Start the exit transition.
+ * @hide
+ */
+ public Bundle startExitTransitionToCallee(Bundle options) {
+ return null;
+ }
+
+ /**
+ * Starts the transition back to the calling Activity.
+ * onTransitionEnd will be called on the current thread if there is no exit transition.
+ * @hide
+ */
+ public void startExitTransitionToCaller(Runnable onTransitionEnd) {
+ onTransitionEnd.run();
+ }
+
+ /** @hide */
+ public void restoreViewVisibilityAfterTransitionToCallee() {
+ }
+
+ /**
+ * 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/WindowAnimationFrameStats.aidl b/core/java/android/view/WindowAnimationFrameStats.aidl
new file mode 100644
index 0000000..77f544b
--- /dev/null
+++ b/core/java/android/view/WindowAnimationFrameStats.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;
+
+parcelable WindowAnimationFrameStats;
diff --git a/core/java/android/view/WindowAnimationFrameStats.java b/core/java/android/view/WindowAnimationFrameStats.java
new file mode 100644
index 0000000..c60b96c
--- /dev/null
+++ b/core/java/android/view/WindowAnimationFrameStats.java
@@ -0,0 +1,94 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains window animation frame statistics. For example, a window
+ * animation is usually performed when the application is transitioning from one
+ * activity to another. The frame statistics are a snapshot for the time interval
+ * from {@link #getStartTimeNano()} to {@link #getEndTimeNano()}.
+ * <p>
+ * The key idea is that in order to provide a smooth user experience the system should
+ * run window animations at a specific time interval obtained by calling {@link
+ * #getRefreshPeriodNano()}. If the system does not render a frame every refresh
+ * period the user will see irregular window transitions. The time when the frame was
+ * actually presented on the display by calling {@link #getFramePresentedTimeNano(int)}.
+ */
+public final class WindowAnimationFrameStats extends FrameStats implements Parcelable {
+ /**
+ * @hide
+ */
+ public WindowAnimationFrameStats() {
+ /* do nothing */
+ }
+
+ /**
+ * Initializes this isntance.
+ *
+ * @param refreshPeriodNano The display refresh period.
+ * @param framesPresentedTimeNano The presented frame times.
+ *
+ * @hide
+ */
+ public void init(long refreshPeriodNano, long[] framesPresentedTimeNano) {
+ mRefreshPeriodNano = refreshPeriodNano;
+ mFramesPresentedTimeNano = framesPresentedTimeNano;
+ }
+
+ private WindowAnimationFrameStats(Parcel parcel) {
+ mRefreshPeriodNano = parcel.readLong();
+ mFramesPresentedTimeNano = parcel.createLongArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeLong(mRefreshPeriodNano);
+ parcel.writeLongArray(mFramesPresentedTimeNano);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("WindowAnimationFrameStats[");
+ builder.append("frameCount:" + getFrameCount());
+ builder.append(", fromTimeNano:" + getStartTimeNano());
+ builder.append(", toTimeNano:" + getEndTimeNano());
+ builder.append(']');
+ return builder.toString();
+ }
+
+ public static final Creator<WindowAnimationFrameStats> CREATOR =
+ new Creator<WindowAnimationFrameStats>() {
+ @Override
+ public WindowAnimationFrameStats createFromParcel(Parcel parcel) {
+ return new WindowAnimationFrameStats(parcel);
+ }
+
+ @Override
+ public WindowAnimationFrameStats[] newArray(int size) {
+ return new WindowAnimationFrameStats[size];
+ }
+ };
+}
diff --git a/core/java/android/view/WindowContentFrameStats.aidl b/core/java/android/view/WindowContentFrameStats.aidl
new file mode 100644
index 0000000..aa9c2d6
--- /dev/null
+++ b/core/java/android/view/WindowContentFrameStats.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;
+
+parcelable WindowContentFrameStats;
diff --git a/core/java/android/view/WindowContentFrameStats.java b/core/java/android/view/WindowContentFrameStats.java
new file mode 100644
index 0000000..c6da2fb
--- /dev/null
+++ b/core/java/android/view/WindowContentFrameStats.java
@@ -0,0 +1,152 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains window content frame statistics. For example, a window content
+ * is rendred in frames when a view is scrolled. The frame statistics are a snapshot
+ * for the time interval from {@link #getStartTimeNano()} to {@link #getEndTimeNano()}.
+ * <p>
+ * The key idea is that in order to provide a smooth user experience an application
+ * has to draw a frame at a specific time interval obtained by calling {@link
+ * #getRefreshPeriodNano()}. If the application does not render a frame every refresh
+ * period the user will see irregular UI transitions.
+ * </p>
+ * <p>
+ * An application posts a frame for presentation by synchronously rendering its contents
+ * in a buffer which is then posted or posting a buffer to which the application is
+ * asychronously rendering the content via GL. After the frame is posted and rendered
+ * (potentially asynchronosly) it is presented to the user. The time a frame was posted
+ * can be obtained via {@link #getFramePostedTimeNano(int)}, the time a frame content
+ * was rendered and ready for dsiplay (GL case) via {@link #getFrameReadyTimeNano(int)},
+ * and the time a frame was presented on the screen via {@link #getFramePresentedTimeNano(int)}.
+ * </p>
+ */
+public final class WindowContentFrameStats extends FrameStats implements Parcelable {
+ private long[] mFramesPostedTimeNano;
+ private long[] mFramesReadyTimeNano;
+
+ /**
+ * @hide
+ */
+ public WindowContentFrameStats() {
+ /* do nothing */
+ }
+
+ /**
+ * Initializes this isntance.
+ *
+ * @param refreshPeriodNano The display refresh period.
+ * @param framesPostedTimeNano The times in milliseconds for when the frame contents were posted.
+ * @param framesPresentedTimeNano The times in milliseconds for when the frame contents were presented.
+ * @param framesReadyTimeNano The times in milliseconds for when the frame contents were ready to be presented.
+ *
+ * @hide
+ */
+ public void init(long refreshPeriodNano, long[] framesPostedTimeNano,
+ long[] framesPresentedTimeNano, long[] framesReadyTimeNano) {
+ mRefreshPeriodNano = refreshPeriodNano;
+ mFramesPostedTimeNano = framesPostedTimeNano;
+ mFramesPresentedTimeNano = framesPresentedTimeNano;
+ mFramesReadyTimeNano = framesReadyTimeNano;
+ }
+
+ private WindowContentFrameStats(Parcel parcel) {
+ mRefreshPeriodNano = parcel.readLong();
+ mFramesPostedTimeNano = parcel.createLongArray();
+ mFramesPresentedTimeNano = parcel.createLongArray();
+ mFramesReadyTimeNano = parcel.createLongArray();
+ }
+
+ /**
+ * Get the time a frame at a given index was posted by the producer (e.g. the application).
+ * It is either explicitly set or defaulted to the time when the render buffer was posted.
+ * <p>
+ * <strong>Note:</strong> A frame can be posted and still it contents being rendered
+ * asynchronously in GL. To get the time the frame content was completely rendered and
+ * ready to display call {@link #getFrameReadyTimeNano(int)}.
+ * </p>
+ *
+ * @param index The frame index.
+ * @return The posted time in nanoseconds.
+ */
+ public long getFramePostedTimeNano(int index) {
+ if (mFramesPostedTimeNano == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mFramesPostedTimeNano[index];
+ }
+
+ /**
+ * Get the time a frame at a given index was ready for presentation.
+ * <p>
+ * <strong>Note:</strong> A frame can be posted and still it contents being rendered
+ * asynchronously in GL. In such a case this is the time when the frame contents were
+ * completely rendered.
+ * </p>
+ *
+ * @param index The frame index.
+ * @return The ready time in nanoseconds or {@link #UNDEFINED_TIME_NANO}
+ * if the frame is not ready yet.
+ */
+ public long getFrameReadyTimeNano(int index) {
+ if (mFramesReadyTimeNano == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mFramesReadyTimeNano[index];
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeLong(mRefreshPeriodNano);
+ parcel.writeLongArray(mFramesPostedTimeNano);
+ parcel.writeLongArray(mFramesPresentedTimeNano);
+ parcel.writeLongArray(mFramesReadyTimeNano);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("WindowContentFrameStats[");
+ builder.append("frameCount:" + getFrameCount());
+ builder.append(", fromTimeNano:" + getStartTimeNano());
+ builder.append(", toTimeNano:" + getEndTimeNano());
+ builder.append(']');
+ return builder.toString();
+ }
+
+ public static final Parcelable.Creator<WindowContentFrameStats> CREATOR =
+ new Creator<WindowContentFrameStats>() {
+ @Override
+ public WindowContentFrameStats createFromParcel(Parcel parcel) {
+ return new WindowContentFrameStats(parcel);
+ }
+
+ @Override
+ public WindowContentFrameStats[] newArray(int size) {
+ return new WindowContentFrameStats[size];
+ }
+ };
+}
diff --git a/core/java/android/view/WindowInfo.aidl b/core/java/android/view/WindowInfo.aidl
new file mode 100644
index 0000000..75b8fd2
--- /dev/null
+++ b/core/java/android/view/WindowInfo.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;
+
+parcelable WindowInfo;
diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java
new file mode 100644
index 0000000..7f89044
--- /dev/null
+++ b/core/java/android/view/WindowInfo.java
@@ -0,0 +1,164 @@
+/*
+ * 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;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pools;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents information about a window from the
+ * window manager to another part of the system.
+ *
+ * @hide
+ */
+public class WindowInfo implements Parcelable {
+ private static final int MAX_POOL_SIZE = 10;
+
+ private static final Pools.SynchronizedPool<WindowInfo> sPool =
+ new Pools.SynchronizedPool<WindowInfo>(MAX_POOL_SIZE);
+
+ public int type;
+ public int layer;
+ public IBinder token;
+ public IBinder parentToken;
+ public boolean focused;
+ public final Rect boundsInScreen = new Rect();
+ public List<IBinder> childTokens;
+
+ private WindowInfo() {
+ /* do nothing - hide constructor */
+ }
+
+ public static WindowInfo obtain() {
+ WindowInfo window = sPool.acquire();
+ if (window == null) {
+ window = new WindowInfo();
+ }
+ return window;
+ }
+
+ public static WindowInfo obtain(WindowInfo other) {
+ WindowInfo window = obtain();
+ window.type = other.type;
+ window.layer = other.layer;
+ window.token = other.token;
+ window.parentToken = other.parentToken;
+ window.focused = other.focused;
+ window.boundsInScreen.set(other.boundsInScreen);
+
+ if (other.childTokens != null && !other.childTokens.isEmpty()) {
+ if (window.childTokens == null) {
+ window.childTokens = new ArrayList<IBinder>(other.childTokens);
+ } else {
+ window.childTokens.addAll(other.childTokens);
+ }
+ }
+
+ return window;
+ }
+
+ public void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(type);
+ parcel.writeInt(layer);
+ parcel.writeStrongBinder(token);
+ parcel.writeStrongBinder(parentToken);
+ parcel.writeInt(focused ? 1 : 0);
+ boundsInScreen.writeToParcel(parcel, flags);
+
+ if (childTokens != null && !childTokens.isEmpty()) {
+ parcel.writeInt(1);
+ parcel.writeBinderList(childTokens);
+ } else {
+ parcel.writeInt(0);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("WindowInfo[");
+ builder.append("type=").append(type);
+ builder.append(", layer=").append(layer);
+ builder.append(", token=").append(token);
+ builder.append(", parent=").append(parentToken);
+ builder.append(", focused=").append(focused);
+ builder.append(", children=").append(childTokens);
+ builder.append(']');
+ return builder.toString();
+ }
+
+ private void initFromParcel(Parcel parcel) {
+ type = parcel.readInt();
+ layer = parcel.readInt();
+ token = parcel.readStrongBinder();
+ parentToken = parcel.readStrongBinder();
+ focused = (parcel.readInt() == 1);
+ boundsInScreen.readFromParcel(parcel);
+
+ final boolean hasChildren = (parcel.readInt() == 1);
+ if (hasChildren) {
+ if (childTokens == null) {
+ childTokens = new ArrayList<IBinder>();
+ }
+ parcel.readBinderList(childTokens);
+ }
+ }
+
+ private void clear() {
+ type = 0;
+ layer = 0;
+ token = null;
+ parentToken = null;
+ focused = false;
+ boundsInScreen.setEmpty();
+ if (childTokens != null) {
+ childTokens.clear();
+ }
+ }
+
+ public static final Parcelable.Creator<WindowInfo> CREATOR =
+ new Creator<WindowInfo>() {
+ @Override
+ public WindowInfo createFromParcel(Parcel parcel) {
+ WindowInfo window = obtain();
+ window.initFromParcel(parcel);
+ return window;
+ }
+
+ @Override
+ public WindowInfo[] newArray(int size) {
+ return new WindowInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d5a7d33..032a82f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -49,7 +49,7 @@ import android.util.Log;
public interface WindowManager extends ViewManager {
/**
* Exception that is thrown when trying to add view whose
- * {@link WindowManager.LayoutParams} {@link WindowManager.LayoutParams#token}
+ * {@link LayoutParams} {@link LayoutParams#token}
* is invalid.
*/
public static class BadTokenException extends RuntimeException {
@@ -173,7 +173,6 @@ public interface WindowManager extends ViewManager {
* @see #TYPE_SEARCH_BAR
* @see #TYPE_PHONE
* @see #TYPE_SYSTEM_ALERT
- * @see #TYPE_KEYGUARD
* @see #TYPE_TOAST
* @see #TYPE_SYSTEM_OVERLAY
* @see #TYPE_PRIORITY_PHONE
@@ -197,7 +196,6 @@ public interface WindowManager extends ViewManager {
@ViewDebug.IntToString(from = TYPE_SEARCH_BAR, to = "TYPE_SEARCH_BAR"),
@ViewDebug.IntToString(from = TYPE_PHONE, to = "TYPE_PHONE"),
@ViewDebug.IntToString(from = TYPE_SYSTEM_ALERT, to = "TYPE_SYSTEM_ALERT"),
- @ViewDebug.IntToString(from = TYPE_KEYGUARD, to = "TYPE_KEYGUARD"),
@ViewDebug.IntToString(from = TYPE_TOAST, to = "TYPE_TOAST"),
@ViewDebug.IntToString(from = TYPE_SYSTEM_OVERLAY, to = "TYPE_SYSTEM_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_PRIORITY_PHONE, to = "TYPE_PRIORITY_PHONE"),
@@ -341,13 +339,13 @@ public interface WindowManager extends ViewManager {
* 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.
@@ -916,7 +914,6 @@ public interface WindowManager extends ViewManager {
*/
public static final int FLAG_NEEDS_MENU_KEY = 0x40000000;
-
/**
* Various behavioral options/flags. Default is none.
*
@@ -1090,6 +1087,14 @@ public interface WindowManager extends ViewManager {
public static final int PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR = 0x00000200;
/**
+ * Flag whether the current window is a keyguard window, meaning that it will hide all other
+ * windows behind it except for windows with flag {@link #FLAG_SHOW_WHEN_LOCKED} set.
+ * Further, this can only be set by {@link LayoutParams#TYPE_STATUS_BAR}.
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_KEYGUARD = 0x00000400;
+
+ /**
* Control flags that are private to the platform.
* @hide
*/
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index a1bd4bd..14dc356 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -16,7 +16,12 @@
package android.view;
+import android.graphics.Rect;
+import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
+import android.os.IBinder;
+
+import java.util.List;
/**
* Window manager local system service interface.
@@ -24,10 +29,136 @@ import android.hardware.display.DisplayManagerInternal;
* @hide Only for use within the system server.
*/
public abstract class WindowManagerInternal {
+
+ /**
+ * Interface to receive a callback when the windows reported for
+ * accessibility changed.
+ */
+ public interface WindowsForAccessibilityCallback {
+
+ /**
+ * Called when the windows for accessibility changed.
+ *
+ * @param windows The windows for accessibility.
+ */
+ public void onWindowsForAccessibilityChanged(List<WindowInfo> windows);
+ }
+
+ /**
+ * Callbacks for contextual changes that affect the screen magnification
+ * feature.
+ */
+ public interface MagnificationCallbacks {
+
+ /**
+ * Called when the bounds of the screen content that is magnified changed.
+ * Note that not the entire screen is magnified.
+ *
+ * @param bounds The bounds.
+ */
+ public void onMagnifedBoundsChanged(Region bounds);
+
+ /**
+ * Called when an application requests a rectangle on the screen to allow
+ * the client to apply the appropriate pan and scale.
+ *
+ * @param left The rectangle left.
+ * @param top The rectangle top.
+ * @param right The rectangle right.
+ * @param bottom The rectangle bottom.
+ */
+ public void onRectangleOnScreenRequested(int left, int top, int right, int bottom);
+
+ /**
+ * Notifies that the rotation changed.
+ *
+ * @param rotation The current rotation.
+ */
+ public void onRotationChanged(int rotation);
+
+ /**
+ * Notifies that the context of the user changed. For example, an application
+ * was started.
+ */
+ public void onUserContextChanged();
+ }
+
/**
* Request that the window manager call
* {@link DisplayManagerInternal#performTraversalInTransactionFromWindowManager}
* within a surface transaction at a later time.
*/
public abstract void requestTraversalFromDisplayManager();
-} \ No newline at end of file
+
+ /**
+ * Set by the accessibility layer to observe changes in the magnified region,
+ * rotation, and other window transformations related to display magnification
+ * as the window manager is responsible for doing the actual magnification
+ * and has access to the raw window data while the accessibility layer serves
+ * as a controller.
+ *
+ * @param callbacks The callbacks to invoke.
+ */
+ public abstract void setMagnificationCallbacks(MagnificationCallbacks callbacks);
+
+ /**
+ * Set by the accessibility layer to specify the magnification and panning to
+ * be applied to all windows that should be magnified.
+ *
+ * @param callbacks The callbacks to invoke.
+ *
+ * @see #setMagnificationCallbacks(MagnificationCallbacks)
+ */
+ public abstract void setMagnificationSpec(MagnificationSpec spec);
+
+ /**
+ * Gets the magnification and translation applied to a window given its token.
+ * Not all windows are magnified and the window manager policy determines which
+ * windows are magnified. The returned result also takes into account the compat
+ * scale if necessary.
+ *
+ * @param windowToken The window's token.
+ *
+ * @return The magnification spec for the window.
+ *
+ * @see #setMagnificationCallbacks(MagnificationCallbacks)
+ */
+ public abstract MagnificationSpec getCompatibleMagnificationSpecForWindow(
+ IBinder windowToken);
+
+ /**
+ * Sets a callback for observing which windows are touchable for the purposes
+ * of accessibility.
+ *
+ * @param callback The callback.
+ */
+ public abstract void setWindowsForAccessibilityCallback(
+ WindowsForAccessibilityCallback callback);
+
+ /**
+ * Sets a filter for manipulating the input event stream.
+ *
+ * @param filter The filter implementation.
+ */
+ public abstract void setInputFilter(IInputFilter filter);
+
+ /**
+ * Gets the token of the window that has input focus.
+ *
+ * @return The token.
+ */
+ public abstract IBinder getFocusedWindowToken();
+
+ /**
+ * @return Whether the keyguard is engaged.
+ */
+ public abstract boolean isKeyguardLocked();
+
+ /**
+ * Gets the frame of a window given its token.
+ *
+ * @param token The token.
+ * @param outBounds The frame to populate.
+ */
+ public abstract void getWindowFrame(IBinder token, Rect outBounds);
+}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 893db89..c9be54d 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
@@ -449,6 +453,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;
@@ -996,6 +1005,14 @@ public interface WindowManagerPolicy {
public void dismissKeyguardLw();
/**
+ * Ask the policy whether the Keyguard has drawn. If the Keyguard is disabled, this method
+ * returns true as soon as we know that Keyguard is disabled.
+ *
+ * @return true if the keyguard has drawn.
+ */
+ public boolean isKeyguardDrawnLw();
+
+ /**
* Given an orientation constant, returns the appropriate surface rotation,
* taking into account sensors, docking mode, rotation lock, and other factors.
*
@@ -1004,7 +1021,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
@@ -1019,7 +1037,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.
@@ -1068,7 +1087,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.
@@ -1095,6 +1114,7 @@ public interface WindowManagerPolicy {
* @see WindowManagerPolicy#USER_ROTATION_LOCKED
* @see WindowManagerPolicy#USER_ROTATION_FREE
*/
+ @UserRotationMode
public int getUserRotationMode();
/**
@@ -1105,12 +1125,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);
@@ -1133,6 +1153,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.
*
@@ -1168,11 +1193,4 @@ public interface WindowManagerPolicy {
* @return True if the window is a top level one.
*/
public boolean isTopLevelWindow(int windowType);
-
- /**
- * Sets the current touch exploration state.
- *
- * @param enabled Whether touch exploration is enabled.
- */
- public void setTouchExplorationEnabled(boolean enabled);
}
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
new file mode 100644
index 0000000..77d48e2
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2012 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;
+
+import android.os.Build;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.LongArray;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Cache for AccessibilityWindowInfos and AccessibilityNodeInfos.
+ * It is updated when windows change or nodes change.
+ */
+final class AccessibilityCache {
+
+ private static final String LOG_TAG = "AccessibilityCache";
+
+ private static final boolean DEBUG = false;
+
+ private static final boolean CHECK_INTEGRITY = Build.IS_DEBUGGABLE;
+
+ private final Object mLock = new Object();
+
+ private final LongArray mTempLongArray = new LongArray();
+
+ private final SparseArray<AccessibilityWindowInfo> mWindowCache =
+ new SparseArray<AccessibilityWindowInfo>();
+
+ private final SparseArray<LongSparseArray<AccessibilityNodeInfo>> mNodeCache =
+ new SparseArray<LongSparseArray<AccessibilityNodeInfo>>();
+
+ private final SparseArray<AccessibilityWindowInfo> mTempWindowArray =
+ new SparseArray<AccessibilityWindowInfo>();
+
+ public void addWindow(AccessibilityWindowInfo window) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Caching window: " + window.getId());
+ }
+ mWindowCache.put(window.getId(), window);
+ }
+ }
+
+ public void removeWindows(int[] windowIds) {
+ synchronized (mLock) {
+ final int windowCount = windowIds.length;
+ for (int i = 0; i < windowCount; i++) {
+ final int windowId = windowIds[i];
+ AccessibilityWindowInfo window = mWindowCache.get(windowId);
+ if (window != null) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Removing window: " + windowId);
+ }
+ window.recycle();
+ mWindowCache.remove(windowId);
+ }
+ clearNodesForWindowLocked(windowIds[i]);
+ }
+ }
+ }
+
+ /**
+ * Notifies the cache that the something in the UI changed. As a result
+ * the cache will either refresh some nodes or evict some nodes.
+ *
+ * @param event An event.
+ */
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ synchronized (mLock) {
+ final int eventType = event.getEventType();
+ switch (eventType) {
+ case AccessibilityEvent.TYPE_VIEW_FOCUSED:
+ case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
+ case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
+ case AccessibilityEvent.TYPE_VIEW_SELECTED:
+ case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
+ case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
+ refreshCachedNodeLocked(event.getWindowId(), event.getSourceNodeId());
+ } break;
+
+ case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
+ synchronized (mLock) {
+ final int windowId = event.getWindowId();
+ final long sourceId = event.getSourceNodeId();
+ if ((event.getContentChangeTypes()
+ & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
+ clearSubTreeLocked(windowId, sourceId);
+ } else {
+ refreshCachedNodeLocked(windowId, sourceId);
+ }
+ }
+ } break;
+
+ case AccessibilityEvent.TYPE_VIEW_SCROLLED: {
+ clearSubTreeLocked(event.getWindowId(), event.getSourceNodeId());
+ } break;
+ }
+ }
+
+ if (CHECK_INTEGRITY) {
+ checkIntegrity();
+ }
+ }
+
+ private void refreshCachedNodeLocked(int windowId, long sourceId) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Refreshing cached node.");
+ }
+
+ LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+ if (nodes == null) {
+ return;
+ }
+ AccessibilityNodeInfo cachedInfo = nodes.get(sourceId);
+ // If the source is not in the cache - nothing to do.
+ if (cachedInfo == null) {
+ return;
+ }
+ // The node changed so we will just refresh it right now.
+ if (cachedInfo.refresh(true)) {
+ return;
+ }
+ // Weird, we could not refresh. Just evict the entire sub-tree.
+ clearSubTreeLocked(windowId, sourceId);
+ }
+
+ /**
+ * Gets a cached {@link AccessibilityNodeInfo} given the id of the hosting
+ * window and the accessibility id of the node.
+ *
+ * @param windowId The id of the window hosting the node.
+ * @param accessibilityNodeId The info accessibility node id.
+ * @return The cached {@link AccessibilityNodeInfo} or null if such not found.
+ */
+ public AccessibilityNodeInfo getNode(int windowId, long accessibilityNodeId) {
+ synchronized(mLock) {
+ LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+ if (nodes == null) {
+ return null;
+ }
+ AccessibilityNodeInfo info = nodes.get(accessibilityNodeId);
+ if (info != null) {
+ // Return a copy since the client calls to AccessibilityNodeInfo#recycle()
+ // will wipe the data of the cached info.
+ info = AccessibilityNodeInfo.obtain(info);
+ }
+ if (DEBUG) {
+ Log.i(LOG_TAG, "get(" + accessibilityNodeId + ") = " + info);
+ }
+ return info;
+ }
+ }
+
+ public List<AccessibilityWindowInfo> getWindows() {
+ synchronized (mLock) {
+ final int windowCount = mWindowCache.size();
+ if (windowCount > 0) {
+ // Careful to return the windows in a decreasing layer order.
+ SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray;
+ sortedWindows.clear();
+
+ for (int i = 0; i < windowCount; i++) {
+ AccessibilityWindowInfo window = mWindowCache.valueAt(i);
+ sortedWindows.put(window.getLayer(), window);
+ }
+
+ List<AccessibilityWindowInfo> windows = new ArrayList<AccessibilityWindowInfo>();
+ for (int i = windowCount - 1; i >= 0; i--) {
+ AccessibilityWindowInfo window = sortedWindows.valueAt(i);
+ windows.add(AccessibilityWindowInfo.obtain(window));
+ }
+
+ sortedWindows.clear();
+
+ return windows;
+ }
+ return null;
+ }
+ }
+
+ public AccessibilityWindowInfo getWindow(int windowId) {
+ synchronized (mLock) {
+ AccessibilityWindowInfo window = mWindowCache.get(windowId);
+ if (window != null) {
+ return AccessibilityWindowInfo.obtain(window);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Caches an {@link AccessibilityNodeInfo}.
+ *
+ * @param info The node to cache.
+ */
+ public void add(AccessibilityNodeInfo info) {
+ synchronized(mLock) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "add(" + info + ")");
+ }
+
+ final int windowId = info.getWindowId();
+ LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+ if (nodes == null) {
+ nodes = new LongSparseArray<AccessibilityNodeInfo>();
+ mNodeCache.put(windowId, nodes);
+ }
+
+ final long sourceId = info.getSourceNodeId();
+ AccessibilityNodeInfo oldInfo = nodes.get(sourceId);
+ if (oldInfo != null) {
+ // If the added node is in the cache we have to be careful if
+ // the new one represents a source state where some of the
+ // children have been removed to remove the descendants that
+ // are no longer present.
+ final LongArray newChildrenIds = info.getChildNodeIds();
+ if (newChildrenIds != null) {
+ // Cache the new ids as we will do some lookups.
+ LongArray newChildNodeIds = mTempLongArray;
+ final int newChildCount = newChildNodeIds.size();
+ for (int i = 0; i < newChildCount; i++) {
+ newChildNodeIds.add(newChildrenIds.get(i));
+ }
+
+ final int oldChildCount = oldInfo.getChildCount();
+ for (int i = 0; i < oldChildCount; i++) {
+ final long oldChildId = oldInfo.getChildId(i);
+ if (newChildNodeIds.indexOf(oldChildId) < 0) {
+ clearSubTreeLocked(windowId, oldChildId);
+ }
+ }
+
+ newChildNodeIds.clear();
+ }
+
+ // Also be careful if the parent has changed since the new
+ // parent may be a predecessor of the old parent which will
+ // add cyclse to the cache.
+ final long oldParentId = oldInfo.getParentNodeId();
+ if (info.getParentNodeId() != oldParentId) {
+ clearSubTreeLocked(windowId, oldParentId);
+ }
+ }
+
+ // Cache a copy since the client calls to AccessibilityNodeInfo#recycle()
+ // will wipe the data of the cached info.
+ AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(info);
+ nodes.put(sourceId, clone);
+ }
+ }
+
+ /**
+ * Clears the cache.
+ */
+ public void clear() {
+ synchronized(mLock) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "clear()");
+ }
+ final int windowCount = mWindowCache.size();
+ for (int i = windowCount - 1; i >= 0; i--) {
+ AccessibilityWindowInfo window = mWindowCache.valueAt(i);
+ window.recycle();
+ mWindowCache.removeAt(i);
+ }
+ final int nodesForWindowCount = mNodeCache.size();
+ for (int i = 0; i < nodesForWindowCount; i++) {
+ final int windowId = mNodeCache.keyAt(i);
+ clearNodesForWindowLocked(windowId);
+ }
+ }
+ }
+
+ private void clearNodesForWindowLocked(int windowId) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "clearWindowLocked(" + windowId + ")");
+ }
+ LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+ if (nodes == null) {
+ return;
+ }
+ // Recycle the nodes before clearing the cache.
+ final int nodeCount = nodes.size();
+ for (int i = nodeCount - 1; i >= 0; i--) {
+ AccessibilityNodeInfo info = nodes.valueAt(i);
+ nodes.removeAt(i);
+ info.recycle();
+ }
+ mNodeCache.remove(windowId);
+ }
+
+ /**
+ * Clears a subtree rooted at the node with the given id that is
+ * hosted in a given window.
+ *
+ * @param windowId The id of the hosting window.
+ * @param rootNodeId The root id.
+ */
+ private void clearSubTreeLocked(int windowId, long rootNodeId) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Clearing cached subtree.");
+ }
+ LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId);
+ if (nodes != null) {
+ clearSubTreeRecursiveLocked(nodes, rootNodeId);
+ }
+ }
+
+ /**
+ * Clears a subtree given a pointer to the root id and the nodes
+ * in the hosting window.
+ *
+ * @param nodes The nodes in the hosting window.
+ * @param rootNodeId The id of the root to evict.
+ */
+ private void clearSubTreeRecursiveLocked(LongSparseArray<AccessibilityNodeInfo> nodes,
+ long rootNodeId) {
+ AccessibilityNodeInfo current = nodes.get(rootNodeId);
+ if (current == null) {
+ return;
+ }
+ nodes.remove(rootNodeId);
+ final int childCount = current.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final long childNodeId = current.getChildId(i);
+ clearSubTreeRecursiveLocked(nodes, childNodeId);
+ }
+ }
+
+ /**
+ * Check the integrity of the cache which is nodes from different windows
+ * are not mixed, there is a single active window, there is a single focused
+ * window, for every window there are no duplicates nodes, all nodes for a
+ * window are connected, for every window there is a single input focused
+ * node, and for every window there is a single accessibility focused node.
+ */
+ public void checkIntegrity() {
+ synchronized (mLock) {
+ // Get the root.
+ if (mWindowCache.size() <= 0 && mNodeCache.size() == 0) {
+ return;
+ }
+
+ AccessibilityWindowInfo focusedWindow = null;
+ AccessibilityWindowInfo activeWindow = null;
+
+ final int windowCount = mWindowCache.size();
+ for (int i = 0; i < windowCount; i++) {
+ AccessibilityWindowInfo window = mWindowCache.valueAt(i);
+
+ // Check for one active window.
+ if (window.isActive()) {
+ if (activeWindow != null) {
+ Log.e(LOG_TAG, "Duplicate active window:" + window);
+ } else {
+ activeWindow = window;
+ }
+ }
+
+ // Check for one focused window.
+ if (window.isFocused()) {
+ if (focusedWindow != null) {
+ Log.e(LOG_TAG, "Duplicate focused window:" + window);
+ } else {
+ focusedWindow = window;
+ }
+ }
+ }
+
+ // Traverse the tree and do some checks.
+ AccessibilityNodeInfo accessFocus = null;
+ AccessibilityNodeInfo inputFocus = null;
+
+ final int nodesForWindowCount = mNodeCache.size();
+ for (int i = 0; i < nodesForWindowCount; i++) {
+ LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.valueAt(i);
+ if (nodes.size() <= 0) {
+ continue;
+ }
+
+ ArraySet<AccessibilityNodeInfo> seen = new ArraySet<AccessibilityNodeInfo>();
+ final int windowId = mNodeCache.keyAt(i);
+
+ final int nodeCount = nodes.size();
+ for (int j = 0; j < nodeCount; j++) {
+ AccessibilityNodeInfo node = nodes.valueAt(j);
+
+ // Check for duplicates
+ if (!seen.add(node)) {
+ Log.e(LOG_TAG, "Duplicate node: " + node
+ + " in window:" + windowId);
+ }
+
+ // Check for one accessibility focus.
+ if (node.isAccessibilityFocused()) {
+ if (accessFocus != null) {
+ Log.e(LOG_TAG, "Duplicate accessibility focus:" + node
+ + " in window:" + windowId);
+ } else {
+ accessFocus = node;
+ }
+ }
+
+ // Check for one input focus.
+ if (node.isFocused()) {
+ if (inputFocus != null) {
+ Log.e(LOG_TAG, "Duplicate input focus: " + node
+ + " in window:" + windowId);
+ } else {
+ inputFocus = node;
+ }
+ }
+
+ // The node should be a child of its parent if we have the parent.
+ AccessibilityNodeInfo nodeParent = nodes.get(node.getParentNodeId());
+ if (nodeParent != null) {
+ boolean childOfItsParent = false;
+ final int childCount = nodeParent.getChildCount();
+ for (int k = 0; k < childCount; k++) {
+ AccessibilityNodeInfo child = nodes.get(nodeParent.getChildId(k));
+ if (child == node) {
+ childOfItsParent = true;
+ break;
+ }
+ }
+ if (!childOfItsParent) {
+ Log.e(LOG_TAG, "Invalid parent-child ralation between parent: "
+ + nodeParent + " and child: " + node);
+ }
+ }
+
+ // The node should be the parent of its child if we have the child.
+ final int childCount = node.getChildCount();
+ for (int k = 0; k < childCount; k++) {
+ AccessibilityNodeInfo child = nodes.get(node.getChildId(k));
+ if (child != null) {
+ AccessibilityNodeInfo parent = nodes.get(child.getParentNodeId());
+ if (parent != node) {
+ Log.e(LOG_TAG, "Invalid child-parent ralation between child: "
+ + node + " and parent: " + nodeParent);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index f635eee..417e22c 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -343,6 +343,23 @@ import java.util.List;
* view.</br>
* </p>
* <p>
+ * <b>Windows changed</b> - represents the event of changes in the windows shown on
+ * the screen such as a window appeared, a window disappeared, a window size changed,
+ * a window layer changed, etc.</br>
+ * <em>Type:</em> {@link #TYPE_WINDOWS_CHANGED}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * </ul>
+ * <em>Note:</em> You can retrieve the {@link AccessibilityWindowInfo} for the window
+ * source of the event via {@link AccessibilityEvent#getSource()} to get the source
+ * node on which then call {@link AccessibilityNodeInfo#getWindow()
+ * AccessibilityNodeInfo.getWindow()} to get the window. Also all windows on the screen can
+ * be retrieved by a call to {@link android.accessibilityservice.AccessibilityService#getWindows()
+ * android.accessibilityservice.AccessibilityService.getWindows()}.
+ * </p>
+ * <p>
* <b>NOTIFICATION TYPES</b></br>
* </p>
* <p>
@@ -662,6 +679,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000;
/**
+ * Represents the event change in the windows shown on the screen.
+ */
+ public static final int TYPE_WINDOWS_CHANGED = 0x00400000;
+
+ /**
* Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
* The type of change is not defined.
*/
@@ -708,6 +730,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* @see #TYPE_GESTURE_DETECTION_END
* @see #TYPE_TOUCH_INTERACTION_START
* @see #TYPE_TOUCH_INTERACTION_END
+ * @see #TYPE_WINDOWS_CHANGED
*/
public static final int TYPES_ALL_MASK = 0xFFFFFFFF;
@@ -722,7 +745,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 +778,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 +794,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 +806,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 +819,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 +995,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 +1047,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 +1073,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 +1186,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(":");
@@ -1350,6 +1389,13 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
builder.append("TYPE_TOUCH_INTERACTION_END");
eventTypeCount++;
} break;
+ case TYPE_WINDOWS_CHANGED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_WINDOWS_CHANGED");
+ eventTypeCount++;
+ } break;
}
}
if (eventTypeCount > 1) {
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 139df3e..5b9372d 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;
@@ -101,14 +100,11 @@ public final class AccessibilityInteractionClient
private Message mSameThreadMessage;
- // The connection cache is shared between all interrogating threads.
private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
new SparseArray<IAccessibilityServiceConnection>();
- // The connection cache is shared between all interrogating threads since
- // at any given time there is only one window allowing querying.
- private static final AccessibilityNodeInfoCache sAccessibilityNodeInfoCache =
- new AccessibilityNodeInfoCache();
+ private static final AccessibilityCache sAccessibilityCache =
+ new AccessibilityCache();
/**
* @return The client for the current thread.
@@ -167,6 +163,99 @@ public final class AccessibilityInteractionClient
}
/**
+ * Gets the root {@link AccessibilityNodeInfo} in a given window.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param windowId The window id.
+ * @return The root {@link AccessibilityNodeInfo} if found, null otherwise.
+ */
+ public AccessibilityNodeInfo getRootInWindow(int connectionId, int windowId) {
+ return findAccessibilityNodeInfoByAccessibilityId(connectionId, windowId,
+ AccessibilityNodeInfo.ROOT_NODE_ID, false,
+ AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
+ }
+
+ /**
+ * Gets the info for a window.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @return The {@link AccessibilityWindowInfo}.
+ */
+ public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ AccessibilityWindowInfo window = sAccessibilityCache.getWindow(
+ accessibilityWindowId);
+ if (window != null) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Window cache hit");
+ }
+ return window;
+ }
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Window cache miss");
+ }
+ window = connection.getWindow(accessibilityWindowId);
+ if (window != null) {
+ sAccessibilityCache.addWindow(window);
+ return window;
+ }
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while calling remote getWindow", re);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the info for all windows.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @return The {@link AccessibilityWindowInfo} list.
+ */
+ public List<AccessibilityWindowInfo> getWindows(int connectionId) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ List<AccessibilityWindowInfo> windows = sAccessibilityCache.getWindows();
+ if (windows != null) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Window cache hit");
+ }
+ return windows;
+ }
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Window cache miss");
+ }
+ windows = connection.getWindows();
+ if (windows != null) {
+ final int windowCount = windows.size();
+ for (int i = 0; i < windowCount; i++) {
+ AccessibilityWindowInfo window = windows.get(i);
+ sAccessibilityCache.addWindow(window);
+ }
+ return windows;
+ }
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while calling remote getWindows", re);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
* Finds an {@link AccessibilityNodeInfo} by accessibility id.
*
* @param connectionId The id of a connection for interacting with the system.
@@ -184,15 +273,26 @@ public final class AccessibilityInteractionClient
public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId,
int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache,
int prefetchFlags) {
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0
+ && (prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) == 0) {
+ throw new IllegalArgumentException("FLAG_PREFETCH_SIBLINGS"
+ + " requires FLAG_PREFETCH_PREDECESSORS");
+ }
try {
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
if (!bypassCache) {
- AccessibilityNodeInfo cachedInfo = sAccessibilityNodeInfoCache.get(
- accessibilityNodeId);
+ AccessibilityNodeInfo cachedInfo = sAccessibilityCache.getNode(
+ accessibilityWindowId, accessibilityNodeId);
if (cachedInfo != null) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Node cache hit");
+ }
return cachedInfo;
}
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Node cache miss");
+ }
}
final int interactionId = mInteractionIdCounter.getAndIncrement();
final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId(
@@ -213,10 +313,8 @@ public final class AccessibilityInteractionClient
}
}
} catch (RemoteException re) {
- if (DEBUG) {
- Log.w(LOG_TAG, "Error while calling remote"
- + " findAccessibilityNodeInfoByAccessibilityId", re);
- }
+ Log.e(LOG_TAG, "Error while calling remote"
+ + " findAccessibilityNodeInfoByAccessibilityId", re);
}
return null;
}
@@ -260,10 +358,8 @@ public final class AccessibilityInteractionClient
}
}
} catch (RemoteException re) {
- if (DEBUG) {
- Log.w(LOG_TAG, "Error while calling remote"
- + " findAccessibilityNodeInfoByViewIdInActiveWindow", re);
- }
+ Log.w(LOG_TAG, "Error while calling remote"
+ + " findAccessibilityNodeInfoByViewIdInActiveWindow", re);
}
return Collections.emptyList();
}
@@ -308,10 +404,8 @@ public final class AccessibilityInteractionClient
}
}
} catch (RemoteException re) {
- if (DEBUG) {
- Log.w(LOG_TAG, "Error while calling remote"
- + " findAccessibilityNodeInfosByViewText", re);
- }
+ Log.w(LOG_TAG, "Error while calling remote"
+ + " findAccessibilityNodeInfosByViewText", re);
}
return Collections.emptyList();
}
@@ -353,9 +447,7 @@ public final class AccessibilityInteractionClient
}
}
} catch (RemoteException re) {
- if (DEBUG) {
- Log.w(LOG_TAG, "Error while calling remote findFocus", re);
- }
+ Log.w(LOG_TAG, "Error while calling remote findFocus", re);
}
return null;
}
@@ -397,9 +489,7 @@ public final class AccessibilityInteractionClient
}
}
} catch (RemoteException re) {
- if (DEBUG) {
- Log.w(LOG_TAG, "Error while calling remote accessibilityFocusSearch", re);
- }
+ Log.w(LOG_TAG, "Error while calling remote accessibilityFocusSearch", re);
}
return null;
}
@@ -437,19 +527,21 @@ public final class AccessibilityInteractionClient
}
}
} catch (RemoteException re) {
- if (DEBUG) {
- Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re);
- }
+ Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re);
}
return false;
}
public void clearCache() {
- sAccessibilityNodeInfoCache.clear();
+ sAccessibilityCache.clear();
}
public void onAccessibilityEvent(AccessibilityEvent event) {
- sAccessibilityNodeInfoCache.onAccessibilityEvent(event);
+ sAccessibilityCache.onAccessibilityEvent(event);
+ }
+
+ public void removeWindows(int[] windowIds) {
+ sAccessibilityCache.removeWindows(windowIds);
}
/**
@@ -614,7 +706,7 @@ public final class AccessibilityInteractionClient
if (info != null) {
info.setConnectionId(connectionId);
info.setSealed(true);
- sAccessibilityNodeInfoCache.add(info);
+ sAccessibilityCache.add(info);
}
}
@@ -718,10 +810,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..cbc38c6 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -75,13 +75,49 @@ 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;
- private static final int DO_SET_STATE = 10;
+ private final Object mLock = new Object();
- final IAccessibilityManager mService;
+ private IAccessibilityManager mService;
final int mUserId;
@@ -130,29 +166,14 @@ public final class AccessibilityManager {
public void onTouchExplorationStateChanged(boolean enabled);
}
- final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() {
+ private final IAccessibilityManagerClient.Stub mClient =
+ new IAccessibilityManagerClient.Stub() {
public void setState(int state) {
- mHandler.obtainMessage(DO_SET_STATE, state, 0).sendToTarget();
- }
- };
-
- class MyHandler extends Handler {
-
- MyHandler(Looper mainLooper) {
- super(mainLooper);
- }
-
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case DO_SET_STATE :
- setState(message.arg1);
- return;
- default :
- Log.w(LOG_TAG, "Unknown message type: " + message.what);
+ synchronized (mLock) {
+ setStateLocked(state);
}
}
- }
+ };
/**
* Get an AccessibilityManager instance (create one if necessary).
@@ -198,26 +219,29 @@ public final class AccessibilityManager {
mHandler = new MyHandler(context.getMainLooper());
mService = service;
mUserId = userId;
- if (mService == null) {
- mIsEnabled = false;
- }
- try {
- if (mService != null) {
- final int stateFlags = mService.addClient(mClient, userId);
- setState(stateFlags);
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
+ synchronized (mLock) {
+ tryConnectToServiceLocked();
}
}
/**
+ * @hide
+ */
+ public IAccessibilityManagerClient getClient() {
+ return mClient;
+ }
+
+ /**
* Returns if the accessibility in the system is enabled.
*
* @return True if accessibility is enabled, false otherwise.
*/
public boolean isEnabled() {
- synchronized (mHandler) {
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
return mIsEnabled;
}
}
@@ -228,24 +252,16 @@ public final class AccessibilityManager {
* @return True if touch exploration is enabled, false otherwise.
*/
public boolean isTouchExplorationEnabled() {
- synchronized (mHandler) {
+ synchronized (mLock) {
+ IAccessibilityManager service = getServiceLocked();
+ if (service == null) {
+ return false;
+ }
return mIsTouchExplorationEnabled;
}
}
/**
- * Returns the client interface this instance registers in
- * the centralized accessibility manager service.
- *
- * @return The client.
- *
- * @hide
- */
- public IAccessibilityManagerClient getClient() {
- return (IAccessibilityManagerClient) mClient.asBinder();
- }
-
- /**
* Sends an {@link AccessibilityEvent}.
*
* @param event The event to send.
@@ -259,8 +275,17 @@ public final class AccessibilityManager {
* their descendants.
*/
public void sendAccessibilityEvent(AccessibilityEvent event) {
- if (!mIsEnabled) {
- throw new IllegalStateException("Accessibility off. Did you forget to check that?");
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ if (!mIsEnabled) {
+ throw new IllegalStateException("Accessibility off. Did you forget to check that?");
+ }
+ userId = mUserId;
}
boolean doRecycle = false;
try {
@@ -269,7 +294,7 @@ public final class AccessibilityManager {
// client using it is called through Binder from another process. Example: MMS
// app adds a SMS notification and the NotificationManagerService calls this method
long identityToken = Binder.clearCallingIdentity();
- doRecycle = mService.sendAccessibilityEvent(event, mUserId);
+ doRecycle = service.sendAccessibilityEvent(event, userId);
Binder.restoreCallingIdentity(identityToken);
if (DEBUG) {
Log.i(LOG_TAG, event + " sent");
@@ -287,11 +312,20 @@ public final class AccessibilityManager {
* Requests feedback interruption from all accessibility services.
*/
public void interrupt() {
- if (!mIsEnabled) {
- throw new IllegalStateException("Accessibility off. Did you forget to check that?");
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ if (!mIsEnabled) {
+ throw new IllegalStateException("Accessibility off. Did you forget to check that?");
+ }
+ userId = mUserId;
}
try {
- mService.interrupt(mUserId);
+ service.interrupt(userId);
if (DEBUG) {
Log.i(LOG_TAG, "Requested interrupt from all services");
}
@@ -325,18 +359,30 @@ public final class AccessibilityManager {
* @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
*/
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return Collections.emptyList();
+ }
+ userId = mUserId;
+ }
+
List<AccessibilityServiceInfo> services = null;
try {
- if (mService != null) {
- services = mService.getInstalledAccessibilityServiceList(mUserId);
- if (DEBUG) {
- Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
- }
+ services = service.getInstalledAccessibilityServiceList(userId);
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
}
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
}
- return services != null ? Collections.unmodifiableList(services) : Collections.EMPTY_LIST;
+ if (services != null) {
+ return Collections.unmodifiableList(services);
+ } else {
+ return Collections.emptyList();
+ }
}
/**
@@ -354,18 +400,30 @@ public final class AccessibilityManager {
*/
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
int feedbackTypeFlags) {
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return Collections.emptyList();
+ }
+ userId = mUserId;
+ }
+
List<AccessibilityServiceInfo> services = null;
try {
- if (mService != null) {
- services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags, mUserId);
- if (DEBUG) {
- Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
- }
+ services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
}
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
}
- return services != null ? Collections.unmodifiableList(services) : Collections.EMPTY_LIST;
+ if (services != null) {
+ return Collections.unmodifiableList(services);
+ } else {
+ return Collections.emptyList();
+ }
}
/**
@@ -377,6 +435,7 @@ public final class AccessibilityManager {
*/
public boolean addAccessibilityStateChangeListener(
AccessibilityStateChangeListener listener) {
+ // Final CopyOnArrayList - no lock needed.
return mAccessibilityStateChangeListeners.add(listener);
}
@@ -388,6 +447,7 @@ public final class AccessibilityManager {
*/
public boolean removeAccessibilityStateChangeListener(
AccessibilityStateChangeListener listener) {
+ // Final CopyOnArrayList - no lock needed.
return mAccessibilityStateChangeListeners.remove(listener);
}
@@ -400,6 +460,7 @@ public final class AccessibilityManager {
*/
public boolean addTouchExplorationStateChangeListener(
TouchExplorationStateChangeListener listener) {
+ // Final CopyOnArrayList - no lock needed.
return mTouchExplorationStateChangeListeners.add(listener);
}
@@ -411,6 +472,7 @@ public final class AccessibilityManager {
*/
public boolean removeTouchExplorationStateChangeListener(
TouchExplorationStateChangeListener listener) {
+ // Final CopyOnArrayList - no lock needed.
return mTouchExplorationStateChangeListeners.remove(listener);
}
@@ -419,50 +481,24 @@ public final class AccessibilityManager {
*
* @param stateFlags The state flags.
*/
- private void setState(int stateFlags) {
+ private void setStateLocked(int stateFlags) {
final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
final boolean touchExplorationEnabled =
(stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
- synchronized (mHandler) {
- final boolean wasEnabled = mIsEnabled;
- final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
- // Ensure listeners get current state from isZzzEnabled() calls.
- mIsEnabled = enabled;
- mIsTouchExplorationEnabled = touchExplorationEnabled;
+ final boolean wasEnabled = mIsEnabled;
+ final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
- if (wasEnabled != enabled) {
- notifyAccessibilityStateChangedLh();
- }
+ // Ensure listeners get current state from isZzzEnabled() calls.
+ mIsEnabled = enabled;
+ mIsTouchExplorationEnabled = touchExplorationEnabled;
- if (wasTouchExplorationEnabled != touchExplorationEnabled) {
- notifyTouchExplorationStateChangedLh();
- }
+ if (wasEnabled != enabled) {
+ mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED);
}
- }
- /**
- * Notifies the registered {@link AccessibilityStateChangeListener}s.
- * <p>
- * The caller must be locked on {@link #mHandler}.
- */
- private void notifyAccessibilityStateChangedLh() {
- final int listenerCount = mAccessibilityStateChangeListeners.size();
- for (int i = 0; i < listenerCount; i++) {
- mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(mIsEnabled);
- }
- }
-
- /**
- * Notifies the registered {@link TouchExplorationStateChangeListener}s.
- * <p>
- * The caller must be locked on {@link #mHandler}.
- */
- private void notifyTouchExplorationStateChangedLh() {
- final int listenerCount = mTouchExplorationStateChangeListeners.size();
- for (int i = 0; i < listenerCount; i++) {
- mTouchExplorationStateChangeListeners.get(i)
- .onTouchExplorationStateChanged(mIsTouchExplorationEnabled);
+ if (wasTouchExplorationEnabled != touchExplorationEnabled) {
+ mHandler.sendEmptyMessage(MyHandler.MSG_NOTIFY_EXPLORATION_STATE_CHANGED);
}
}
@@ -475,11 +511,17 @@ public final class AccessibilityManager {
*/
public int addAccessibilityInteractionConnection(IWindow windowToken,
IAccessibilityInteractionConnection connection) {
- if (mService == null) {
- return View.NO_ID;
+ final IAccessibilityManager service;
+ final int userId;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return View.NO_ID;
+ }
+ userId = mUserId;
}
try {
- return mService.addAccessibilityInteractionConnection(windowToken, connection, mUserId);
+ return service.addAccessibilityInteractionConnection(windowToken, connection, userId);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
}
@@ -493,12 +535,90 @@ public final class AccessibilityManager {
* @hide
*/
public void removeAccessibilityInteractionConnection(IWindow windowToken) {
- try {
- if (mService != null) {
- mService.removeAccessibilityInteractionConnection(windowToken);
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
}
+ }
+ try {
+ service.removeAccessibilityInteractionConnection(windowToken);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
}
}
+
+ private IAccessibilityManager getServiceLocked() {
+ if (mService == null) {
+ tryConnectToServiceLocked();
+ }
+ return mService;
+ }
+
+ private void tryConnectToServiceLocked() {
+ IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+ if (iBinder == null) {
+ return;
+ }
+ IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder);
+ try {
+ final int stateFlags = service.addClient(mClient, mUserId);
+ setStateLocked(stateFlags);
+ mService = service;
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
+ }
+ }
+
+ /**
+ * Notifies the registered {@link AccessibilityStateChangeListener}s.
+ */
+ private void handleNotifyAccessibilityStateChanged() {
+ final boolean isEnabled;
+ synchronized (mLock) {
+ isEnabled = mIsEnabled;
+ }
+ final int listenerCount = mAccessibilityStateChangeListeners.size();
+ for (int i = 0; i < listenerCount; i++) {
+ mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(isEnabled);
+ }
+ }
+
+ /**
+ * Notifies the registered {@link TouchExplorationStateChangeListener}s.
+ */
+ private void handleNotifyTouchExplorationStateChanged() {
+ final boolean isTouchExplorationEnabled;
+ synchronized (mLock) {
+ isTouchExplorationEnabled = mIsTouchExplorationEnabled;
+ }
+ final int listenerCount = mTouchExplorationStateChangeListeners.size();
+ for (int i = 0; i < listenerCount; i++) {
+ mTouchExplorationStateChangeListeners.get(i)
+ .onTouchExplorationStateChanged(isTouchExplorationEnabled);
+ }
+ }
+
+ private final class MyHandler extends Handler {
+ public static final int MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED = 1;
+ public static final int MSG_NOTIFY_EXPLORATION_STATE_CHANGED = 2;
+
+ public MyHandler(Looper looper) {
+ super(looper, null, false);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_NOTIFY_ACCESSIBILITY_STATE_CHANGED: {
+ handleNotifyAccessibilityStateChanged();
+ } break;
+
+ case MSG_NOTIFY_EXPLORATION_STATE_CHANGED: {
+ handleNotifyTouchExplorationStateChanged();
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 4f53c1e..9d10930 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;
@@ -62,13 +62,22 @@ public class AccessibilityNodeInfo implements Parcelable {
private static final boolean DEBUG = false;
/** @hide */
- public static final int UNDEFINED = -1;
+ public static final int UNDEFINED_CONNECTION_ID = -1;
/** @hide */
- public static final long ROOT_NODE_ID = makeNodeId(UNDEFINED, UNDEFINED);
+ public static final int UNDEFINED_SELECTION_INDEX = -1;
/** @hide */
- public static final int ACTIVE_WINDOW_ID = UNDEFINED;
+ public static final int UNDEFINED_ITEM_ID = Integer.MAX_VALUE;
+
+ /** @hide */
+ public static final long ROOT_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID);
+
+ /** @hide */
+ public static final int ACTIVE_WINDOW_ID = UNDEFINED_ITEM_ID;
+
+ /** @hide */
+ public static final int ANY_WINDOW_ID = -2;
/** @hide */
public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;
@@ -282,6 +291,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 +376,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
/**
@@ -476,6 +513,13 @@ public class AccessibilityNodeInfo implements Parcelable {
* @hide
*/
public static long makeNodeId(int accessibilityViewId, int virtualDescendantId) {
+ // We changed the value for undefined node to positive due to wrong
+ // global id composition (two 32-bin ints into one 64-bit long) but
+ // the value used for the host node provider view has id -1 so we
+ // remap it here.
+ if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
+ virtualDescendantId = UNDEFINED_ITEM_ID;
+ }
return (((long) virtualDescendantId) << VIRTUAL_DESCENDANT_ID_SHIFT) | accessibilityViewId;
}
@@ -487,7 +531,7 @@ public class AccessibilityNodeInfo implements Parcelable {
private boolean mSealed;
// Data.
- private int mWindowId = UNDEFINED;
+ private int mWindowId = UNDEFINED_ITEM_ID;
private long mSourceNodeId = ROOT_NODE_ID;
private long mParentNodeId = ROOT_NODE_ID;
private long mLabelForId = ROOT_NODE_ID;
@@ -503,19 +547,19 @@ 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;
- private int mTextSelectionStart = UNDEFINED;
- private int mTextSelectionEnd = UNDEFINED;
+ private int mTextSelectionStart = UNDEFINED_SELECTION_INDEX;
+ private int mTextSelectionEnd = UNDEFINED_SELECTION_INDEX;
private int mInputType = InputType.TYPE_NULL;
private int mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE;
private Bundle mExtras;
- private int mConnectionId = UNDEFINED;
+ private int mConnectionId = UNDEFINED_CONNECTION_ID;
private RangeInfo mRangeInfo;
private CollectionInfo mCollectionInfo;
@@ -539,7 +583,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param source The info source.
*/
public void setSource(View source) {
- setSource(source, UNDEFINED);
+ setSource(source, UNDEFINED_ITEM_ID);
}
/**
@@ -563,9 +607,9 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public void setSource(View root, int virtualDescendantId) {
enforceNotSealed();
- mWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED;
+ mWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED_ITEM_ID;
final int rootAccessibilityViewId =
- (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
}
@@ -666,21 +710,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 +757,9 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public AccessibilityNodeInfo getChild(int index) {
enforceSealed();
+ if (mChildNodeIds == null) {
+ return null;
+ }
if (!canPerformRequestOverConnection(mSourceNodeId)) {
return null;
}
@@ -721,7 +782,35 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void addChild(View child) {
- addChild(child, UNDEFINED);
+ addChildInternal(child, UNDEFINED_ITEM_ID, 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_ITEM_ID, 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_ITEM_ID);
}
/**
@@ -739,12 +828,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();
+ if (mChildNodeIds == null) {
+ mChildNodeIds = new LongArray();
+ }
+ final int rootAccessibilityViewId =
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
+ final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ // 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 int index = mChildNodeIds.size();
+ final LongArray childIds = mChildNodeIds;
+ if (childIds == null) {
+ return false;
+ }
final int rootAccessibilityViewId =
- (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
final long childNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
- mChildNodeIds.put(index, childNodeId);
+ final int index = childIds.indexOf(childNodeId);
+ if (index < 0) {
+ return false;
+ }
+ childIds.remove(index);
+ return true;
}
/**
@@ -789,6 +915,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
@@ -915,6 +1059,22 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Gets the window to which this node belongs.
+ *
+ * @return The window.
+ *
+ * @see android.accessibilityservice.AccessibilityService#getWindows()
+ */
+ public AccessibilityWindowInfo getWindow() {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.getWindow(mConnectionId, mWindowId);
+ }
+
+ /**
* Gets the parent.
* <p>
* <strong>Note:</strong> It is a client responsibility to recycle the
@@ -931,7 +1091,8 @@ public class AccessibilityNodeInfo implements Parcelable {
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
- mWindowId, mParentNodeId, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
+ mWindowId, mParentNodeId, false, FLAG_PREFETCH_PREDECESSORS
+ | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
}
/**
@@ -956,7 +1117,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setParent(View parent) {
- setParent(parent, UNDEFINED);
+ setParent(parent, UNDEFINED_ITEM_ID);
}
/**
@@ -981,7 +1142,7 @@ public class AccessibilityNodeInfo implements Parcelable {
public void setParent(View root, int virtualDescendantId) {
enforceNotSealed();
final int rootAccessibilityViewId =
- (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
+ (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
mParentNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
}
@@ -1408,8 +1569,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();
@@ -1685,7 +1844,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param labeled The view for which this info serves as a label.
*/
public void setLabelFor(View labeled) {
- setLabelFor(labeled, UNDEFINED);
+ setLabelFor(labeled, UNDEFINED_ITEM_ID);
}
/**
@@ -1710,7 +1869,7 @@ public class AccessibilityNodeInfo implements Parcelable {
public void setLabelFor(View root, int virtualDescendantId) {
enforceNotSealed();
final int rootAccessibilityViewId = (root != null)
- ? root.getAccessibilityViewId() : UNDEFINED;
+ ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
mLabelForId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
}
@@ -1732,7 +1891,8 @@ public class AccessibilityNodeInfo implements Parcelable {
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
- mWindowId, mLabelForId, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
+ mWindowId, mLabelForId, false, FLAG_PREFETCH_PREDECESSORS
+ | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
}
/**
@@ -1742,7 +1902,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param label The view that labels this node's source.
*/
public void setLabeledBy(View label) {
- setLabeledBy(label, UNDEFINED);
+ setLabeledBy(label, UNDEFINED_ITEM_ID);
}
/**
@@ -1767,7 +1927,7 @@ public class AccessibilityNodeInfo implements Parcelable {
public void setLabeledBy(View root, int virtualDescendantId) {
enforceNotSealed();
final int rootAccessibilityViewId = (root != null)
- ? root.getAccessibilityViewId() : UNDEFINED;
+ ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
}
@@ -1789,7 +1949,8 @@ public class AccessibilityNodeInfo implements Parcelable {
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
- mWindowId, mLabeledById, false, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
+ mWindowId, mLabeledById, false, FLAG_PREFETCH_PREDECESSORS
+ | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
}
/**
@@ -1951,6 +2112,7 @@ public class AccessibilityNodeInfo implements Parcelable {
/**
* {@inheritDoc}
*/
+ @Override
public int describeContents() {
return 0;
}
@@ -2114,6 +2276,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 +2286,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 +2346,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 +2358,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 +2391,17 @@ 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.clear();
+ mChildNodeIds.addAll(otherChildNodeIds);
+ }
}
+
mTextSelectionStart = other.mTextSelectionStart;
mTextSelectionEnd = other.mTextSelectionEnd;
mInputType = other.mInputType;
@@ -2255,11 +2431,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 +2486,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 +2496,7 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
+ parcel.readInt() == 1,
parcel.readInt() == 1);
}
}
@@ -2328,10 +2510,12 @@ public class AccessibilityNodeInfo implements Parcelable {
mParentNodeId = ROOT_NODE_ID;
mLabelForId = ROOT_NODE_ID;
mLabeledById = ROOT_NODE_ID;
- mWindowId = UNDEFINED;
- mConnectionId = UNDEFINED;
+ mWindowId = UNDEFINED_ITEM_ID;
+ mConnectionId = UNDEFINED_CONNECTION_ID;
mMovementGranularities = 0;
- mChildNodeIds.clear();
+ if (mChildNodeIds != null) {
+ mChildNodeIds.clear();
+ }
mBoundsInParent.set(0, 0, 0, 0);
mBoundsInScreen.set(0, 0, 0, 0);
mBooleanProperties = 0;
@@ -2341,8 +2525,8 @@ public class AccessibilityNodeInfo implements Parcelable {
mContentDescription = null;
mViewIdResourceName = null;
mActions = 0;
- mTextSelectionStart = UNDEFINED;
- mTextSelectionEnd = UNDEFINED;
+ mTextSelectionStart = UNDEFINED_SELECTION_INDEX;
+ mTextSelectionEnd = UNDEFINED_SELECTION_INDEX;
mInputType = InputType.TYPE_NULL;
mLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE;
if (mExtras != null) {
@@ -2435,9 +2619,9 @@ public class AccessibilityNodeInfo implements Parcelable {
}
private boolean canPerformRequestOverConnection(long accessibilityNodeId) {
- return (mWindowId != UNDEFINED
- && getAccessibilityViewId(accessibilityNodeId) != UNDEFINED
- && mConnectionId != UNDEFINED);
+ return (mWindowId != UNDEFINED_ITEM_ID
+ && getAccessibilityViewId(accessibilityNodeId) != UNDEFINED_ITEM_ID
+ && mConnectionId != UNDEFINED_CONNECTION_ID);
}
@Override
@@ -2477,6 +2661,7 @@ public class AccessibilityNodeInfo implements Parcelable {
builder.append(super.toString());
if (DEBUG) {
+ builder.append("; sourceNodeId: " + mSourceNodeId);
builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId));
builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId));
builder.append("; mParentNodeId: " + mParentNodeId);
@@ -2493,12 +2678,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 +2855,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 +2872,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 +2894,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 +2930,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 +2968,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 +2993,7 @@ public class AccessibilityNodeInfo implements Parcelable {
mRowCount = 0;
mColumnCount = 0;
mHierarchical = false;
+ mSelectionMode = SELECTION_MODE_NONE;
}
}
@@ -2781,12 +3019,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 +3037,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 +3072,7 @@ public class AccessibilityNodeInfo implements Parcelable {
private int mRowIndex;
private int mColumnSpan;
private int mRowSpan;
+ private boolean mSelected;
/**
* Creates a new instance.
@@ -2820,13 +3083,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 +3140,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 +3162,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
deleted file mode 100644
index a9473a8..0000000
--- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2012 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;
-
-import android.os.Build;
-import android.util.Log;
-import android.util.LongSparseArray;
-import android.util.SparseLongArray;
-
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.Queue;
-
-/**
- * Simple cache for AccessibilityNodeInfos. The cache is mapping an
- * accessibility id to an info. The cache allows storing of
- * <code>null</code> values. It also tracks accessibility events
- * and invalidates accordingly.
- *
- * @hide
- */
-public class AccessibilityNodeInfoCache {
-
- private static final String LOG_TAG = AccessibilityNodeInfoCache.class.getSimpleName();
-
- private static final boolean ENABLED = true;
-
- private static final boolean DEBUG = false;
-
- private static final boolean CHECK_INTEGRITY_IF_DEBUGGABLE_BUILD = true;
-
- private final Object mLock = new Object();
-
- private final LongSparseArray<AccessibilityNodeInfo> mCacheImpl;
-
- private int mWindowId;
-
- public AccessibilityNodeInfoCache() {
- if (ENABLED) {
- mCacheImpl = new LongSparseArray<AccessibilityNodeInfo>();
- } else {
- mCacheImpl = null;
- }
- }
-
- /**
- * The cache keeps track of {@link AccessibilityEvent}s and invalidates
- * cached nodes as appropriate.
- *
- * @param event An event.
- */
- public void onAccessibilityEvent(AccessibilityEvent event) {
- if (ENABLED) {
- final int eventType = event.getEventType();
- switch (eventType) {
- case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:
- case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
- case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
- case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: {
- // If the active window changes, clear the cache.
- final int windowId = event.getWindowId();
- if (mWindowId != windowId) {
- mWindowId = windowId;
- clear();
- }
- } break;
- case AccessibilityEvent.TYPE_VIEW_FOCUSED:
- case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
- case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
- case AccessibilityEvent.TYPE_VIEW_SELECTED:
- case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
- case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
- refreshCachedNode(event.getSourceNodeId());
- } break;
- case AccessibilityEvent.TYPE_VIEW_SCROLLED: {
- synchronized (mLock) {
- clearSubTreeLocked(event.getSourceNodeId());
- }
- } break;
- case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
- synchronized (mLock) {
- final long sourceId = event.getSourceNodeId();
- if ((event.getContentChangeTypes()
- & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
- clearSubTreeLocked(sourceId);
- } else {
- refreshCachedNode(sourceId);
- }
- }
- } break;
- }
- if (CHECK_INTEGRITY_IF_DEBUGGABLE_BUILD && Build.IS_DEBUGGABLE) {
- checkIntegrity();
- }
- }
- }
-
- private void refreshCachedNode(long sourceId) {
- if (DEBUG) {
- Log.i(LOG_TAG, "Refreshing cached node.");
- }
- synchronized (mLock) {
- AccessibilityNodeInfo cachedInfo = mCacheImpl.get(sourceId);
- // If the source is not in the cache - nothing to do.
- if (cachedInfo == null) {
- return;
- }
- // The node changed so we will just refresh it right now.
- if (cachedInfo.refresh(true)) {
- return;
- }
- // Weird, we could not refresh. Just evict the entire sub-tree.
- clearSubTreeLocked(sourceId);
- }
- }
-
- /**
- * Gets a cached {@link AccessibilityNodeInfo} given its accessibility node id.
- *
- * @param accessibilityNodeId The info accessibility node id.
- * @return The cached {@link AccessibilityNodeInfo} or null if such not found.
- */
- public AccessibilityNodeInfo get(long accessibilityNodeId) {
- if (ENABLED) {
- synchronized(mLock) {
- AccessibilityNodeInfo info = mCacheImpl.get(accessibilityNodeId);
- if (info != null) {
- // Return a copy since the client calls to AccessibilityNodeInfo#recycle()
- // will wipe the data of the cached info.
- info = AccessibilityNodeInfo.obtain(info);
- }
- if (DEBUG) {
- Log.i(LOG_TAG, "get(" + accessibilityNodeId + ") = " + info);
- }
- return info;
- }
- } else {
- return null;
- }
- }
-
- /**
- * Caches an {@link AccessibilityNodeInfo} given its accessibility node id.
- *
- * @param info The {@link AccessibilityNodeInfo} to cache.
- */
- public void add(AccessibilityNodeInfo info) {
- if (ENABLED) {
- synchronized(mLock) {
- if (DEBUG) {
- Log.i(LOG_TAG, "add(" + info + ")");
- }
-
- final long sourceId = info.getSourceNodeId();
- AccessibilityNodeInfo oldInfo = mCacheImpl.get(sourceId);
- if (oldInfo != null) {
- // If the added node is in the cache we have to be careful if
- // 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);
- }
- }
-
- // Also be careful if the parent has changed since the new
- // parent may be a predecessor of the old parent which will
- // make the cached tree cyclic.
- final long oldParentId = oldInfo.getParentNodeId();
- if (info.getParentNodeId() != oldParentId) {
- clearSubTreeLocked(oldParentId);
- }
- }
-
- // Cache a copy since the client calls to AccessibilityNodeInfo#recycle()
- // will wipe the data of the cached info.
- AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(info);
- mCacheImpl.put(sourceId, clone);
- }
- }
- }
-
- /**
- * Clears the cache.
- */
- public void clear() {
- if (ENABLED) {
- synchronized(mLock) {
- if (DEBUG) {
- Log.i(LOG_TAG, "clear()");
- }
- // Recycle the nodes before clearing the cache.
- final int nodeCount = mCacheImpl.size();
- for (int i = 0; i < nodeCount; i++) {
- AccessibilityNodeInfo info = mCacheImpl.valueAt(i);
- info.recycle();
- }
- mCacheImpl.clear();
- }
- }
- }
-
- /**
- * Clears a subtree rooted at the node with the given id.
- *
- * @param rootNodeId The root id.
- */
- private void clearSubTreeLocked(long rootNodeId) {
- if (DEBUG) {
- Log.i(LOG_TAG, "Clearing cached subtree.");
- }
- clearSubTreeRecursiveLocked(rootNodeId);
- }
-
- private void clearSubTreeRecursiveLocked(long rootNodeId) {
- AccessibilityNodeInfo current = mCacheImpl.get(rootNodeId);
- if (current == null) {
- return;
- }
- mCacheImpl.remove(rootNodeId);
- SparseLongArray childNodeIds = current.getChildNodeIds();
- final int childCount = childNodeIds.size();
- for (int i = 0; i < childCount; i++) {
- final long childNodeId = childNodeIds.valueAt(i);
- clearSubTreeRecursiveLocked(childNodeId);
- }
- }
-
- /**
- * Check the integrity of the cache which is it does not have nodes
- * from more than one window, there are no duplicates, all nodes are
- * connected, there is a single input focused node, and there is a
- * single accessibility focused node.
- */
- private void checkIntegrity() {
- synchronized (mLock) {
- // Get the root.
- if (mCacheImpl.size() <= 0) {
- return;
- }
-
- // If the cache is a tree it does not matter from
- // which node we start to search for the root.
- AccessibilityNodeInfo root = mCacheImpl.valueAt(0);
- AccessibilityNodeInfo parent = root;
- while (parent != null) {
- root = parent;
- parent = mCacheImpl.get(parent.getParentNodeId());
- }
-
- // Traverse the tree and do some checks.
- final int windowId = root.getWindowId();
- AccessibilityNodeInfo accessFocus = null;
- AccessibilityNodeInfo inputFocus = null;
- HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>();
- Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
- fringe.add(root);
-
- while (!fringe.isEmpty()) {
- AccessibilityNodeInfo current = fringe.poll();
- // Check for duplicates
- if (!seen.add(current)) {
- Log.e(LOG_TAG, "Duplicate node: " + current);
- return;
- }
-
- // Check for one accessibility focus.
- if (current.isAccessibilityFocused()) {
- if (accessFocus != null) {
- Log.e(LOG_TAG, "Duplicate accessibility focus:" + current);
- } else {
- accessFocus = current;
- }
- }
-
- // Check for one input focus.
- if (current.isFocused()) {
- if (inputFocus != null) {
- Log.e(LOG_TAG, "Duplicate input focus: " + current);
- } else {
- inputFocus = current;
- }
- }
-
- SparseLongArray childIds = current.getChildNodeIds();
- final int childCount = childIds.size();
- for (int i = 0; i < childCount; i++) {
- final long childId = childIds.valueAt(i);
- AccessibilityNodeInfo child = mCacheImpl.get(childId);
- if (child != null) {
- fringe.add(child);
- }
- }
- }
-
- // 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);
- } else {
- Log.e(LOG_TAG, "Node from: " + info.getWindowId() + " not from:"
- + windowId + " " + info);
- }
- }
- }
- }
- }
-}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeProvider.java b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
index 718c32f..abcbb70 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeProvider.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
@@ -70,9 +70,14 @@ import java.util.List;
public abstract class AccessibilityNodeProvider {
/**
+ * The virtual id for the hosting View.
+ */
+ public static final int HOST_VIEW_ID = -1;
+
+ /**
* Returns an {@link AccessibilityNodeInfo} representing a virtual view,
* i.e. a descendant of the host View, with the given <code>virtualViewId</code>
- * or the host View itself if <code>virtualViewId</code> equals to {@link View#NO_ID}.
+ * or the host View itself if <code>virtualViewId</code> equals to {@link #HOST_VIEW_ID}.
* <p>
* A virtual descendant is an imaginary View that is reported as a part of the view
* hierarchy for accessibility purposes. This enables custom views that draw complex
@@ -99,7 +104,7 @@ public abstract class AccessibilityNodeProvider {
/**
* Performs an accessibility action on a virtual view, i.e. a descendant of the
* host View, with the given <code>virtualViewId</code> or the host View itself
- * if <code>virtualViewId</code> equals to {@link View#NO_ID}.
+ * if <code>virtualViewId</code> equals to {@link #HOST_VIEW_ID}.
*
* @param virtualViewId A client defined virtual view id.
* @param action The action to perform.
@@ -117,8 +122,8 @@ public abstract class AccessibilityNodeProvider {
/**
* Finds {@link AccessibilityNodeInfo}s by text. The match is case insensitive
* containment. The search is relative to the virtual view, i.e. a descendant of the
- * host View, with the given <code>virtualViewId</code> or the host View itself
- * <code>virtualViewId</code> equals to {@link View#NO_ID}.
+ * host View, with the given <code>virtualViewId</code> or the host View itself
+ * <code>virtualViewId</code> equals to {@link #HOST_VIEW_ID}.
*
* @param virtualViewId A client defined virtual view id which defined
* the root of the tree in which to perform the search.
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/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index 3fcd218..cc6a71d 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -78,7 +78,7 @@ public class AccessibilityRecord {
private boolean mIsInPool;
boolean mSealed;
- int mBooleanProperties = PROPERTY_IMPORTANT_FOR_ACCESSIBILITY;
+ int mBooleanProperties = 0;
int mCurrentItemIndex = UNDEFINED;
int mItemCount = UNDEFINED;
int mFromIndex = UNDEFINED;
@@ -791,7 +791,7 @@ public class AccessibilityRecord {
*/
void clear() {
mSealed = false;
- mBooleanProperties = PROPERTY_IMPORTANT_FOR_ACCESSIBILITY;
+ mBooleanProperties = 0;
mCurrentItemIndex = UNDEFINED;
mItemCount = UNDEFINED;
mFromIndex = UNDEFINED;
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.aidl b/core/java/android/view/accessibility/AccessibilityWindowInfo.aidl
new file mode 100644
index 0000000..fdb25fb
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.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 AccessibilityWindowInfo;
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
new file mode 100644
index 0000000..80b5c50
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -0,0 +1,574 @@
+/*
+ * 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;
+
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.LongArray;
+import android.util.Pools.SynchronizedPool;
+
+/**
+ * This class represents a state snapshot of a window for accessibility
+ * purposes. The screen content contains one or more windows where some
+ * windows can be descendants of other windows, which is the windows are
+ * hierarchically ordered. Note that there is no root window. Hence, the
+ * screen content can be seen as a collection of window trees.
+ */
+public final class AccessibilityWindowInfo implements Parcelable {
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * Window type: This is an application window. Such a window shows UI for
+ * interacting with an application.
+ */
+ public static final int TYPE_APPLICATION = 1;
+
+ /**
+ * Window type: This is an input method window. Such a window shows UI for
+ * inputting text such as keyboard, suggestions, etc.
+ */
+ public static final int TYPE_INPUT_METHOD = 2;
+
+ /**
+ * Window type: This is an system window. Such a window shows UI for
+ * interacting with the system.
+ */
+ public static final int TYPE_SYSTEM = 3;
+
+ private static final int UNDEFINED = -1;
+
+ private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0;
+ private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1;
+
+ // Housekeeping.
+ private static final int MAX_POOL_SIZE = 10;
+ private static final SynchronizedPool<AccessibilityWindowInfo> sPool =
+ new SynchronizedPool<AccessibilityWindowInfo>(MAX_POOL_SIZE);
+
+ // Data.
+ private int mType = UNDEFINED;
+ private int mLayer = UNDEFINED;
+ private int mBooleanProperties;
+ private int mId = UNDEFINED;
+ private int mParentId = UNDEFINED;
+ private final Rect mBoundsInScreen = new Rect();
+ private LongArray mChildIds;
+
+ private int mConnectionId = UNDEFINED;
+
+ private AccessibilityWindowInfo() {
+ /* do nothing - hide constructor */
+ }
+
+ /**
+ * Gets the type of the window.
+ *
+ * @return The type.
+ *
+ * @see #TYPE_APPLICATION
+ * @see #TYPE_INPUT_METHOD
+ * @see #TYPE_SYSTEM
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Sets the type of the window.
+ *
+ * @param The type
+ *
+ * @hide
+ */
+ public void setType(int type) {
+ mType = type;
+ }
+
+ /**
+ * Gets the layer which determines the Z-order of the window. Windows
+ * with greater layer appear on top of windows with lesser layer.
+ *
+ * @return The window layer.
+ */
+ public int getLayer() {
+ return mLayer;
+ }
+
+ /**
+ * Sets the layer which determines the Z-order of the window. Windows
+ * with greater layer appear on top of windows with lesser layer.
+ *
+ * @param The window layer.
+ *
+ * @hide
+ */
+ public void setLayer(int layer) {
+ mLayer = layer;
+ }
+
+ /**
+ * Gets the root node in the window's hierarchy.
+ *
+ * @return The root node.
+ */
+ public AccessibilityNodeInfo getRoot() {
+ if (mConnectionId == UNDEFINED) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
+ mId, AccessibilityNodeInfo.ROOT_NODE_ID,
+ true, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
+ }
+
+ /**
+ * Gets the parent window if such.
+ *
+ * @return The parent window.
+ */
+ public AccessibilityWindowInfo getParent() {
+ if (mConnectionId == UNDEFINED || mParentId == UNDEFINED) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.getWindow(mConnectionId, mParentId);
+ }
+
+ /**
+ * Sets the parent window id.
+ *
+ * @param parentId The parent id.
+ *
+ * @hide
+ */
+ public void setParentId(int parentId) {
+ mParentId = parentId;
+ }
+
+ /**
+ * Gets the unique window id.
+ *
+ * @return windowId The window id.
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Sets the unique window id.
+ *
+ * @param windowId The window id.
+ *
+ * @hide
+ */
+ public void setId(int id) {
+ mId = id;
+ }
+
+ /**
+ * Sets the unique id of the IAccessibilityServiceConnection over which
+ * this instance can send requests to the system.
+ *
+ * @param connectionId The connection id.
+ *
+ * @hide
+ */
+ public void setConnectionId(int connectionId) {
+ mConnectionId = connectionId;
+ }
+
+ /**
+ * Gets the bounds of this window in the screen.
+ *
+ * @param outBounds The out window bounds.
+ */
+ public void getBoundsInScreen(Rect outBounds) {
+ outBounds.set(mBoundsInScreen);
+ }
+
+ /**
+ * Sets the bounds of this window in the screen.
+ *
+ * @param bounds The out window bounds.
+ *
+ * @hide
+ */
+ public void setBoundsInScreen(Rect bounds) {
+ mBoundsInScreen.set(bounds);
+ }
+
+ /**
+ * Gets if this window is active. An active window is the one
+ * the user is currently touching or the window has input focus
+ * and the user is not touching any window.
+ *
+ * @return Whether this is the active window.
+ */
+ public boolean isActive() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_ACTIVE);
+ }
+
+ /**
+ * Sets if this window is active, which is this is the window
+ * the user is currently touching or the window has input focus
+ * and the user is not touching any window.
+ *
+ * @param Whether this is the active window.
+ *
+ * @hide
+ */
+ public void setActive(boolean active) {
+ setBooleanProperty(BOOLEAN_PROPERTY_ACTIVE, active);
+ }
+
+ /**
+ * Gets if this window has input focus.
+ *
+ * @return Whether has input focus.
+ */
+ public boolean isFocused() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED);
+ }
+
+ /**
+ * Sets if this window has input focus.
+ *
+ * @param Whether has input focus.
+ *
+ * @hide
+ */
+ public void setFocused(boolean focused) {
+ setBooleanProperty(BOOLEAN_PROPERTY_FOCUSED, focused);
+ }
+
+ /**
+ * Gets the number of child windows.
+ *
+ * @return The child count.
+ */
+ public int getChildCount() {
+ return (mChildIds != null) ? mChildIds.size() : 0;
+ }
+
+ /**
+ * Gets the child window at a given index.
+ *
+ * @param index The index.
+ * @return The child.
+ */
+ public AccessibilityWindowInfo getChild(int index) {
+ if (mChildIds == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (mConnectionId == UNDEFINED) {
+ return null;
+ }
+ final int childId = (int) mChildIds.get(index);
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.getWindow(mConnectionId, childId);
+ }
+
+ /**
+ * Adds a child window.
+ *
+ * @param childId The child window id.
+ *
+ * @hide
+ */
+ public void addChild(int childId) {
+ if (mChildIds == null) {
+ mChildIds = new LongArray();
+ }
+ mChildIds.add(childId);
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * created.
+ *
+ * @return An instance.
+ */
+ public static AccessibilityWindowInfo obtain() {
+ AccessibilityWindowInfo info = sPool.acquire();
+ if (info == null) {
+ info = new AccessibilityWindowInfo();
+ }
+ return info;
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * created. The returned instance is initialized from the given
+ * <code>info</code>.
+ *
+ * @param info The other info.
+ * @return An instance.
+ */
+ public static AccessibilityWindowInfo obtain(AccessibilityWindowInfo info) {
+ AccessibilityWindowInfo infoClone = obtain();
+
+ infoClone.mType = info.mType;
+ infoClone.mLayer = info.mLayer;
+ infoClone.mBooleanProperties = info.mBooleanProperties;
+ infoClone.mId = info.mId;
+ infoClone.mParentId = info.mParentId;
+ infoClone.mBoundsInScreen.set(info.mBoundsInScreen);
+
+ if (info.mChildIds != null && info.mChildIds.size() > 0) {
+ if (infoClone.mChildIds == null) {
+ infoClone.mChildIds = info.mChildIds.clone();
+ } else {
+ infoClone.mChildIds.addAll(info.mChildIds);
+ }
+ }
+
+ infoClone.mConnectionId = info.mConnectionId;
+
+ return infoClone;
+ }
+
+ /**
+ * Return an instance back to be reused.
+ * <p>
+ * <strong>Note:</strong> You must not touch the object after calling this function.
+ * </p>
+ *
+ * @throws IllegalStateException If the info is already recycled.
+ */
+ public void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mType);
+ parcel.writeInt(mLayer);
+ parcel.writeInt(mBooleanProperties);
+ parcel.writeInt(mId);
+ parcel.writeInt(mParentId);
+ mBoundsInScreen.writeToParcel(parcel, flags);
+
+ final LongArray childIds = mChildIds;
+ if (childIds == null) {
+ parcel.writeInt(0);
+ } else {
+ final int childCount = childIds.size();
+ parcel.writeInt(childCount);
+ for (int i = 0; i < childCount; i++) {
+ parcel.writeInt((int) childIds.get(i));
+ }
+ }
+
+ parcel.writeInt(mConnectionId);
+ }
+
+ private void initFromParcel(Parcel parcel) {
+ mType = parcel.readInt();
+ mLayer = parcel.readInt();
+ mBooleanProperties = parcel.readInt();
+ mId = parcel.readInt();
+ mParentId = parcel.readInt();
+ mBoundsInScreen.readFromParcel(parcel);
+
+ final int childCount = parcel.readInt();
+ if (childCount > 0) {
+ if (mChildIds == null) {
+ mChildIds = new LongArray(childCount);
+ }
+ for (int i = 0; i < childCount; i++) {
+ final int childId = parcel.readInt();
+ mChildIds.add(childId);
+ }
+ }
+
+ mConnectionId = parcel.readInt();
+ }
+
+ @Override
+ public int hashCode() {
+ return mId;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ AccessibilityWindowInfo other = (AccessibilityWindowInfo) obj;
+ return (mId == other.mId);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("AccessibilityWindowInfo[");
+ builder.append("id=").append(mId);
+ builder.append(", type=").append(typeToString(mType));
+ builder.append(", layer=").append(mLayer);
+ builder.append(", bounds=").append(mBoundsInScreen);
+ builder.append(", focused=").append(isFocused());
+ builder.append(", active=").append(isActive());
+ if (DEBUG) {
+ builder.append(", parent=").append(mParentId);
+ builder.append(", children=[");
+ if (mChildIds != null) {
+ final int childCount = mChildIds.size();
+ for (int i = 0; i < childCount; i++) {
+ builder.append(mChildIds.get(i));
+ if (i < childCount - 1) {
+ builder.append(',');
+ }
+ }
+ } else {
+ builder.append("null");
+ }
+ builder.append(']');
+ } else {
+ builder.append(", hasParent=").append(mParentId != UNDEFINED);
+ builder.append(", hasChildren=").append(mChildIds != null
+ && mChildIds.size() > 0);
+ }
+ builder.append(']');
+ return builder.toString();
+ }
+
+ /**
+ * Clears the internal state.
+ */
+ private void clear() {
+ mType = UNDEFINED;
+ mLayer = UNDEFINED;
+ mBooleanProperties = 0;
+ mId = UNDEFINED;
+ mParentId = UNDEFINED;
+ mBoundsInScreen.setEmpty();
+ if (mChildIds != null) {
+ mChildIds.clear();
+ }
+ mConnectionId = UNDEFINED;
+ }
+
+ /**
+ * Gets the value of a boolean property.
+ *
+ * @param property The property.
+ * @return The value.
+ */
+ private boolean getBooleanProperty(int property) {
+ return (mBooleanProperties & property) != 0;
+ }
+
+ /**
+ * Sets a boolean property.
+ *
+ * @param property The property.
+ * @param value The value.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ private void setBooleanProperty(int property, boolean value) {
+ if (value) {
+ mBooleanProperties |= property;
+ } else {
+ mBooleanProperties &= ~property;
+ }
+ }
+
+ private static String typeToString(int type) {
+ switch (type) {
+ case TYPE_APPLICATION: {
+ return "TYPE_APPLICATION";
+ }
+ case TYPE_INPUT_METHOD: {
+ return "TYPE_INPUT_METHOD";
+ }
+ case TYPE_SYSTEM: {
+ return "TYPE_SYSTEM";
+ }
+ default:
+ return "<UNKNOWN>";
+ }
+ }
+
+ /**
+ * Checks whether this window changed. The argument should be
+ * another state of the same window, which is have the same id
+ * and type as they never change.
+ *
+ * @param other The new state.
+ * @return Whether something changed.
+ *
+ * @hide
+ */
+ public boolean changed(AccessibilityWindowInfo other) {
+ if (other.mId != mId) {
+ throw new IllegalArgumentException("Not same window.");
+ }
+ if (other.mType != mType) {
+ throw new IllegalArgumentException("Not same type.");
+ }
+ if (!mBoundsInScreen.equals(mBoundsInScreen)) {
+ return true;
+ }
+ if (mLayer != other.mLayer) {
+ return true;
+ }
+ if (mBooleanProperties != other.mBooleanProperties) {
+ return true;
+ }
+ if (mParentId != other.mParentId) {
+ return true;
+ }
+ if (mChildIds == null) {
+ if (other.mChildIds != null) {
+ return true;
+ }
+ } else if (!mChildIds.equals(other.mChildIds)) {
+ return true;
+ }
+ return false;
+ }
+
+ public static final Parcelable.Creator<AccessibilityWindowInfo> CREATOR =
+ new Creator<AccessibilityWindowInfo>() {
+ @Override
+ public AccessibilityWindowInfo createFromParcel(Parcel parcel) {
+ AccessibilityWindowInfo info = obtain();
+ info.initFromParcel(parcel);
+ return info;
+ }
+
+ @Override
+ public AccessibilityWindowInfo[] newArray(int size) {
+ return new AccessibilityWindowInfo[size];
+ }
+ };
+}
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/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index fe3e5c6..b6570cc 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -57,4 +57,6 @@ interface IAccessibilityManager {
void temporaryEnableAccessibilityStateUntilKeyguardRemoved(in ComponentName service,
boolean touchExplorationEnabled);
+
+ IBinder getWindowToken(int windowId);
}
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/ClipRectAnimation.java b/core/java/android/view/animation/ClipRectAnimation.java
new file mode 100644
index 0000000..2361501
--- /dev/null
+++ b/core/java/android/view/animation/ClipRectAnimation.java
@@ -0,0 +1,59 @@
+/*
+ * 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.animation;
+
+import android.graphics.Rect;
+
+/**
+ * An animation that controls the clip of an object. See the
+ * {@link android.view.animation full package} description for details and
+ * sample code.
+ *
+ * @hide
+ */
+public class ClipRectAnimation extends Animation {
+ private Rect mFromRect = new Rect();
+ private Rect mToRect = new Rect();
+
+ /**
+ * Constructor to use when building a ClipRectAnimation from code
+ *
+ * @param fromClip the clip rect to animate from
+ * @param toClip the clip rect to animate to
+ */
+ public ClipRectAnimation(Rect fromClip, Rect toClip) {
+ if (fromClip == null || toClip == null) {
+ throw new RuntimeException("Expected non-null animation clip rects");
+ }
+ mFromRect.set(fromClip);
+ mToRect.set(toClip);
+ }
+
+ @Override
+ protected void applyTransformation(float it, Transformation tr) {
+ int l = mFromRect.left + (int) ((mToRect.left - mFromRect.left) * it);
+ int t = mFromRect.top + (int) ((mToRect.top - mFromRect.top) * it);
+ int r = mFromRect.right + (int) ((mToRect.right - mFromRect.right) * it);
+ int b = mFromRect.bottom + (int) ((mToRect.bottom - mFromRect.bottom) * it);
+ tr.setClipRect(l, t, r, b);
+ }
+
+ @Override
+ public boolean willChangeTransformationMatrix() {
+ return false;
+ }
+}
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/animation/Transformation.java b/core/java/android/view/animation/Transformation.java
index 890909b..2f4fe73 100644
--- a/core/java/android/view/animation/Transformation.java
+++ b/core/java/android/view/animation/Transformation.java
@@ -17,6 +17,7 @@
package android.view.animation;
import android.graphics.Matrix;
+import android.graphics.Rect;
import java.io.PrintWriter;
@@ -47,6 +48,9 @@ public class Transformation {
protected float mAlpha;
protected int mTransformationType;
+ private boolean mHasClipRect;
+ private Rect mClipRect = new Rect();
+
/**
* Creates a new transformation with alpha = 1 and the identity matrix.
*/
@@ -65,6 +69,8 @@ public class Transformation {
} else {
mMatrix.reset();
}
+ mClipRect.setEmpty();
+ mHasClipRect = false;
mAlpha = 1.0f;
mTransformationType = TYPE_BOTH;
}
@@ -98,9 +104,15 @@ public class Transformation {
public void set(Transformation t) {
mAlpha = t.getAlpha();
mMatrix.set(t.getMatrix());
+ if (t.mHasClipRect) {
+ setClipRect(t.getClipRect());
+ } else {
+ mHasClipRect = false;
+ mClipRect.setEmpty();
+ }
mTransformationType = t.getTransformationType();
}
-
+
/**
* Apply this Transformation to an existing Transformation, e.g. apply
* a scale effect to something that has already been rotated.
@@ -109,6 +121,9 @@ public class Transformation {
public void compose(Transformation t) {
mAlpha *= t.getAlpha();
mMatrix.preConcat(t.getMatrix());
+ if (t.mHasClipRect) {
+ setClipRect(t.getClipRect());
+ }
}
/**
@@ -119,6 +134,9 @@ public class Transformation {
public void postCompose(Transformation t) {
mAlpha *= t.getAlpha();
mMatrix.postConcat(t.getMatrix());
+ if (t.mHasClipRect) {
+ setClipRect(t.getClipRect());
+ }
}
/**
@@ -138,6 +156,39 @@ public class Transformation {
}
/**
+ * Sets the current Transform's clip rect
+ * @hide
+ */
+ public void setClipRect(Rect r) {
+ setClipRect(r.left, r.top, r.right, r.bottom);
+ }
+
+ /**
+ * Sets the current Transform's clip rect
+ * @hide
+ */
+ public void setClipRect(int l, int t, int r, int b) {
+ mClipRect.set(l, t, r, b);
+ mHasClipRect = true;
+ }
+
+ /**
+ * Returns the current Transform's clip rect
+ * @hide
+ */
+ public Rect getClipRect() {
+ return mClipRect;
+ }
+
+ /**
+ * Returns whether the current Transform's clip rect is set
+ * @hide
+ */
+ public boolean hasClipRect() {
+ return mHasClipRect;
+ }
+
+ /**
* @return The degree of transparency
*/
public float getAlpha() {
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..e3bd9fd 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -26,6 +26,7 @@ import com.android.internal.view.InputBindResult;
import android.content.Context;
import android.graphics.Rect;
+import android.inputmethodservice.InputMethodService;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -41,13 +42,13 @@ import android.util.Pools.Pool;
import android.util.Pools.SimplePool;
import android.util.PrintWriterPrinter;
import android.util.Printer;
+import android.util.SparseArray;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventSender;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewRootImpl;
-import android.util.SparseArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -334,6 +335,11 @@ public final class InputMethodManager {
InputChannel mCurChannel;
ImeInputEventSender mCurSender;
+ /**
+ * The current cursor/anchor monitor mode.
+ */
+ int mCursorAnchorMonitorMode = InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_NONE;
+
final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20);
final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20);
@@ -346,6 +352,7 @@ public final class InputMethodManager {
static final int MSG_SEND_INPUT_EVENT = 5;
static final int MSG_TIMEOUT_INPUT_EVENT = 6;
static final int MSG_FLUSH_INPUT_EVENT = 7;
+ static final int SET_CURSOR_ANCHOR_MONITOR_MODE = 8;
class H extends Handler {
H(Looper looper) {
@@ -476,6 +483,12 @@ public final class InputMethodManager {
finishedInputEvent(msg.arg1, false, false);
return;
}
+ case SET_CURSOR_ANCHOR_MONITOR_MODE: {
+ synchronized (mH) {
+ mCursorAnchorMonitorMode = msg.arg1;
+ }
+ return;
+ }
}
}
}
@@ -540,6 +553,11 @@ public final class InputMethodManager {
public void setActive(boolean active) {
mH.sendMessage(mH.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, 0));
}
+
+ @Override
+ public void setCursorAnchorMonitorMode(int monitorMode) {
+ mH.sendMessage(mH.obtainMessage(SET_CURSOR_ANCHOR_MONITOR_MODE, monitorMode, 0));
+ }
};
final InputConnection mDummyInputConnection = new BaseInputConnection(this, false);
@@ -714,6 +732,7 @@ public final class InputMethodManager {
* Reset all of the state associated with being bound to an input method.
*/
void clearBindingLocked() {
+ if (DEBUG) Log.v(TAG, "Clearing binding!");
clearConnectionLocked();
setInputChannelLocked(null);
mBindSequence = -1;
@@ -1394,6 +1413,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) {
@@ -1456,9 +1483,30 @@ public final class InputMethodManager {
* of the input editor's cursor in its window.
*/
public boolean isWatchingCursor(View view) {
- return false;
+ if (!isActive(view)) {
+ return false;
+ }
+ synchronized (mH) {
+ return mCursorAnchorMonitorMode ==
+ InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT;
+ }
}
-
+
+ /**
+ * Set cursor/anchor monitor mode via {@link com.android.server.InputMethodManagerService}.
+ * This is an internal method for {@link android.inputmethodservice.InputMethodService} and
+ * should never be used from IMEs and applications.
+ *
+ * @hide
+ */
+ public void setCursorAnchorMonitorMode(IBinder imeToken, int monitorMode) {
+ try {
+ mService.setCursorAnchorMonitorMode(imeToken, monitorMode);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/**
* Report the current cursor location in its window.
*/
@@ -1805,6 +1853,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 +1902,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/PermissionRequest.java b/core/java/android/webkit/PermissionRequest.java
new file mode 100644
index 0000000..2f8850b
--- /dev/null
+++ b/core/java/android/webkit/PermissionRequest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.webkit;
+
+import android.net.Uri;
+
+/**
+ * This class wraps a permission request, and is used to request permission for
+ * the web content to access the resources.
+ *
+ * Either {@link #grant(long) grant()} or {@link #deny()} must be called to response the
+ * request, otherwise, {@link WebChromeClient#onPermissionRequest(PermissionRequest)} will
+ * not be invoked again if there is other permission request in this WebView.
+ *
+ * @hide
+ */
+public interface PermissionRequest {
+ /**
+ * Resource belongs to geolocation service.
+ */
+ public final static long RESOURCE_GEOLOCATION = 1 << 0;
+ /**
+ * Resource belongs to video capture device, like camera.
+ */
+ public final static long RESOURCE_VIDEO_CAPTURE = 1 << 1;
+ /**
+ * Resource belongs to audio capture device, like microphone.
+ */
+ public final static long RESOURCE_AUDIO_CAPTURE = 1 << 2;
+
+ /**
+ * @return the origin of web content which attempt to access the restricted
+ * resources.
+ */
+ public Uri getOrigin();
+
+ /**
+ * @return a bit mask of resources the web content wants to access.
+ */
+ public long getResources();
+
+ /**
+ * Call this method to grant origin the permission to access the given resources.
+ * The granted permission is only valid for this WebView.
+ *
+ * @param resources the resources granted to be accessed by origin, to grant
+ * request, the requested resources returned by {@link #getResources()}
+ * must be equals or a subset of granted resources.
+ * This parameter is designed to avoid granting permission by accident
+ * especially when new resources are requested by web content.
+ * Calling grant(getResources()) has security issue, the new permission
+ * will be granted without being noticed.
+ */
+ public void grant(long resources);
+
+ /**
+ * Call this method to deny the request.
+ */
+ public void deny();
+}
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/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index aa57423..60cba86 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -296,6 +296,30 @@ public class WebChromeClient {
public void onGeolocationPermissionsHidePrompt() {}
/**
+ * Notify the host application that web content is requesting permission to
+ * access the specified resources and the permission currently isn't granted
+ * or denied. The host application must invoke {@link PermissionRequest#grant(long)}
+ * or {@link PermissionRequest#deny()}.
+ *
+ * If this method isn't overridden, the permission is denied.
+ *
+ * @param request the PermissionRequest from current web content.
+ * @hide
+ */
+ public void onPermissionRequest(PermissionRequest request) {
+ request.deny();
+ }
+
+ /**
+ * Notify the host application that the given permission request
+ * has been canceled. Any related UI should therefore be hidden.
+ *
+ * @param request the PermissionRequest need be canceled.
+ * @hide
+ */
+ public void onPermissionRequestCanceled(PermissionRequest request) {}
+
+ /**
* Tell the client that a JavaScript execution timeout has occured. And the
* client may decide whether or not to interrupt the execution. If the
* client returns true, the JavaScript will be interrupted. If the client
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..62fbbc4 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -25,10 +25,10 @@ import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
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 +255,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 +449,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 +462,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 +491,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 +504,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 +515,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 +821,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 +846,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 +856,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);
+ }
}
/**
@@ -1598,6 +1641,21 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Preauthorize the given origin to access resources.
+ * This authorization only valid for this WebView instance life cycle and
+ * will not retained.
+ *
+ * @param origin the origin authorized to access resources
+ * @param resources the resource authorized to be accessed by origin.
+ *
+ * @hide
+ */
+ public void preauthorizePermission(Uri origin, long resources) {
+ checkThread();
+ mProvider.preauthorizePermission(origin, resources);
+ }
+
+ /**
* Sets the Picture listener. This is an interface used to receive
* notifications of a new Picture.
*
@@ -1649,6 +1707,9 @@ public class WebView extends AbsoluteLayout
* thread of this WebView. Care is therefore required to maintain thread
* safety.</li>
* <li> The Java object's fields are not accessible.</li>
+ * <li> For applications targeted to API level {@link android.os.Build.VERSION_CODES#L}
+ * and above, methods of injected Java objects are enumerable from
+ * JavaScript.</li>
* </ul>
*
* @param object the Java object to inject into this WebView's JavaScript
@@ -2097,10 +2158,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/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 696aad4..9488cdd 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -23,6 +23,7 @@ import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.net.http.SslCertificate;
import android.os.Bundle;
import android.os.Message;
@@ -245,6 +246,8 @@ public interface WebViewProvider {
public View findHierarchyView(String className, int hashCode);
+ public void preauthorizePermission(Uri origin, long resources);
+
//-------------------------------------------------------------------------
// Provider internal methods
//-------------------------------------------------------------------------
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index bbaa33d..301317e 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());
}
}
@@ -3039,7 +3114,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (event.isConfirmKey()) {
+ if (KeyEvent.isConfirmKey(keyCode)) {
if (!isEnabled()) {
return true;
}
@@ -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, -1, 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/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
new file mode 100644
index 0000000..e4575e5
--- /dev/null
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -0,0 +1,746 @@
+/*
+ * 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.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.util.SparseBooleanArray;
+import android.view.ActionProvider;
+import android.view.Gravity;
+import android.view.MenuItem;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.ListPopupWindow.ForwardingListener;
+import com.android.internal.transition.ActionBarTransition;
+import com.android.internal.view.ActionBarPolicy;
+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 {
+ private static final String TAG = "ActionMenuPresenter";
+
+ private View mOverflowButton;
+ private boolean mReserveOverflow;
+ private boolean mReserveOverflowSet;
+ private int mWidthLimit;
+ private int mActionItemWidthLimit;
+ private int mMaxItems;
+ private boolean mMaxItemsSet;
+ private boolean mStrictWidthLimit;
+ private boolean mWidthLimitSet;
+ private boolean mExpandedActionViewsExclusive;
+
+ private int mMinCellSize;
+
+ // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
+ private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
+
+ private View mScrapActionButtonView;
+
+ private OverflowPopup mOverflowPopup;
+ private ActionButtonSubmenu mActionButtonPopup;
+
+ private OpenOverflowRunnable mPostedOpenRunnable;
+ private ActionMenuPopupCallback mPopupCallback;
+
+ final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
+ int mOpenSubMenuId;
+
+ public ActionMenuPresenter(Context context) {
+ super(context, com.android.internal.R.layout.action_menu_layout,
+ com.android.internal.R.layout.action_menu_item_layout);
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ super.initForMenu(context, menu);
+
+ final Resources res = context.getResources();
+
+ final ActionBarPolicy abp = ActionBarPolicy.get(context);
+ if (!mReserveOverflowSet) {
+ mReserveOverflow = abp.showsOverflowMenuButton();
+ }
+
+ if (!mWidthLimitSet) {
+ mWidthLimit = abp.getEmbeddedMenuWidthLimit();
+ }
+
+ // Measure for initial configuration
+ if (!mMaxItemsSet) {
+ mMaxItems = abp.getMaxActionButtons();
+ }
+
+ int width = mWidthLimit;
+ if (mReserveOverflow) {
+ if (mOverflowButton == null) {
+ mOverflowButton = new OverflowMenuButton(mSystemContext);
+ final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ mOverflowButton.measure(spec, spec);
+ }
+ width -= mOverflowButton.getMeasuredWidth();
+ } else {
+ mOverflowButton = null;
+ }
+
+ mActionItemWidthLimit = width;
+
+ mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density);
+
+ // Drop a scrap view as it may no longer reflect the proper context/config.
+ mScrapActionButtonView = null;
+ }
+
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (!mMaxItemsSet) {
+ mMaxItems = mContext.getResources().getInteger(
+ com.android.internal.R.integer.max_action_buttons);
+ }
+ if (mMenu != null) {
+ mMenu.onItemsChanged(true);
+ }
+ }
+
+ public void setWidthLimit(int width, boolean strict) {
+ mWidthLimit = width;
+ mStrictWidthLimit = strict;
+ mWidthLimitSet = true;
+ }
+
+ public void setReserveOverflow(boolean reserveOverflow) {
+ mReserveOverflow = reserveOverflow;
+ mReserveOverflowSet = true;
+ }
+
+ public void setItemLimit(int itemCount) {
+ mMaxItems = itemCount;
+ mMaxItemsSet = true;
+ }
+
+ public void setExpandedActionViewsExclusive(boolean isExclusive) {
+ mExpandedActionViewsExclusive = isExclusive;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ MenuView result = super.getMenuView(root);
+ ((ActionMenuView) result).setPresenter(this);
+ return result;
+ }
+
+ @Override
+ public View getItemView(final MenuItemImpl item, View convertView, ViewGroup parent) {
+ View actionView = item.getActionView();
+ if (actionView == null || item.hasCollapsibleActionView()) {
+ actionView = super.getItemView(item, convertView, parent);
+ }
+ actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
+
+ final ActionMenuView menuParent = (ActionMenuView) parent;
+ final ViewGroup.LayoutParams lp = actionView.getLayoutParams();
+ if (!menuParent.checkLayoutParams(lp)) {
+ actionView.setLayoutParams(menuParent.generateLayoutParams(lp));
+ }
+ return actionView;
+ }
+
+ @Override
+ 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 (mPopupCallback == null) {
+ mPopupCallback = new ActionMenuPopupCallback();
+ }
+ actionItemView.setPopupCallback(mPopupCallback);
+ }
+
+ @Override
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ return item.isActionButton();
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
+ if (menuViewParent != null) {
+ ActionBarTransition.beginDelayedTransition(menuViewParent);
+ }
+ super.updateMenuView(cleared);
+
+ ((View) mMenuView).requestLayout();
+
+ if (mMenu != null) {
+ final ArrayList<MenuItemImpl> actionItems = mMenu.getActionItems();
+ final int count = actionItems.size();
+ for (int i = 0; i < count; i++) {
+ final ActionProvider provider = actionItems.get(i).getActionProvider();
+ if (provider != null) {
+ provider.setSubUiVisibilityListener(this);
+ }
+ }
+ }
+
+ final ArrayList<MenuItemImpl> nonActionItems = mMenu != null ?
+ mMenu.getNonActionItems() : null;
+
+ boolean hasOverflow = false;
+ if (mReserveOverflow && nonActionItems != null) {
+ final int count = nonActionItems.size();
+ if (count == 1) {
+ hasOverflow = !nonActionItems.get(0).isActionViewExpanded();
+ } else {
+ hasOverflow = count > 0;
+ }
+ }
+
+ if (hasOverflow) {
+ if (mOverflowButton == null) {
+ mOverflowButton = new OverflowMenuButton(mSystemContext);
+ }
+ ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
+ if (parent != mMenuView) {
+ if (parent != null) {
+ parent.removeView(mOverflowButton);
+ }
+ ActionMenuView menuView = (ActionMenuView) mMenuView;
+ menuView.addView(mOverflowButton, menuView.generateOverflowButtonLayoutParams());
+ }
+ } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
+ ((ViewGroup) mMenuView).removeView(mOverflowButton);
+ }
+
+ ((ActionMenuView) mMenuView).setOverflowReserved(mReserveOverflow);
+ }
+
+ @Override
+ public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ if (parent.getChildAt(childIndex) == mOverflowButton) return false;
+ return super.filterLeftoverView(parent, childIndex);
+ }
+
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (!subMenu.hasVisibleItems()) return false;
+
+ SubMenuBuilder topSubMenu = subMenu;
+ while (topSubMenu.getParentMenu() != mMenu) {
+ topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
+ }
+ View anchor = findViewForItem(topSubMenu.getItem());
+ if (anchor == null) {
+ if (mOverflowButton == null) return false;
+ anchor = mOverflowButton;
+ }
+
+ mOpenSubMenuId = subMenu.getItem().getItemId();
+ mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
+ mActionButtonPopup.setAnchorView(anchor);
+ mActionButtonPopup.show();
+ super.onSubMenuSelected(subMenu);
+ return true;
+ }
+
+ private View findViewForItem(MenuItem item) {
+ final ViewGroup parent = (ViewGroup) mMenuView;
+ if (parent == null) return null;
+
+ final int count = parent.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = parent.getChildAt(i);
+ if (child instanceof MenuView.ItemView &&
+ ((MenuView.ItemView) child).getItemData() == item) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Display the overflow menu if one is present.
+ * @return true if the overflow menu was shown, false otherwise.
+ */
+ public boolean showOverflowMenu() {
+ if (mReserveOverflow && !isOverflowMenuShowing() && mMenu != null && mMenuView != null &&
+ mPostedOpenRunnable == null && !mMenu.getNonActionItems().isEmpty()) {
+ OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
+ mPostedOpenRunnable = new OpenOverflowRunnable(popup);
+ // Post this for later; we might still need a layout for the anchor to be right.
+ ((View) mMenuView).post(mPostedOpenRunnable);
+
+ // ActionMenuPresenter uses null as a callback argument here
+ // to indicate overflow is opening.
+ super.onSubMenuSelected(null);
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Hide the overflow menu if it is currently showing.
+ *
+ * @return true if the overflow menu was hidden, false otherwise.
+ */
+ public boolean hideOverflowMenu() {
+ if (mPostedOpenRunnable != null && mMenuView != null) {
+ ((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
+ mPostedOpenRunnable = null;
+ return true;
+ }
+
+ MenuPopupHelper popup = mOverflowPopup;
+ if (popup != null) {
+ popup.dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Dismiss all popup menus - overflow and submenus.
+ * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+ */
+ public boolean dismissPopupMenus() {
+ boolean result = hideOverflowMenu();
+ result |= hideSubMenus();
+ return result;
+ }
+
+ /**
+ * Dismiss all submenu popups.
+ *
+ * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+ */
+ public boolean hideSubMenus() {
+ if (mActionButtonPopup != null) {
+ mActionButtonPopup.dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return true if the overflow menu is currently showing
+ */
+ public boolean isOverflowMenuShowing() {
+ return mOverflowPopup != null && mOverflowPopup.isShowing();
+ }
+
+ public boolean isOverflowMenuShowPending() {
+ return mPostedOpenRunnable != null || isOverflowMenuShowing();
+ }
+
+ /**
+ * @return true if space has been reserved in the action menu for an overflow item.
+ */
+ public boolean isOverflowReserved() {
+ return mReserveOverflow;
+ }
+
+ public boolean flagActionItems() {
+ final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
+ final int itemsSize = visibleItems.size();
+ int maxActions = mMaxItems;
+ int widthLimit = mActionItemWidthLimit;
+ final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final ViewGroup parent = (ViewGroup) mMenuView;
+
+ int requiredItems = 0;
+ int requestedItems = 0;
+ int firstActionWidth = 0;
+ boolean hasOverflow = false;
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (item.requiresActionButton()) {
+ requiredItems++;
+ } else if (item.requestsActionButton()) {
+ requestedItems++;
+ } else {
+ hasOverflow = true;
+ }
+ if (mExpandedActionViewsExclusive && item.isActionViewExpanded()) {
+ // Overflow everything if we have an expanded action view and we're
+ // space constrained.
+ maxActions = 0;
+ }
+ }
+
+ // Reserve a spot for the overflow item if needed.
+ if (mReserveOverflow &&
+ (hasOverflow || requiredItems + requestedItems > maxActions)) {
+ maxActions--;
+ }
+ maxActions -= requiredItems;
+
+ final SparseBooleanArray seenGroups = mActionButtonGroups;
+ seenGroups.clear();
+
+ int cellSize = 0;
+ int cellsRemaining = 0;
+ if (mStrictWidthLimit) {
+ cellsRemaining = widthLimit / mMinCellSize;
+ final int cellSizeRemaining = widthLimit % mMinCellSize;
+ cellSize = mMinCellSize + cellSizeRemaining / cellsRemaining;
+ }
+
+ // Flag as many more requested items as will fit.
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+
+ if (item.requiresActionButton()) {
+ View v = getItemView(item, mScrapActionButtonView, parent);
+ if (mScrapActionButtonView == null) {
+ mScrapActionButtonView = v;
+ }
+ if (mStrictWidthLimit) {
+ cellsRemaining -= ActionMenuView.measureChildForCells(v,
+ cellSize, cellsRemaining, querySpec, 0);
+ } else {
+ v.measure(querySpec, querySpec);
+ }
+ final int measuredWidth = v.getMeasuredWidth();
+ widthLimit -= measuredWidth;
+ if (firstActionWidth == 0) {
+ firstActionWidth = measuredWidth;
+ }
+ final int groupId = item.getGroupId();
+ if (groupId != 0) {
+ seenGroups.put(groupId, true);
+ }
+ item.setIsActionButton(true);
+ } else if (item.requestsActionButton()) {
+ // Items in a group with other items that already have an action slot
+ // can break the max actions rule, but not the width limit.
+ final int groupId = item.getGroupId();
+ final boolean inGroup = seenGroups.get(groupId);
+ boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0 &&
+ (!mStrictWidthLimit || cellsRemaining > 0);
+
+ if (isAction) {
+ View v = getItemView(item, mScrapActionButtonView, parent);
+ if (mScrapActionButtonView == null) {
+ mScrapActionButtonView = v;
+ }
+ if (mStrictWidthLimit) {
+ final int cells = ActionMenuView.measureChildForCells(v,
+ cellSize, cellsRemaining, querySpec, 0);
+ cellsRemaining -= cells;
+ if (cells == 0) {
+ isAction = false;
+ }
+ } else {
+ v.measure(querySpec, querySpec);
+ }
+ final int measuredWidth = v.getMeasuredWidth();
+ widthLimit -= measuredWidth;
+ if (firstActionWidth == 0) {
+ firstActionWidth = measuredWidth;
+ }
+
+ if (mStrictWidthLimit) {
+ isAction &= widthLimit >= 0;
+ } else {
+ // Did this push the entire first item past the limit?
+ isAction &= widthLimit + firstActionWidth > 0;
+ }
+ }
+
+ if (isAction && groupId != 0) {
+ seenGroups.put(groupId, true);
+ } else if (inGroup) {
+ // We broke the width limit. Demote the whole group, they all overflow now.
+ seenGroups.put(groupId, false);
+ for (int j = 0; j < i; j++) {
+ MenuItemImpl areYouMyGroupie = visibleItems.get(j);
+ if (areYouMyGroupie.getGroupId() == groupId) {
+ // Give back the action slot
+ if (areYouMyGroupie.isActionButton()) maxActions++;
+ areYouMyGroupie.setIsActionButton(false);
+ }
+ }
+ }
+
+ if (isAction) maxActions--;
+
+ item.setIsActionButton(isAction);
+ } else {
+ // Neither requires nor requests an action button.
+ item.setIsActionButton(false);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ dismissPopupMenus();
+ super.onCloseMenu(menu, allMenusAreClosing);
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ SavedState state = new SavedState();
+ state.openSubMenuId = mOpenSubMenuId;
+ return state;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState saved = (SavedState) state;
+ if (saved.openSubMenuId > 0) {
+ MenuItem item = mMenu.findItem(saved.openSubMenuId);
+ if (item != null) {
+ SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
+ onSubMenuSelected(subMenu);
+ }
+ }
+ }
+
+ @Override
+ public void onSubUiVisibilityChanged(boolean isVisible) {
+ if (isVisible) {
+ // Not a submenu, but treat it like one.
+ super.onSubMenuSelected(null);
+ } else {
+ mMenu.close(false);
+ }
+ }
+
+ public void setMenuView(ActionMenuView menuView) {
+ mMenuView = menuView;
+ }
+
+ private static class SavedState implements Parcelable {
+ public int openSubMenuId;
+
+ SavedState() {
+ }
+
+ SavedState(Parcel in) {
+ openSubMenuId = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(openSubMenuId);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR
+ = new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ private class OverflowMenuButton extends ImageButton implements ActionMenuView.ActionMenuChildView {
+ public OverflowMenuButton(Context context) {
+ super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
+
+ setClickable(true);
+ setFocusable(true);
+ setVisibility(VISIBLE);
+ setEnabled(true);
+
+ setOnTouchListener(new ForwardingListener(this) {
+ @Override
+ public ListPopupWindow getPopup() {
+ if (mOverflowPopup == null) {
+ return null;
+ }
+
+ return mOverflowPopup.getPopup();
+ }
+
+ @Override
+ public boolean onForwardingStarted() {
+ showOverflowMenu();
+ return true;
+ }
+
+ @Override
+ public boolean onForwardingStopped() {
+ // Displaying the popup occurs asynchronously, so wait for
+ // the runnable to finish before deciding whether to stop
+ // forwarding.
+ if (mPostedOpenRunnable != null) {
+ return false;
+ }
+
+ hideOverflowMenu();
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public boolean performClick() {
+ if (super.performClick()) {
+ return true;
+ }
+
+ playSoundEffect(SoundEffectConstants.CLICK);
+ showOverflowMenu();
+ return true;
+ }
+
+ @Override
+ public boolean needsDividerBefore() {
+ return false;
+ }
+
+ @Override
+ public boolean needsDividerAfter() {
+ return false;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setCanOpenPopup(true);
+ }
+ }
+
+ private class OverflowPopup extends MenuPopupHelper {
+ public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
+ boolean overflowOnly) {
+ super(context, menu, anchorView, overflowOnly);
+ setGravity(Gravity.END);
+ setCallback(mPopupPresenterCallback);
+ }
+
+ @Override
+ public void onDismiss() {
+ super.onDismiss();
+ mMenu.close();
+ mOverflowPopup = null;
+ }
+ }
+
+ private class ActionButtonSubmenu extends MenuPopupHelper {
+ private SubMenuBuilder mSubMenu;
+
+ public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
+ super(context, subMenu);
+ mSubMenu = subMenu;
+
+ MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
+ if (!item.isActionButton()) {
+ // Give a reasonable anchor to nested submenus.
+ setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
+ }
+
+ setCallback(mPopupPresenterCallback);
+
+ boolean preserveIconSpacing = false;
+ final int count = subMenu.size();
+ for (int i = 0; i < count; i++) {
+ MenuItem childItem = subMenu.getItem(i);
+ if (childItem.isVisible() && childItem.getIcon() != null) {
+ preserveIconSpacing = true;
+ break;
+ }
+ }
+ setForceShowIcon(preserveIconSpacing);
+ }
+
+ @Override
+ public void onDismiss() {
+ super.onDismiss();
+ mActionButtonPopup = null;
+ mOpenSubMenuId = 0;
+ }
+ }
+
+ private class PopupPresenterCallback implements Callback {
+
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ if (subMenu == null) return false;
+
+ mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
+ final Callback cb = getCallback();
+ return cb != null ? cb.onOpenSubMenu(subMenu) : false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (menu instanceof SubMenuBuilder) {
+ ((SubMenuBuilder) menu).getRootMenu().close(false);
+ }
+ final Callback cb = getCallback();
+ if (cb != null) {
+ cb.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+ }
+
+ private class OpenOverflowRunnable implements Runnable {
+ private OverflowPopup mPopup;
+
+ public OpenOverflowRunnable(OverflowPopup popup) {
+ mPopup = popup;
+ }
+
+ public void run() {
+ mMenu.changeMenuMode();
+ final View menuView = (View) mMenuView;
+ if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
+ mOverflowPopup = mPopup;
+ }
+ mPostedOpenRunnable = null;
+ }
+ }
+
+ private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback {
+ @Override
+ public ListPopupWindow getPopup() {
+ return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
+ }
+ }
+}
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
new file mode 100644
index 0000000..3975edf
--- /dev/null
+++ b/core/java/android/widget/ActionMenuView.java
@@ -0,0 +1,696 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+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.MenuPresenter;
+import com.android.internal.view.menu.MenuView;
+
+/**
+ * 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";
+
+ static final int MIN_CELL_SIZE = 56; // dips
+ static final int GENERATED_ITEM_PADDING = 4; // dips
+
+ private MenuBuilder mMenu;
+
+ private boolean mReserveOverflow;
+ private ActionMenuPresenter mPresenter;
+ private boolean mFormatItems;
+ private int mFormatItemsWidth;
+ private int mMinCellSize;
+ private int mGeneratedItemPadding;
+
+ private OnMenuItemClickListener mOnMenuItemClickListener;
+
+ public ActionMenuView(Context context) {
+ this(context, null);
+ }
+
+ public ActionMenuView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setBaselineAligned(false);
+ final float density = context.getResources().getDisplayMetrics().density;
+ mMinCellSize = (int) (MIN_CELL_SIZE * density);
+ mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
+ }
+
+ /** @hide */
+ public void setPresenter(ActionMenuPresenter presenter) {
+ mPresenter = presenter;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mPresenter.updateMenuView(false);
+
+ if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
+ mPresenter.hideOverflowMenu();
+ mPresenter.showOverflowMenu();
+ }
+ }
+
+ public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
+ mOnMenuItemClickListener = listener;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // If we've been given an exact size to match, apply special formatting during layout.
+ final boolean wasFormatted = mFormatItems;
+ mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
+
+ if (wasFormatted != mFormatItems) {
+ mFormatItemsWidth = 0; // Reset this when switching modes
+ }
+
+ // Special formatting can change whether items can fit as action buttons.
+ // Kick the menu and update presenters when this changes.
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
+ mFormatItemsWidth = widthSize;
+ mMenu.onItemsChanged(true);
+ }
+
+ final int childCount = getChildCount();
+ if (mFormatItems && childCount > 0) {
+ onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
+ } else {
+ // Previous measurement at exact format may have set margins - reset them.
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ lp.leftMargin = lp.rightMargin = 0;
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
+ // We already know the width mode is EXACTLY if we're here.
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ final int widthPadding = getPaddingLeft() + getPaddingRight();
+ final int heightPadding = getPaddingTop() + getPaddingBottom();
+
+ final int itemHeightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ widthSize -= widthPadding;
+
+ // Divide the view into cells.
+ final int cellCount = widthSize / mMinCellSize;
+ final int cellSizeRemaining = widthSize % mMinCellSize;
+
+ if (cellCount == 0) {
+ // Give up, nothing fits.
+ setMeasuredDimension(widthSize, 0);
+ return;
+ }
+
+ final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
+
+ int cellsRemaining = cellCount;
+ int maxChildHeight = 0;
+ int maxCellsUsed = 0;
+ int expandableItemCount = 0;
+ int visibleItemCount = 0;
+ boolean hasOverflow = false;
+
+ // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
+ long smallestItemsAt = 0;
+
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == GONE) continue;
+
+ final boolean isGeneratedItem = child instanceof ActionMenuItemView;
+ visibleItemCount++;
+
+ if (isGeneratedItem) {
+ // Reset padding for generated menu item views; it may change below
+ // and views are recycled.
+ child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ lp.expanded = false;
+ lp.extraPixels = 0;
+ lp.cellsUsed = 0;
+ lp.expandable = false;
+ lp.leftMargin = 0;
+ lp.rightMargin = 0;
+ lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
+
+ // Overflow always gets 1 cell. No more, no less.
+ final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
+
+ final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
+ itemHeightSpec, heightPadding);
+
+ maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
+ if (lp.expandable) expandableItemCount++;
+ if (lp.isOverflowButton) hasOverflow = true;
+
+ cellsRemaining -= cellsUsed;
+ maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
+ if (cellsUsed == 1) smallestItemsAt |= (1 << i);
+ }
+
+ // When we have overflow and a single expanded (text) item, we want to try centering it
+ // visually in the available space even though overflow consumes some of it.
+ final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
+
+ // Divide space for remaining cells if we have items that can expand.
+ // Try distributing whole leftover cells to smaller items first.
+
+ boolean needsExpansion = false;
+ while (expandableItemCount > 0 && cellsRemaining > 0) {
+ int minCells = Integer.MAX_VALUE;
+ long minCellsAt = 0; // Bit locations are indices of relevant child views
+ int minCellsItemCount = 0;
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ // Don't try to expand items that shouldn't.
+ if (!lp.expandable) continue;
+
+ // Mark indices of children that can receive an extra cell.
+ if (lp.cellsUsed < minCells) {
+ minCells = lp.cellsUsed;
+ minCellsAt = 1 << i;
+ minCellsItemCount = 1;
+ } else if (lp.cellsUsed == minCells) {
+ minCellsAt |= 1 << i;
+ minCellsItemCount++;
+ }
+ }
+
+ // Items that get expanded will always be in the set of smallest items when we're done.
+ smallestItemsAt |= minCellsAt;
+
+ if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
+
+ // We have enough cells, all minimum size items will be incremented.
+ minCells++;
+
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if ((minCellsAt & (1 << i)) == 0) {
+ // If this item is already at our small item count, mark it for later.
+ if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
+ continue;
+ }
+
+ if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
+ // Add padding to this item such that it centers.
+ child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
+ }
+ lp.cellsUsed++;
+ lp.expanded = true;
+ cellsRemaining--;
+ }
+
+ needsExpansion = true;
+ }
+
+ // Divide any space left that wouldn't divide along cell boundaries
+ // evenly among the smallest items
+
+ final boolean singleItem = !hasOverflow && visibleItemCount == 1;
+ if (cellsRemaining > 0 && smallestItemsAt != 0 &&
+ (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
+ float expandCount = Long.bitCount(smallestItemsAt);
+
+ if (!singleItem) {
+ // The items at the far edges may only expand by half in order to pin to either side.
+ if ((smallestItemsAt & 1) != 0) {
+ LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
+ if (!lp.preventEdgeOffset) expandCount -= 0.5f;
+ }
+ if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
+ LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
+ if (!lp.preventEdgeOffset) expandCount -= 0.5f;
+ }
+ }
+
+ final int extraPixels = expandCount > 0 ?
+ (int) (cellsRemaining * cellSize / expandCount) : 0;
+
+ for (int i = 0; i < childCount; i++) {
+ if ((smallestItemsAt & (1 << i)) == 0) continue;
+
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (child instanceof ActionMenuItemView) {
+ // If this is one of our views, expand and measure at the larger size.
+ lp.extraPixels = extraPixels;
+ lp.expanded = true;
+ if (i == 0 && !lp.preventEdgeOffset) {
+ // First item gets part of its new padding pushed out of sight.
+ // The last item will get this implicitly from layout.
+ lp.leftMargin = -extraPixels / 2;
+ }
+ needsExpansion = true;
+ } else if (lp.isOverflowButton) {
+ lp.extraPixels = extraPixels;
+ lp.expanded = true;
+ lp.rightMargin = -extraPixels / 2;
+ needsExpansion = true;
+ } else {
+ // If we don't know what it is, give it some margins instead
+ // and let it center within its space. We still want to pin
+ // against the edges.
+ if (i != 0) {
+ lp.leftMargin = extraPixels / 2;
+ }
+ if (i != childCount - 1) {
+ lp.rightMargin = extraPixels / 2;
+ }
+ }
+ }
+
+ cellsRemaining = 0;
+ }
+
+ // Remeasure any items that have had extra space allocated to them.
+ if (needsExpansion) {
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ if (!lp.expanded) continue;
+
+ final int width = lp.cellsUsed * cellSize + lp.extraPixels;
+ child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ itemHeightSpec);
+ }
+ }
+
+ if (heightMode != MeasureSpec.EXACTLY) {
+ heightSize = maxChildHeight;
+ }
+
+ setMeasuredDimension(widthSize, heightSize);
+ }
+
+ /**
+ * Measure a child view to fit within cell-based formatting. The child's width
+ * will be measured to a whole multiple of cellSize.
+ *
+ * <p>Sets the expandable and cellsUsed fields of LayoutParams.
+ *
+ * @param child Child to measure
+ * @param cellSize Size of one cell
+ * @param cellsRemaining Number of cells remaining that this view can expand to fill
+ * @param parentHeightMeasureSpec MeasureSpec used by the parent view
+ * @param parentHeightPadding Padding present in the parent view
+ * @return Number of cells this child was measured to occupy
+ */
+ static int measureChildForCells(View child, int cellSize, int cellsRemaining,
+ int parentHeightMeasureSpec, int parentHeightPadding) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
+ parentHeightPadding;
+ final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
+ final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
+
+ final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
+ (ActionMenuItemView) child : null;
+ final boolean hasText = itemView != null && itemView.hasText();
+
+ int cellsUsed = 0;
+ if (cellsRemaining > 0 && (!hasText || cellsRemaining >= 2)) {
+ final int childWidthSpec = MeasureSpec.makeMeasureSpec(
+ cellSize * cellsRemaining, MeasureSpec.AT_MOST);
+ child.measure(childWidthSpec, childHeightSpec);
+
+ final int measuredWidth = child.getMeasuredWidth();
+ cellsUsed = measuredWidth / cellSize;
+ if (measuredWidth % cellSize != 0) cellsUsed++;
+ if (hasText && cellsUsed < 2) cellsUsed = 2;
+ }
+
+ final boolean expandable = !lp.isOverflowButton && hasText;
+ lp.expandable = expandable;
+
+ lp.cellsUsed = cellsUsed;
+ final int targetWidth = cellsUsed * cellSize;
+ child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
+ childHeightSpec);
+ return cellsUsed;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (!mFormatItems) {
+ super.onLayout(changed, left, top, right, bottom);
+ return;
+ }
+
+ final int childCount = getChildCount();
+ final int midVertical = (top + bottom) / 2;
+ final int dividerWidth = getDividerWidth();
+ int overflowWidth = 0;
+ int nonOverflowWidth = 0;
+ int nonOverflowCount = 0;
+ int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
+ boolean hasOverflow = false;
+ final boolean isLayoutRtl = isLayoutRtl();
+ for (int i = 0; i < childCount; i++) {
+ final View v = getChildAt(i);
+ if (v.getVisibility() == GONE) {
+ continue;
+ }
+
+ LayoutParams p = (LayoutParams) v.getLayoutParams();
+ if (p.isOverflowButton) {
+ overflowWidth = v.getMeasuredWidth();
+ if (hasDividerBeforeChildAt(i)) {
+ overflowWidth += dividerWidth;
+ }
+
+ int height = v.getMeasuredHeight();
+ int r;
+ int l;
+ if (isLayoutRtl) {
+ l = getPaddingLeft() + p.leftMargin;
+ r = l + overflowWidth;
+ } else {
+ r = getWidth() - getPaddingRight() - p.rightMargin;
+ l = r - overflowWidth;
+ }
+ int t = midVertical - (height / 2);
+ int b = t + height;
+ v.layout(l, t, r, b);
+
+ widthRemaining -= overflowWidth;
+ hasOverflow = true;
+ } else {
+ final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
+ nonOverflowWidth += size;
+ widthRemaining -= size;
+ if (hasDividerBeforeChildAt(i)) {
+ nonOverflowWidth += dividerWidth;
+ }
+ nonOverflowCount++;
+ }
+ }
+
+ if (childCount == 1 && !hasOverflow) {
+ // Center a single child
+ final View v = getChildAt(0);
+ final int width = v.getMeasuredWidth();
+ final int height = v.getMeasuredHeight();
+ final int midHorizontal = (right - left) / 2;
+ final int l = midHorizontal - width / 2;
+ final int t = midVertical - height / 2;
+ v.layout(l, t, l + width, t + height);
+ return;
+ }
+
+ final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
+ final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
+
+ if (isLayoutRtl) {
+ int startRight = getWidth() - getPaddingRight();
+ for (int i = 0; i < childCount; i++) {
+ final View v = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+ if (v.getVisibility() == GONE || lp.isOverflowButton) {
+ continue;
+ }
+
+ startRight -= lp.rightMargin;
+ int width = v.getMeasuredWidth();
+ int height = v.getMeasuredHeight();
+ int t = midVertical - height / 2;
+ v.layout(startRight - width, t, startRight, t + height);
+ startRight -= width + lp.leftMargin + spacerSize;
+ }
+ } else {
+ int startLeft = getPaddingLeft();
+ for (int i = 0; i < childCount; i++) {
+ final View v = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+ if (v.getVisibility() == GONE || lp.isOverflowButton) {
+ continue;
+ }
+
+ startLeft += lp.leftMargin;
+ int width = v.getMeasuredWidth();
+ int height = v.getMeasuredHeight();
+ int t = midVertical - height / 2;
+ v.layout(startLeft, t, startLeft + width, t + height);
+ startLeft += width + lp.rightMargin + spacerSize;
+ }
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mPresenter.dismissPopupMenus();
+ }
+
+ /** @hide */
+ public boolean isOverflowReserved() {
+ return mReserveOverflow;
+ }
+
+ /** @hide */
+ public void setOverflowReserved(boolean reserveOverflow) {
+ mReserveOverflow = reserveOverflow;
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ params.gravity = Gravity.CENTER_VERTICAL;
+ return params;
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ if (p != null) {
+ final LayoutParams result = p instanceof LayoutParams
+ ? new LayoutParams((LayoutParams) p)
+ : new LayoutParams(p);
+ if (result.gravity <= Gravity.NO_GRAVITY) {
+ result.gravity = Gravity.CENTER_VERTICAL;
+ }
+ return result;
+ }
+ return generateDefaultLayoutParams();
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ 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);
+ mMenu.setCallback(new MenuBuilderCallback());
+ mPresenter = new ActionMenuPresenter(context);
+ mPresenter.setMenuView(this);
+ mPresenter.setCallback(new ActionMenuPresenterCallback());
+ mMenu.addMenuPresenter(mPresenter);
+ }
+
+ return mMenu;
+ }
+
+ /**
+ * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public.
+ */
+ @Override
+ protected boolean hasDividerBeforeChildAt(int childIndex) {
+ if (childIndex == 0) {
+ return false;
+ }
+ final View childBefore = getChildAt(childIndex - 1);
+ final View child = getChildAt(childIndex);
+ boolean result = false;
+ if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
+ result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
+ }
+ if (childIndex > 0 && child instanceof ActionMenuChildView) {
+ result |= ((ActionMenuChildView) child).needsDividerBefore();
+ }
+ return result;
+ }
+
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ return false;
+ }
+
+ /**
+ * Interface responsible for receiving menu item click events if the items themselves
+ * do not have individual item click listeners.
+ */
+ public interface OnMenuItemClickListener {
+ /**
+ * This method will be invoked when a menu item is clicked if the item itself did
+ * not already handle the event.
+ *
+ * @param item {@link MenuItem} that was clicked
+ * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
+ */
+ public boolean onMenuItemClick(MenuItem item);
+ }
+
+ private class MenuBuilderCallback implements MenuBuilder.Callback {
+ @Override
+ public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ return mOnMenuItemClickListener != null &&
+ mOnMenuItemClickListener.onMenuItemClick(item);
+ }
+
+ @Override
+ public void onMenuModeChange(MenuBuilder menu) {
+ }
+ }
+
+ private class ActionMenuPresenterCallback implements ActionMenuPresenter.Callback {
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ }
+
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ 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) {
+ super(c, attrs);
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams other) {
+ super(other);
+ }
+
+ public LayoutParams(LayoutParams other) {
+ super((LinearLayout.LayoutParams) other);
+ isOverflowButton = other.isOverflowButton;
+ }
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ 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..b0a4e24 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -23,7 +23,9 @@ 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.util.GrowingArrayUtils;
import com.android.internal.widget.EditableInputConnection;
import android.R;
@@ -45,8 +47,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;
@@ -75,10 +75,11 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ActionMode;
import android.view.ActionMode.Callback;
-import android.view.DisplayList;
+import android.view.RenderNode;
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 +138,16 @@ public class Editor {
InputContentType mInputContentType;
InputMethodState mInputMethodState;
- DisplayList[] mTextDisplayLists;
+ private static class TextDisplayList {
+ RenderNode displayList;
+ boolean isDirty;
+ public TextDisplayList(String name) {
+ isDirty = true;
+ displayList = RenderNode.create(name);
+ }
+ boolean needsRecord() { return isDirty || !displayList.isValid(); }
+ }
+ TextDisplayList[] mTextDisplayLists;
boolean mFrozenWithFocus;
boolean mSelectionMoved;
@@ -262,7 +272,7 @@ public class Editor {
mTextView.removeCallbacks(mShowSuggestionRunnable);
}
- invalidateTextDisplayList();
+ destroyDisplayListsData();
if (mSpellChecker != null) {
mSpellChecker.closeSession();
@@ -277,6 +287,18 @@ public class Editor {
mTemporaryDetach = false;
}
+ private void destroyDisplayListsData() {
+ if (mTextDisplayLists != null) {
+ for (int i = 0; i < mTextDisplayLists.length; i++) {
+ RenderNode displayList = mTextDisplayLists[i] != null
+ ? mTextDisplayLists[i].displayList : null;
+ if (displayList != null && displayList.isValid()) {
+ displayList.destroyDisplayListData();
+ }
+ }
+ }
+ }
+
private void showError() {
if (mTextView.getWindowToken() == null) {
mShowErrorAfterAttach = true;
@@ -1319,7 +1341,7 @@ public class Editor {
if (layout instanceof DynamicLayout) {
if (mTextDisplayLists == null) {
- mTextDisplayLists = new DisplayList[ArrayUtils.idealObjectArraySize(0)];
+ mTextDisplayLists = ArrayUtils.emptyArray(TextDisplayList.class);
}
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();
+ RenderNode 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(hardwareCanvas);
// Same as drawDisplayList below, handled by our TextView's parent
blockDisplayList.setClipToBounds(false);
}
@@ -1421,10 +1441,7 @@ public class Editor {
}
// No available index found, the pool has to grow
- int newSize = ArrayUtils.idealIntArraySize(length + 1);
- DisplayList[] displayLists = new DisplayList[newSize];
- System.arraycopy(mTextDisplayLists, 0, displayLists, 0, length);
- mTextDisplayLists = displayLists;
+ mTextDisplayLists = GrowingArrayUtils.append(mTextDisplayLists, length, null);
return length;
}
@@ -1461,7 +1478,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 +1489,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 +1698,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 +2338,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 +2988,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 +3565,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 +3611,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 6dd93a7..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) {
@@ -1228,7 +1232,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (event.isConfirmKey()) {
+ if (KeyEvent.isConfirmKey(keyCode)) {
if (mReceivedInvokeKeyDown) {
if (mItemCount > 0) {
dispatchPress(mSelectedChild);
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..eedacb5 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);
}
@@ -657,7 +664,7 @@ public class ImageView extends View {
InputStream stream = null;
try {
stream = mContext.getContentResolver().openInputStream(mUri);
- d = Drawable.createFromStream(stream, null);
+ d = Drawable.createFromStreamThemed(stream, null, mContext.getTheme());
} catch (Exception e) {
Log.w("ImageView", "Unable to open content: " + mUri, e);
} finally {
@@ -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 13f3eb6..b47177a 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -24,6 +24,7 @@ import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.os.SystemClock;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.IntProperty;
@@ -33,7 +34,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;
@@ -843,7 +843,7 @@ public class ListPopupWindow {
// to select one of its items
if (keyCode != KeyEvent.KEYCODE_SPACE
&& (mDropDownList.getSelectedItemPosition() >= 0
- || !event.isConfirmKey())) {
+ || !KeyEvent.isConfirmKey(keyCode))) {
int curIndex = mDropDownList.getSelectedItemPosition();
boolean consumed;
@@ -931,7 +931,7 @@ public class ListPopupWindow {
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
boolean consumed = mDropDownList.onKeyUp(keyCode, event);
- if (consumed && event.isConfirmKey()) {
+ if (consumed && KeyEvent.isConfirmKey(keyCode)) {
// if the list accepts the key events and the key event was a click, the text view
// gets the selected item from the drop down as its content
dismiss();
@@ -1226,6 +1226,15 @@ public class ListPopupWindow {
forwarding = onTouchForwarded(event) || !onForwardingStopped();
} else {
forwarding = onTouchObserved(event) && onForwardingStarted();
+
+ if (forwarding) {
+ // Make sure we cancel any ongoing source event stream.
+ final long now = SystemClock.uptimeMillis();
+ final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
+ 0.0f, 0.0f, 0);
+ mSrc.onTouchEvent(e);
+ e.recycle();
+ }
}
mForwarding = forwarding;
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 2c44703..d6fa05a 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/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0d3df51..f7d20b53 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1516,6 +1516,75 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * Helper action to set a color filter on a compound drawable on a TextView. Supports relative
+ * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
+ */
+ private class TextViewDrawableColorFilterAction extends Action {
+ public TextViewDrawableColorFilterAction(int viewId, boolean isRelative, int index,
+ int color, PorterDuff.Mode mode) {
+ this.viewId = viewId;
+ this.isRelative = isRelative;
+ this.index = index;
+ this.color = color;
+ this.mode = mode;
+ }
+
+ public TextViewDrawableColorFilterAction(Parcel parcel) {
+ viewId = parcel.readInt();
+ isRelative = (parcel.readInt() != 0);
+ index = parcel.readInt();
+ color = parcel.readInt();
+ mode = readPorterDuffMode(parcel);
+ }
+
+ private PorterDuff.Mode readPorterDuffMode(Parcel parcel) {
+ int mode = parcel.readInt();
+ if (mode >= 0 && mode < PorterDuff.Mode.values().length) {
+ return PorterDuff.Mode.values()[mode];
+ } else {
+ return PorterDuff.Mode.CLEAR;
+ }
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(TAG);
+ dest.writeInt(viewId);
+ dest.writeInt(isRelative ? 1 : 0);
+ dest.writeInt(index);
+ dest.writeInt(color);
+ dest.writeInt(mode.ordinal());
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+ final TextView target = (TextView) root.findViewById(viewId);
+ if (target == null) return;
+ Drawable[] drawables = isRelative
+ ? target.getCompoundDrawablesRelative()
+ : target.getCompoundDrawables();
+ if (index < 0 || index >= 4) {
+ throw new IllegalStateException("index must be in range [0, 3].");
+ }
+ Drawable d = drawables[index];
+ if (d != null) {
+ d.mutate();
+ d.setColorFilter(color, mode);
+ }
+ }
+
+ public String getActionName() {
+ return "TextViewDrawableColorFilterAction";
+ }
+
+ final boolean isRelative;
+ final int index;
+ final int color;
+ final PorterDuff.Mode mode;
+
+ public final static int TAG = 17;
+ }
+
+ /**
* Simple class used to keep track of memory usage in a RemoteViews.
*
*/
@@ -1686,6 +1755,9 @@ public class RemoteViews implements Parcelable, Filter {
case SetRemoteViewsAdapterList.TAG:
mActions.add(new SetRemoteViewsAdapterList(parcel));
break;
+ case TextViewDrawableColorFilterAction.TAG:
+ mActions.add(new TextViewDrawableColorFilterAction(parcel));
+ break;
default:
throw new ActionException("Tag " + tag + " not found");
}
@@ -1921,6 +1993,28 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * Equivalent to applying a color filter on one of the drawables in
+ * {@link android.widget.TextView#getCompoundDrawablesRelative()}.
+ *
+ * @param viewId The id of the view whose text should change.
+ * @param index The index of the drawable in the array of
+ * {@link android.widget.TextView#getCompoundDrawablesRelative()} to set the color
+ * filter on. Must be in [0, 3].
+ * @param color The color of the color filter. See
+ * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}.
+ * @param mode The mode of the color filter. See
+ * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}.
+ * @hide
+ */
+ public void setTextViewCompoundDrawablesRelativeColorFilter(int viewId,
+ int index, int color, PorterDuff.Mode mode) {
+ if (index < 0 || index >= 4) {
+ throw new IllegalArgumentException("index must be in range [0, 3].");
+ }
+ addAction(new TextViewDrawableColorFilterAction(viewId, true, index, color, mode));
+ }
+
+ /**
* Equivalent to calling ImageView.setImageResource
*
* @param viewId The id of the view whose drawable should change
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..d8a6867 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);
@@ -1162,8 +1171,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
|| !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) {
if (mSearchable != null) {
launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, query.toString());
- setImeVisibility(false);
}
+ setImeVisibility(false);
dismissSuggestions();
}
}
@@ -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 fd6ca4c..cde8080 100644
--- a/core/java/android/widget/ShareActionProvider.java
+++ b/core/java/android/widget/ShareActionProvider.java
@@ -170,7 +170,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..595f023 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -35,6 +35,7 @@ import android.view.textservice.TextInfo;
import android.view.textservice.TextServicesManager;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
import java.text.BreakIterator;
import java.util.Locale;
@@ -105,9 +106,9 @@ public class SpellChecker implements SpellCheckerSessionListener {
mTextView = textView;
// Arbitrary: these arrays will automatically double their sizes on demand
- final int size = ArrayUtils.idealObjectArraySize(1);
- mIds = new int[size];
- mSpellCheckSpans = new SpellCheckSpan[size];
+ final int size = 1;
+ mIds = ArrayUtils.newUnpaddedIntArray(size);
+ mSpellCheckSpans = new SpellCheckSpan[mIds.length];
setLocale(mTextView.getSpellCheckerLocale());
@@ -184,17 +185,9 @@ public class SpellChecker implements SpellCheckerSessionListener {
if (mIds[i] < 0) return i;
}
- if (mLength == mSpellCheckSpans.length) {
- final int newSize = mLength * 2;
- int[] newIds = new int[newSize];
- SpellCheckSpan[] newSpellCheckSpans = new SpellCheckSpan[newSize];
- System.arraycopy(mIds, 0, newIds, 0, mLength);
- System.arraycopy(mSpellCheckSpans, 0, newSpellCheckSpans, 0, mLength);
- mIds = newIds;
- mSpellCheckSpans = newSpellCheckSpans;
- }
-
- mSpellCheckSpans[mLength] = new SpellCheckSpan();
+ mIds = GrowingArrayUtils.append(mIds, mLength, 0);
+ mSpellCheckSpans = GrowingArrayUtils.append(
+ mSpellCheckSpans, mLength, new SpellCheckSpan());
mLength++;
return mLength - 1;
}
@@ -731,10 +724,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..0203301 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);
}
@@ -574,7 +574,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
throw new FileNotFoundException("Failed to open " + uri);
}
try {
- return Drawable.createFromStream(stream, null);
+ return Drawable.createFromStreamThemed(stream, null, mContext.getTheme());
} finally {
try {
stream.close();
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 8460375..a7278da 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..79256e5
--- /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);
+
+ final int layoutResourceId = a.getResourceId(
+ R.styleable.TimePicker_internalLayout, R.layout.time_picker_holo);
+
+ a.recycle();
+
+ 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/Toolbar.java b/core/java/android/widget/Toolbar.java
new file mode 100644
index 0000000..075feba
--- /dev/null
+++ b/core/java/android/widget/Toolbar.java
@@ -0,0 +1,1048 @@
+/*
+ * 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.widget;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A standard toolbar for use within application content.
+ *
+ * <p>A Toolbar is a generalization of {@link android.app.ActionBar action bars} for use
+ * within application layouts. While an action bar is traditionally part of an
+ * {@link android.app.Activity Activity's} opaque window decor controlled by the framework,
+ * a Toolbar may be placed at any arbitrary level of nesting within a view hierarchy.
+ * An application may choose to designate a Toolbar as the action bar for an Activity
+ * using the {@link android.app.Activity#setActionBar(Toolbar) setActionBar()} method.</p>
+ *
+ * <p>Toolbar supports a more focused feature set than ActionBar. From start to end, a toolbar
+ * may contain a combination of the following optional elements:
+ *
+ * <ul>
+ * <li><em>A navigation button.</em> This may be an Up arrow, navigation menu toggle, close,
+ * collapse, done or another glyph of the app's choosing. This button should always be used
+ * to access other navigational destinations within the container of the Toolbar and
+ * its signified content or otherwise leave the current context signified by the Toolbar.</li>
+ * <li><em>A branded logo image.</em> This may extend to the height of the bar and can be
+ * arbitrarily wide.</li>
+ * <li><em>A title and subtitle.</em> The title should be a signpost for the Toolbar's current
+ * position in the navigation hierarchy and the content contained there. The subtitle,
+ * if present should indicate any extended information about the current content.
+ * If an app uses a logo image it should strongly consider omitting a title and subtitle.</li>
+ * <li><em>One or more custom views.</em> The application may add arbitrary child views
+ * to the Toolbar. They will appear at this position within the layout. If a child view's
+ * {@link LayoutParams} indicates a {@link Gravity} value of
+ * {@link Gravity#CENTER_HORIZONTAL CENTER_HORIZONTAL} the view will attempt to center
+ * within the available space remaining in the Toolbar after all other elements have been
+ * measured.</li>
+ * <li><em>An {@link ActionMenuView action menu}.</em> The menu of actions will pin to the
+ * end of the Toolbar offering a few
+ * <a href="http://developer.android.com/design/patterns/actionbar.html#ActionButtons">
+ * frequent, important or typical</a> actions along with an optional overflow menu for
+ * additional actions.</li>
+ * </ul>
+ * </p>
+ *
+ * <p>In modern Android UIs developers should lean more on a visually distinct color scheme for
+ * toolbars than on their application icon. The use of application icon plus title as a standard
+ * layout is discouraged on API 21 devices and newer.</p>
+ */
+public class Toolbar extends ViewGroup {
+ private ActionMenuView mMenuView;
+ private TextView mTitleTextView;
+ private TextView mSubtitleTextView;
+ private ImageButton mNavButtonView;
+ private ImageView mLogoView;
+
+ private int mTitleTextAppearance;
+ private int mSubtitleTextAppearance;
+ private int mTitleMarginStart;
+ private int mTitleMarginEnd;
+ private int mTitleMarginTop;
+ private int mTitleMarginBottom;
+
+ private int mGravity = Gravity.START | Gravity.CENTER_VERTICAL;
+
+ private CharSequence mTitleText;
+ private CharSequence mSubtitleText;
+
+ // Clear me after use.
+ private final ArrayList<View> mTempViews = new ArrayList<View>();
+
+ private OnMenuItemClickListener mOnMenuItemClickListener;
+
+ private final ActionMenuView.OnMenuItemClickListener mMenuViewItemClickListener =
+ new ActionMenuView.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ if (mOnMenuItemClickListener != null) {
+ return mOnMenuItemClickListener.onMenuItemClick(item);
+ }
+ return false;
+ }
+ };
+
+ public Toolbar(Context context) {
+ this(context, null);
+ }
+
+ public Toolbar(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.toolbarStyle);
+ }
+
+ public Toolbar(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public Toolbar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Toolbar,
+ defStyleAttr, defStyleRes);
+
+ mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0);
+ mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0);
+ mGravity = a.getInteger(R.styleable.Toolbar_gravity, mGravity);
+ mTitleMarginStart = mTitleMarginEnd = Math.max(0, a.getDimensionPixelOffset(
+ R.styleable.Toolbar_titleMargins, -1));
+
+ final int marginStart = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginStart, -1);
+ if (marginStart >= 0) {
+ mTitleMarginStart = marginStart;
+ }
+
+ final int marginEnd = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginEnd, -1);
+ if (marginEnd >= 0) {
+ mTitleMarginEnd = marginEnd;
+ }
+
+ final int marginTop = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginTop, -1);
+ if (marginTop >= 0) {
+ mTitleMarginTop = marginTop;
+ }
+
+ final int marginBottom = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginBottom,
+ -1);
+ if (marginBottom >= 0) {
+ mTitleMarginBottom = marginBottom;
+ }
+
+ final CharSequence title = a.getText(R.styleable.Toolbar_title);
+ if (!TextUtils.isEmpty(title)) {
+ setTitle(title);
+ }
+
+ final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle);
+ if (!TextUtils.isEmpty(subtitle)) {
+ setSubtitle(title);
+ }
+ a.recycle();
+ }
+
+ /**
+ * Set a logo drawable from a resource id.
+ *
+ * <p>This drawable should generally take the place of title text. The logo cannot be
+ * clicked. Apps using a logo should also supply a description using
+ * {@link #setLogoDescription(int)}.</p>
+ *
+ * @param resId ID of a drawable resource
+ */
+ public void setLogo(int resId) {
+ setLogo(getContext().getDrawable(resId));
+ }
+
+ /**
+ * Set a logo drawable.
+ *
+ * <p>This drawable should generally take the place of title text. The logo cannot be
+ * clicked. Apps using a logo should also supply a description using
+ * {@link #setLogoDescription(int)}.</p>
+ *
+ * @param drawable Drawable to use as a logo
+ */
+ public void setLogo(Drawable drawable) {
+ if (drawable != null) {
+ if (mLogoView == null) {
+ mLogoView = new ImageView(getContext());
+ }
+ if (mLogoView.getParent() == null) {
+ addSystemView(mLogoView);
+ }
+ } else if (mLogoView != null && mLogoView.getParent() != null) {
+ removeView(mLogoView);
+ }
+ if (mLogoView != null) {
+ mLogoView.setImageDrawable(drawable);
+ }
+ }
+
+ /**
+ * Return the current logo drawable.
+ *
+ * @return The current logo drawable
+ * @see #setLogo(int)
+ * @see #setLogo(android.graphics.drawable.Drawable)
+ */
+ public Drawable getLogo() {
+ return mLogoView != null ? mLogoView.getDrawable() : null;
+ }
+
+ /**
+ * Set a description of the toolbar's logo.
+ *
+ * <p>This description will be used for accessibility or other similar descriptions
+ * of the UI.</p>
+ *
+ * @param resId String resource id
+ */
+ public void setLogoDescription(int resId) {
+ setLogoDescription(getContext().getText(resId));
+ }
+
+ /**
+ * Set a description of the toolbar's logo.
+ *
+ * <p>This description will be used for accessibility or other similar descriptions
+ * of the UI.</p>
+ *
+ * @param description Description to set
+ */
+ public void setLogoDescription(CharSequence description) {
+ if (!TextUtils.isEmpty(description) && mLogoView == null) {
+ mLogoView = new ImageView(getContext());
+ }
+ if (mLogoView != null) {
+ mLogoView.setContentDescription(description);
+ }
+ }
+
+ /**
+ * Return the description of the toolbar's logo.
+ *
+ * @return A description of the logo
+ */
+ public CharSequence getLogoDescription() {
+ return mLogoView != null ? mLogoView.getContentDescription() : null;
+ }
+
+ /**
+ * Return the current title displayed in the toolbar.
+ *
+ * @return The current title
+ */
+ public CharSequence getTitle() {
+ return mTitleText;
+ }
+
+ /**
+ * Set the title of this toolbar.
+ *
+ * <p>A title should be used as the anchor for a section of content. It should
+ * describe or name the content being viewed.</p>
+ *
+ * @param resId Resource ID of a string to set as the title
+ */
+ public void setTitle(int resId) {
+ setTitle(getContext().getText(resId));
+ }
+
+ /**
+ * Set the title of this toolbar.
+ *
+ * <p>A title should be used as the anchor for a section of content. It should
+ * describe or name the content being viewed.</p>
+ *
+ * @param title Title to set
+ */
+ public void setTitle(CharSequence title) {
+ if (!TextUtils.isEmpty(title)) {
+ if (mTitleTextView == null) {
+ final Context context = getContext();
+ mTitleTextView = new TextView(context);
+ mTitleTextView.setTextAppearance(context, mTitleTextAppearance);
+ }
+ if (mTitleTextView.getParent() == null) {
+ addSystemView(mTitleTextView);
+ }
+ } else if (mTitleTextView != null && mTitleTextView.getParent() != null) {
+ removeView(mTitleTextView);
+ }
+ if (mTitleTextView != null) {
+ mTitleTextView.setText(title);
+ }
+ mTitleText = title;
+ }
+
+ /**
+ * Return the subtitle of this toolbar.
+ *
+ * @return The current subtitle
+ */
+ public CharSequence getSubtitle() {
+ return mSubtitleText;
+ }
+
+ /**
+ * Set the subtitle of this toolbar.
+ *
+ * <p>Subtitles should express extended information about the current content.</p>
+ *
+ * @param resId String resource ID
+ */
+ public void setSubtitle(int resId) {
+ setSubtitle(getContext().getText(resId));
+ }
+
+ /**
+ * Set the subtitle of this toolbar.
+ *
+ * <p>Subtitles should express extended information about the current content.</p>
+ *
+ * @param subtitle Subtitle to set
+ */
+ public void setSubtitle(CharSequence subtitle) {
+ if (!TextUtils.isEmpty(subtitle)) {
+ if (mSubtitleTextView == null) {
+ final Context context = getContext();
+ mSubtitleTextView = new TextView(context);
+ mSubtitleTextView.setTextAppearance(context, mSubtitleTextAppearance);
+ }
+ if (mSubtitleTextView.getParent() == null) {
+ addSystemView(mSubtitleTextView);
+ }
+ } else if (mSubtitleTextView != null && mSubtitleTextView.getParent() != null) {
+ removeView(mSubtitleTextView);
+ }
+ if (mSubtitleTextView != null) {
+ mSubtitleTextView.setText(subtitle);
+ }
+ mSubtitleText = subtitle;
+ }
+
+ /**
+ * Set the icon to use for the toolbar's navigation button.
+ *
+ * <p>The navigation button appears at the start of the toolbar if present. Setting an icon
+ * will make the navigation button visible.</p>
+ *
+ * <p>If you use a navigation icon you should also set a description for its action using
+ * {@link #setNavigationDescription(int)}. This is used for accessibility and tooltips.</p>
+ *
+ * @param resId Resource ID of a drawable to set
+ */
+ public void setNavigationIcon(int resId) {
+ setNavigationIcon(getContext().getDrawable(resId));
+ }
+
+ /**
+ * Set the icon to use for the toolbar's navigation button.
+ *
+ * <p>The navigation button appears at the start of the toolbar if present. Setting an icon
+ * will make the navigation button visible.</p>
+ *
+ * <p>If you use a navigation icon you should also set a description for its action using
+ * {@link #setNavigationDescription(int)}. This is used for accessibility and tooltips.</p>
+ *
+ * @param icon Drawable to set
+ */
+ public void setNavigationIcon(Drawable icon) {
+ if (icon != null) {
+ ensureNavButtonView();
+ if (mNavButtonView.getParent() == null) {
+ addSystemView(mNavButtonView);
+ }
+ } else if (mNavButtonView != null && mNavButtonView.getParent() != null) {
+ removeView(mNavButtonView);
+ }
+ if (mNavButtonView != null) {
+ mNavButtonView.setImageDrawable(icon);
+ }
+ }
+
+ /**
+ * Return the current drawable used as the navigation icon.
+ *
+ * @return The navigation icon drawable
+ */
+ public Drawable getNavigationIcon() {
+ return mNavButtonView != null ? mNavButtonView.getDrawable() : null;
+ }
+
+ /**
+ * Set a description for the navigation button.
+ *
+ * <p>This description string is used for accessibility, tooltips and other facilities
+ * to improve discoverability.</p>
+ *
+ * @param resId Resource ID of a string to set
+ */
+ public void setNavigationDescription(int resId) {
+ setNavigationDescription(getContext().getText(resId));
+ }
+
+ /**
+ * Set a description for the navigation button.
+ *
+ * <p>This description string is used for accessibility, tooltips and other facilities
+ * to improve discoverability.</p>
+ *
+ * @param description String to set as the description
+ */
+ public void setNavigationDescription(CharSequence description) {
+ if (!TextUtils.isEmpty(description)) {
+ ensureNavButtonView();
+ }
+ if (mNavButtonView != null) {
+ mNavButtonView.setContentDescription(description);
+ }
+ }
+
+ /**
+ * Set a listener to respond to navigation events.
+ *
+ * <p>This listener will be called whenever the user clicks the navigation button
+ * at the start of the toolbar. An icon must be set for the navigation button to appear.</p>
+ *
+ * @param listener Listener to set
+ * @see #setNavigationIcon(android.graphics.drawable.Drawable)
+ */
+ public void setNavigationOnClickListener(OnClickListener listener) {
+ ensureNavButtonView();
+ mNavButtonView.setOnClickListener(listener);
+ }
+
+ /**
+ * Return the Menu shown in the toolbar.
+ *
+ * <p>Applications that wish to populate the toolbar's menu can do so from here. To use
+ * an XML menu resource, use {@link #inflateMenu(int)}.</p>
+ *
+ * @return The toolbar's Menu
+ */
+ public Menu getMenu() {
+ if (mMenuView == null) {
+ mMenuView = new ActionMenuView(getContext());
+ mMenuView.setOnMenuItemClickListener(mMenuViewItemClickListener);
+ addSystemView(mMenuView);
+ }
+ return mMenuView.getMenu();
+ }
+
+ private MenuInflater getMenuInflater() {
+ return new MenuInflater(getContext());
+ }
+
+ /**
+ * Inflate a menu resource into this toolbar.
+ *
+ * <p>Inflate an XML menu resource into this toolbar. Existing items in the menu will not
+ * be modified or removed.</p>
+ *
+ * @param resId ID of a menu resource to inflate
+ */
+ public void inflateMenu(int resId) {
+ getMenuInflater().inflate(resId, getMenu());
+ }
+
+ /**
+ * Set a listener to respond to menu item click events.
+ *
+ * <p>This listener will be invoked whenever a user selects a menu item from
+ * the action buttons presented at the end of the toolbar or the associated overflow.</p>
+ *
+ * @param listener Listener to set
+ */
+ public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
+ mOnMenuItemClickListener = listener;
+ }
+
+ private void ensureNavButtonView() {
+ if (mNavButtonView == null) {
+ mNavButtonView = new ImageButton(getContext(), null, R.attr.borderlessButtonStyle);
+ }
+ }
+
+ private void addSystemView(View v) {
+ final LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT);
+ lp.mViewType = LayoutParams.SYSTEM;
+ addView(v, lp);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ SavedState state = new SavedState(super.onSaveInstanceState());
+ return state;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ final SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = 0;
+ int height = 0;
+ int childState = 0;
+
+ // System views measure first.
+
+ if (shouldLayout(mNavButtonView)) {
+ measureChildWithMargins(mNavButtonView, widthMeasureSpec, width, heightMeasureSpec, 0);
+ width += mNavButtonView.getMeasuredWidth() + getHorizontalMargins(mNavButtonView);
+ height = Math.max(height, mNavButtonView.getMeasuredHeight() +
+ getVerticalMargins(mNavButtonView));
+ childState = combineMeasuredStates(childState, mNavButtonView.getMeasuredState());
+ }
+
+ if (shouldLayout(mMenuView)) {
+ measureChildWithMargins(mMenuView, widthMeasureSpec, width, heightMeasureSpec, 0);
+ width += mMenuView.getMeasuredWidth() + getHorizontalMargins(mMenuView);
+ height = Math.max(height, mMenuView.getMeasuredHeight() +
+ getVerticalMargins(mMenuView));
+ childState = combineMeasuredStates(childState, mMenuView.getMeasuredState());
+ }
+
+ if (shouldLayout(mLogoView)) {
+ measureChildWithMargins(mLogoView, widthMeasureSpec, width, heightMeasureSpec, 0);
+ width += mLogoView.getMeasuredWidth() + getHorizontalMargins(mLogoView);
+ height = Math.max(height, mLogoView.getMeasuredHeight() +
+ getVerticalMargins(mLogoView));
+ childState = combineMeasuredStates(childState, mLogoView.getMeasuredState());
+ }
+
+ int titleWidth = 0;
+ int titleHeight = 0;
+ final int titleVertMargins = mTitleMarginTop + mTitleMarginBottom;
+ final int titleHorizMargins = mTitleMarginStart + mTitleMarginEnd;
+ if (shouldLayout(mTitleTextView)) {
+ measureChildWithMargins(mTitleTextView, widthMeasureSpec, width + titleHorizMargins,
+ heightMeasureSpec, titleVertMargins);
+ titleWidth = mTitleTextView.getMeasuredWidth() + getHorizontalMargins(mTitleTextView);
+ titleHeight = mTitleTextView.getMeasuredHeight() + getVerticalMargins(mTitleTextView);
+ childState = combineMeasuredStates(childState, mTitleTextView.getMeasuredState());
+ }
+ if (shouldLayout(mSubtitleTextView)) {
+ measureChildWithMargins(mSubtitleTextView, widthMeasureSpec, width + titleHorizMargins,
+ heightMeasureSpec, titleHeight + titleVertMargins);
+ titleWidth = Math.max(titleWidth, mSubtitleTextView.getMeasuredWidth() +
+ getHorizontalMargins(mSubtitleTextView));
+ titleHeight += mSubtitleTextView.getMeasuredHeight() +
+ getVerticalMargins(mSubtitleTextView);
+ childState = combineMeasuredStates(childState, mSubtitleTextView.getMeasuredState());
+ }
+
+ width += titleWidth;
+ height = Math.max(height, titleHeight);
+
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.mViewType == LayoutParams.SYSTEM || !shouldLayout(child)) {
+ // We already got all system views above. Skip them and GONE views.
+ continue;
+ }
+
+ measureChildWithMargins(child, widthMeasureSpec, width, heightMeasureSpec, 0);
+ width += child.getMeasuredWidth() + getHorizontalMargins(child);
+ height = Math.max(height, child.getMeasuredHeight() + getVerticalMargins(child));
+ childState = combineMeasuredStates(childState, child.getMeasuredState());
+ }
+
+ // Measurement already took padding into account for available space for the children,
+ // add it in for the final size.
+ width += getPaddingLeft() + getPaddingRight();
+ height += getPaddingTop() + getPaddingBottom();
+
+ final int measuredWidth = resolveSizeAndState(
+ Math.max(width, getSuggestedMinimumWidth()),
+ widthMeasureSpec, childState & MEASURED_STATE_MASK);
+ final int measuredHeight = resolveSizeAndState(
+ Math.max(height, getSuggestedMinimumHeight()),
+ heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT);
+ setMeasuredDimension(measuredWidth, measuredHeight);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ final int width = getWidth();
+ final int height = getHeight();
+ final int paddingLeft = getPaddingLeft();
+ final int paddingRight = getPaddingRight();
+ final int paddingTop = getPaddingTop();
+ final int paddingBottom = getPaddingBottom();
+ int left = paddingLeft;
+ int right = width - paddingRight;
+
+ if (shouldLayout(mNavButtonView)) {
+ if (isRtl) {
+ right = layoutChildRight(mNavButtonView, right);
+ } else {
+ left = layoutChildLeft(mNavButtonView, left);
+ }
+ }
+
+ if (shouldLayout(mMenuView)) {
+ if (isRtl) {
+ left = layoutChildLeft(mMenuView, left);
+ } else {
+ right = layoutChildRight(mMenuView, right);
+ }
+ }
+
+ if (shouldLayout(mLogoView)) {
+ if (isRtl) {
+ right = layoutChildRight(mLogoView, right);
+ } else {
+ left = layoutChildLeft(mLogoView, left);
+ }
+ }
+
+ final boolean layoutTitle = shouldLayout(mTitleTextView);
+ final boolean layoutSubtitle = shouldLayout(mSubtitleTextView);
+ int titleHeight = 0;
+ if (layoutTitle) {
+ final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
+ titleHeight += lp.topMargin + mTitleTextView.getMeasuredHeight() + lp.bottomMargin;
+ }
+ if (layoutSubtitle) {
+ final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
+ titleHeight += lp.bottomMargin + mTitleTextView.getMeasuredHeight() + lp.bottomMargin;
+ }
+
+ if (layoutTitle || layoutSubtitle) {
+ int titleTop;
+ switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) {
+ case Gravity.TOP:
+ titleTop = getPaddingTop();
+ break;
+ default:
+ case Gravity.CENTER_VERTICAL:
+ final View child = layoutTitle ? mTitleTextView : mSubtitleTextView;
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final int space = height - paddingTop - paddingBottom;
+ int spaceAbove = (space - titleHeight) / 2;
+ if (spaceAbove < lp.topMargin + mTitleMarginTop) {
+ spaceAbove = lp.topMargin + mTitleMarginTop;
+ } else {
+ final int spaceBelow = height - paddingBottom - titleHeight -
+ spaceAbove - paddingTop;
+ if (spaceBelow < lp.bottomMargin + mTitleMarginBottom) {
+ spaceAbove = Math.max(0, spaceAbove -
+ (lp.bottomMargin + mTitleMarginBottom - spaceBelow));
+ }
+ }
+ titleTop = paddingTop + spaceAbove;
+ break;
+ case Gravity.BOTTOM:
+ titleTop = height - paddingBottom - titleHeight;
+ break;
+ }
+ if (isRtl) {
+ int titleRight = right;
+ int subtitleRight = right;
+ titleTop += mTitleMarginTop;
+ if (layoutTitle) {
+ final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
+ titleRight -= lp.rightMargin + mTitleMarginStart;
+ titleTop += lp.topMargin;
+ final int titleLeft = titleRight - mTitleTextView.getMeasuredWidth();
+ final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight();
+ mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
+ titleRight = titleLeft - lp.leftMargin - mTitleMarginEnd;
+ titleTop = titleBottom + lp.bottomMargin;
+ }
+ if (layoutSubtitle) {
+ final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
+ subtitleRight -= lp.rightMargin + mTitleMarginStart;
+ titleTop += lp.topMargin;
+ final int subtitleLeft = subtitleRight - mSubtitleTextView.getMeasuredWidth();
+ final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight();
+ mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom);
+ subtitleRight = subtitleRight - lp.leftMargin - mTitleMarginEnd;
+ titleTop = subtitleBottom + lp.bottomMargin;
+ }
+ right = Math.max(titleRight, subtitleRight);
+ } else {
+ int titleLeft = left;
+ int subtitleLeft = left;
+ titleTop += mTitleMarginTop;
+ if (layoutTitle) {
+ final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
+ titleLeft += lp.leftMargin + mTitleMarginStart;
+ titleTop += lp.topMargin;
+ final int titleRight = titleLeft + mTitleTextView.getMeasuredWidth();
+ final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight();
+ mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
+ titleLeft = titleRight + lp.rightMargin + mTitleMarginEnd;
+ titleTop = titleBottom + lp.bottomMargin;
+ }
+ if (layoutSubtitle) {
+ final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
+ subtitleLeft += lp.leftMargin + mTitleMarginStart;
+ titleTop += lp.topMargin;
+ final int subtitleRight = subtitleLeft + mSubtitleTextView.getMeasuredWidth();
+ final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight();
+ mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom);
+ subtitleLeft = subtitleRight + lp.rightMargin + mTitleMarginEnd;
+ titleTop = subtitleBottom + lp.bottomMargin;
+ }
+ left = Math.max(titleLeft, subtitleLeft);
+ }
+ }
+
+ // Get all remaining children sorted for layout. This is all prepared
+ // such that absolute layout direction can be used below.
+
+ addCustomViewsWithGravity(mTempViews, Gravity.LEFT);
+ final int leftViewsCount = mTempViews.size();
+ for (int i = 0; i < leftViewsCount; i++) {
+ left = layoutChildLeft(getChildAt(i), left);
+ }
+
+ addCustomViewsWithGravity(mTempViews, Gravity.RIGHT);
+ final int rightViewsCount = mTempViews.size();
+ for (int i = 0; i < rightViewsCount; i++) {
+ right = layoutChildRight(getChildAt(i), right);
+ }
+
+ // Centered views try to center with respect to the whole bar, but views pinned
+ // to the left or right can push the mass of centered views to one side or the other.
+ addCustomViewsWithGravity(mTempViews, Gravity.CENTER);
+ final int centerViewsWidth = getViewListMeasuredWidth(mTempViews);
+ final int parentCenter = paddingLeft + (width - paddingLeft - paddingRight) / 2;
+ final int halfCenterViewsWidth = centerViewsWidth / 2;
+ int centerLeft = parentCenter - halfCenterViewsWidth;
+ final int centerRight = centerLeft + centerViewsWidth;
+ if (centerLeft < left) {
+ centerLeft = left;
+ } else if (centerRight > right) {
+ centerLeft -= centerRight - right;
+ }
+
+ final int centerViewsCount = mTempViews.size();
+ for (int i = 0; i < centerViewsCount; i++) {
+ centerLeft = layoutChildLeft(getChildAt(i), centerLeft);
+ }
+ mTempViews.clear();
+ }
+
+ private int getViewListMeasuredWidth(List<View> views) {
+ int width = 0;
+ final int count = views.size();
+ for (int i = 0; i < count; i++) {
+ final View v = views.get(i);
+ final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+ width += lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin;
+ }
+ return width;
+ }
+
+ private int layoutChildLeft(View child, int left) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ left += lp.leftMargin;
+ int top = getChildTop(child);
+ child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
+ left += lp.rightMargin;
+ return left;
+ }
+
+ private int layoutChildRight(View child, int right) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ right -= lp.rightMargin;
+ int top = getChildTop(child);
+ child.layout(right - child.getMeasuredWidth(), top, right, top + child.getMeasuredHeight());
+ right -= lp.leftMargin;
+ return right;
+ }
+
+ private int getChildTop(View child) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ switch (getChildVerticalGravity(lp.gravity)) {
+ case Gravity.TOP:
+ return getPaddingTop();
+
+ case Gravity.BOTTOM:
+ return getPaddingBottom() - child.getMeasuredHeight() - lp.bottomMargin;
+
+ default:
+ case Gravity.CENTER_VERTICAL:
+ final int paddingTop = getPaddingTop();
+ final int paddingBottom = getPaddingBottom();
+ final int height = getHeight();
+ final int childHeight = child.getMeasuredHeight();
+ final int space = height - paddingTop - paddingBottom;
+ int spaceAbove = (space - childHeight) / 2;
+ if (spaceAbove < lp.topMargin) {
+ spaceAbove = lp.topMargin;
+ } else {
+ final int spaceBelow = height - paddingBottom - childHeight -
+ spaceAbove - paddingTop;
+ if (spaceBelow < lp.bottomMargin) {
+ spaceAbove = Math.max(0, spaceAbove - (lp.bottomMargin - spaceBelow));
+ }
+ }
+ return paddingTop + spaceAbove;
+ }
+ }
+
+ private int getChildVerticalGravity(int gravity) {
+ final int vgrav = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+ switch (vgrav) {
+ case Gravity.TOP:
+ case Gravity.BOTTOM:
+ case Gravity.CENTER_VERTICAL:
+ return vgrav;
+ default:
+ return mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+ }
+ }
+
+ /**
+ * Prepare a list of non-SYSTEM child views. If the layout direction is RTL
+ * this will be in reverse child order.
+ *
+ * @param views List to populate. It will be cleared before use.
+ * @param gravity Horizontal gravity to match against
+ */
+ private void addCustomViewsWithGravity(List<View> views, int gravity) {
+ final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ final int childCount = getChildCount();
+ final int absGrav = Gravity.getAbsoluteGravity(gravity, getLayoutDirection());
+
+ views.clear();
+
+ if (isRtl) {
+ for (int i = childCount - 1; i >= 0; i--) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.mViewType != LayoutParams.SYSTEM && shouldLayout(child) &&
+ getChildHorizontalGravity(lp.gravity) == absGrav) {
+ views.add(child);
+ }
+
+ }
+ } else {
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.mViewType != LayoutParams.SYSTEM && shouldLayout(child) &&
+ getChildHorizontalGravity(lp.gravity) == absGrav) {
+ views.add(child);
+ }
+ }
+ }
+ }
+
+ private int getChildHorizontalGravity(int gravity) {
+ final int ld = getLayoutDirection();
+ final int absGrav = Gravity.getAbsoluteGravity(gravity, ld);
+ final int hGrav = absGrav & Gravity.HORIZONTAL_GRAVITY_MASK;
+ switch (hGrav) {
+ case Gravity.LEFT:
+ case Gravity.RIGHT:
+ case Gravity.CENTER_HORIZONTAL:
+ return hGrav;
+ default:
+ return ld == LAYOUT_DIRECTION_RTL ? Gravity.RIGHT : Gravity.LEFT;
+ }
+ }
+
+ private boolean shouldLayout(View view) {
+ return view != null && view.getParent() == this && view.getVisibility() != GONE;
+ }
+
+ private int getHorizontalMargins(View v) {
+ final MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
+ return mlp.getMarginStart() + mlp.getMarginEnd();
+ }
+
+ private int getVerticalMargins(View v) {
+ final MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
+ return mlp.topMargin + mlp.bottomMargin;
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return super.generateLayoutParams(attrs);
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ if (p instanceof LayoutParams) {
+ return new LayoutParams((LayoutParams) p);
+ } else if (p instanceof MarginLayoutParams) {
+ return new LayoutParams((MarginLayoutParams) p);
+ } else {
+ return new LayoutParams(p);
+ }
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return super.checkLayoutParams(p) && p instanceof LayoutParams;
+ }
+
+ private static boolean isCustomView(View child) {
+ return ((LayoutParams) child.getLayoutParams()).mViewType == LayoutParams.CUSTOM;
+ }
+
+ /**
+ * Interface responsible for receiving menu item click events if the items themselves
+ * do not have individual item click listeners.
+ */
+ public interface OnMenuItemClickListener {
+ /**
+ * This method will be invoked when a menu item is clicked if the item itself did
+ * not already handle the event.
+ *
+ * @param item {@link MenuItem} that was clicked
+ * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
+ */
+ public boolean onMenuItemClick(MenuItem item);
+ }
+
+ /**
+ * Layout information for child views of Toolbars.
+ *
+ * @attr ref android.R.styleable#Toolbar_LayoutParams_layout_gravity
+ */
+ public static class LayoutParams extends MarginLayoutParams {
+ /**
+ * Gravity for the view associated with these LayoutParams.
+ *
+ * @see android.view.Gravity
+ */
+ @ViewDebug.ExportedProperty(category = "layout", mapping = {
+ @ViewDebug.IntToString(from = -1, to = "NONE"),
+ @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"),
+ @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"),
+ @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"),
+ @ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"),
+ @ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"),
+ @ViewDebug.IntToString(from = Gravity.START, to = "START"),
+ @ViewDebug.IntToString(from = Gravity.END, to = "END"),
+ @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"),
+ @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"),
+ @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
+ @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"),
+ @ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"),
+ @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL")
+ })
+ public int gravity = Gravity.NO_GRAVITY;
+
+ static final int CUSTOM = 0;
+ static final int SYSTEM = 1;
+
+ int mViewType = CUSTOM;
+
+ public LayoutParams(@NonNull Context c, AttributeSet attrs) {
+ super(c, attrs);
+
+ TypedArray a = c.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.Toolbar_LayoutParams);
+ gravity = a.getInt(
+ com.android.internal.R.styleable.Toolbar_LayoutParams_layout_gravity,
+ Gravity.NO_GRAVITY);
+ a.recycle();
+ }
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ this.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
+ }
+
+ public LayoutParams(int width, int height, int gravity) {
+ super(width, height);
+ this.gravity = gravity;
+ }
+
+ public LayoutParams(int gravity) {
+ this(WRAP_CONTENT, MATCH_PARENT, gravity);
+ }
+
+ public LayoutParams(LayoutParams source) {
+ super(source);
+
+ this.gravity = source.gravity;
+ }
+
+ public LayoutParams(MarginLayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ }
+ }
+
+ static class SavedState extends BaseSavedState {
+ public SavedState(Parcel source) {
+ super(source);
+ }
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ }
+
+ public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
+
+ @Override
+ public SavedState createFromParcel(Parcel source) {
+ return new SavedState(source);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
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;